fix: onclick navigateToDep uid interpolation; clear cache on status filter change

This commit is contained in:
Naeel
2026-04-16 14:09:55 +03:00
parent d0376cd6a9
commit 22d4d928ab
+181 -75
View File
@@ -33,12 +33,6 @@
.filters { display: flex; gap: 10px; flex-wrap: wrap; }
.filter-label { display: flex; align-items: center; gap: 5px; cursor: pointer; font-size: 12px; }
.filter-label input { cursor: pointer; width: 18px; height: 18px; accent-color: #58a6ff; }
.data-ts { font-size: 11px; color: #8b949e; white-space: nowrap; margin-right: 8px; }
.nav-back { padding: 6px 16px; background: #161b22; color: #8b949e; font-size: 12px; border-bottom: 1px solid #30363d; }
.nav-back a { color: #58a6ff; text-decoration: none; }
.nav-back a:hover { text-decoration: underline; }
.dep-item { cursor: pointer; border-radius: 4px; transition: background .15s; }
.dep-item:hover { background: #21262d; }
.btn-refresh { padding: 5px 12px; background: #21262d; border: 1px solid #30363d; border-radius: 6px; color: #e1e4e8; cursor: pointer; font-size: 12px; }
.btn-refresh:hover { background: #30363d; }
.btn-graph { padding: 5px 12px; background: #1f6feb; border: 1px solid #3b82f6; border-radius: 6px; color: #fff; cursor: pointer; font-size: 12px; }
@@ -188,7 +182,6 @@
<label class="filter-label" style="margin-left:12px;border-left:1px solid #555;padding-left:12px"><input type="checkbox" id="chk-auxiliary"> <span style="font-size:12px;color:#8b949e">Служебные</span></label>
</div>
<div class="spacer"></div>
<span id="data-ts" style="font-size:11px;color:#8b949e;"></span>
<button class="btn-refresh" id="btn-refresh">&#8635; Обновить</button>
<button class="btn-graph" id="btn-running-graph">Граф зависимостей</button>
<button class="btn-theme" id="btn-theme" title="Сменить тему">🌙</button>
@@ -253,6 +246,9 @@ var graphAllKey = "";
var graphWarmupScheduled = false;
var graphDrawing = false;
var graphFetchInFlight = false;
var rawDetail = {};
var navBackUid = null;
var renderTimer = null;
function escHtml(v){
return String(v == null ? "" : v)
@@ -663,7 +659,18 @@ function depUid(dep){
function visibleUidSet(){
var s = new Set();
allInstances.forEach(function(i){ if(i && i.instanceUid) s.add(i.instanceUid); });
var checked = Array.from(document.querySelectorAll("#filters input[value]:checked")).map(function(i){ return i.value; });
var auxEl = document.getElementById("chk-auxiliary");
var showAux = auxEl ? auxEl.checked : true;
allInstances.forEach(function(i){
if(!i || !i.instanceUid) return;
if(checked.length && checked.indexOf(i.explainedStatus) < 0) return;
if(!showAux){
var det = rawDetail[i.instanceUid];
if(det && det.isAuxiliary) return;
}
s.add(i.instanceUid);
});
return s;
}
@@ -776,7 +783,7 @@ function doLogout(){
}
function getChecked(){
return Array.from(document.querySelectorAll("#filters input:checked")).map(function(i){ return i.value; });
return Array.from(document.querySelectorAll("#filters input[value]:checked")).map(function(i){ return i.value; });
}
function sClass(s){
@@ -864,19 +871,26 @@ function toggle(uid, row){
row.classList.add("expanded");
dr.classList.add("open");
if(!cache[uid]){
dc.innerHTML = "<div class=\"loading\">Загрузка...</div>";
fetch(API + "/instances/" + uid, { headers: apiHeaders() })
.then(function(r){
if(r.status === 401){ doLogout(); return null; }
return r.json();
})
.then(function(d){
if(!d) return;
cache[uid] = buildDetail(d.instance);
dc.innerHTML = cache[uid];
bindTabs(dc);
})
.catch(function(e){ cache[uid] = "<div class=\"err-msg\">" + e.message + "</div>"; dc.innerHTML = cache[uid]; });
if(rawDetail[uid]){
cache[uid] = buildDetail(rawDetail[uid]);
dc.innerHTML = cache[uid];
bindTabs(dc);
} else {
dc.innerHTML = "<div class=\"loading\">Загрузка...</div>";
fetch(API + "/instances/" + uid, { headers: apiHeaders() })
.then(function(r){
if(r.status === 401){ doLogout(); return null; }
return r.json();
})
.then(function(d){
if(!d) return;
rawDetail[uid] = d.instance;
cache[uid] = buildDetail(d.instance);
dc.innerHTML = cache[uid];
bindTabs(dc);
})
.catch(function(e){ cache[uid] = "<div class=\"err-msg\">" + e.message + "</div>"; dc.innerHTML = cache[uid]; });
}
} else {
dc.innerHTML = cache[uid];
bindTabs(dc);
@@ -946,10 +960,11 @@ function buildDetail(inst){
var depsHtml = "";
if(deps.length){
deps.forEach(function(d){
depsHtml += "<div class=\"dep-item\"><div class=\"dep-name\">" + (d.displayName||d.uid) +
"</div><div class=\"dep-meta\">" + (d.svc||"") +
(d.param ? " &middot; через <code>" + d.param + "</code>" : "") +
(d.descr ? " &middot; " + d.descr : "") + "</div></div>";
var did = depUid(d);
depsHtml += "<div class=\"dep-item\" onclick=\"navigateToDep('" + did + "')\"><div class=\"dep-name\">&#9658; " + escHtml(d.displayName||d.uid) +
"</div><div class=\"dep-meta\">" + escHtml(d.svc||"") +
(d.param ? " &middot; через <code>" + escHtml(d.param) + "</code>" : "") +
(d.descr ? " &middot; " + escHtml(d.descr) : "") + "</div></div>";
});
} else {
depsHtml = "<div class=\"empty-msg\">Нет зависимостей</div>";
@@ -957,9 +972,10 @@ function buildDetail(inst){
var depOnHtml = "";
if(depOn.length){
depOn.forEach(function(d){
depOnHtml += "<div class=\"dep-item\"><div class=\"dep-name\">" + (d.displayName||d.uid) +
"</div><div class=\"dep-meta\">" + (d.svc||"") +
(d.param ? " &middot; через <code>" + d.param + "</code>" : "") + "</div></div>";
var did = depUid(d);
depOnHtml += "<div class=\"dep-item\" onclick=\"navigateToDep('" + did + "')\"><div class=\"dep-name\">&#9658; " + escHtml(d.displayName||d.uid) +
"</div><div class=\"dep-meta\">" + escHtml(d.svc||"") +
(d.param ? " &middot; через <code>" + escHtml(d.param) + "</code>" : "") + "</div></div>";
});
} else {
depOnHtml = "<div class=\"empty-msg\">Никто не зависит</div>";
@@ -996,30 +1012,137 @@ function renderP(obj, pfx){
return r || "<div class=\"empty-msg\">Пусто</div>";
}
function loadInstances(){
var checked = getChecked();
document.getElementById("tbody").innerHTML = "<tr><td colspan=\"7\" class=\"loading\">Загрузка...</td></tr>";
var p = new URLSearchParams();
(checked.length ? checked : ["__none__"]).forEach(function(s){ p.append("status", s); });
fetch(API + "/instances?" + p, { headers: apiHeaders() })
function filteredInstances(){
var checked = Array.from(document.querySelectorAll("#filters input[value]:checked")).map(function(i){ return i.value; });
var auxEl = document.getElementById("chk-auxiliary");
var showAux = auxEl ? auxEl.checked : true;
return allInstances.filter(function(inst){
if(checked.length && checked.indexOf(inst.explainedStatus) < 0) return false;
if(!showAux){
var det = rawDetail[inst.instanceUid];
if(det && det.isAuxiliary) return false;
}
return true;
});
}
function updateTimestamp(){
var ts = document.getElementById("data-ts");
if(ts) ts.textContent = "Актуально на " + new Date().toLocaleTimeString("ru-RU",{hour:"2-digit",minute:"2-digit",second:"2-digit"});
}
function scheduleRender(){
if(renderTimer) clearTimeout(renderTimer);
renderTimer = setTimeout(function(){
renderTimer = null;
renderStats(allInstances);
render(filteredInstances());
}, 150);
}
function loadAllDetails(){
allInstances = [];
cache = {};
rawDetail = {};
graphAllData = null;
graphRunningData = null;
graphFullData = null;
graphAllKey = "";
expandedUid = null;
navBackUid = null;
var nb = document.getElementById("nav-back");
if(nb) nb.style.display = "none";
var ts = document.getElementById("data-ts");
if(ts) ts.textContent = "";
document.getElementById("tbody").innerHTML = "<tr><td colspan=\"7\" class=\"loading\">Загрузка данных...</td></tr>";
fetch(API + "/instances/stream", { headers: apiHeaders() })
.then(function(r){
if(r.status === 401){ doLogout(); return null; }
if(!r.ok) throw new Error("HTTP " + r.status);
return r.json();
return r;
})
.then(function(data){
if(!data) return;
allInstances = data.instances || [];
renderStats(allInstances);
render(allInstances);
var ts = document.getElementById("data-ts");
if(ts) ts.textContent = "Данные на: " + new Date().toLocaleTimeString("ru-RU", {hour:"2-digit",minute:"2-digit",second:"2-digit"});
.then(function(r){
if(!r) return;
var reader = r.body.getReader();
var decoder = new TextDecoder();
var buffer = "";
function processLine(line){
var obj;
try { obj = JSON.parse(line); } catch(e){ return; }
if(obj._done){
updateTimestamp();
renderStats(allInstances);
render(filteredInstances());
return;
}
var uid = obj.instanceUid;
if(!uid) return;
var detail = obj._detail;
if(detail !== undefined) delete obj._detail;
if(detail) rawDetail[uid] = detail;
allInstances.push(obj);
scheduleRender();
}
function pump(){
return reader.read().then(function(result){
if(result.done){
if(buffer.trim()) processLine(buffer.trim());
updateTimestamp();
renderStats(allInstances);
render(filteredInstances());
return;
}
buffer += decoder.decode(result.value, {stream: true});
var lines = buffer.split("\n");
buffer = lines.pop();
lines.forEach(function(line){ if(line.trim()) processLine(line.trim()); });
return pump();
});
}
return pump();
})
.catch(function(e){
document.getElementById("tbody").innerHTML = "<tr><td colspan=\"7\" class=\"err-msg\">" + e.message + "</td></tr>";
document.getElementById("tbody").innerHTML = "<tr><td colspan=\"7\" class=\"err-msg\">" + escHtml(e.message) + "</td></tr>";
});
}
function navigateToDep(uid){
var rows = document.querySelectorAll("tr.data-row[data-uid=\"" + uid + "\"]");
if(!rows.length){
var nb = document.getElementById("nav-back");
if(nb){ nb.innerHTML = "<span>&#9888; Инстанс скрыт текущими фильтрами</span>"; nb.style.display = "block"; }
return;
}
navBackUid = expandedUid;
var nb = document.getElementById("nav-back");
if(nb && navBackUid){
var backName = instanceName(navBackUid);
nb.innerHTML = "<a href=\"#\" onclick=\"navBack();return false;\">← Назад: " + escHtml(backName) + "</a>";
nb.style.display = "block";
}
var row = rows[0];
toggle(uid, row);
row.scrollIntoView({behavior:"smooth", block:"center"});
}
function navBack(){
var uid = navBackUid;
navBackUid = null;
var nb = document.getElementById("nav-back");
if(nb) nb.style.display = "none";
if(!uid) return;
var rows = document.querySelectorAll("tr.data-row[data-uid=\"" + uid + "\"]");
if(!rows.length) return;
var row = rows[0];
toggle(uid, row);
row.scrollIntoView({behavior:"smooth", block:"center"});
}
function loadInstances(){ loadAllDetails(); }
function applyTheme(theme){
var isLight = theme === "light";
document.body.classList.toggle("light", isLight);
@@ -1052,7 +1175,7 @@ function doLogin(){
if(!data) return;
localStorage.setItem("deck_token", token);
localStorage.setItem("deck_env", env);
initApp(data.instances || []);
initApp();
})
.catch(function(e){
showLoginErr("Ошибка соединения: " + e.message);
@@ -1061,7 +1184,7 @@ function doLogin(){
});
}
function initApp(preloaded){
function initApp(){
document.getElementById("login-screen").classList.add("hidden");
document.getElementById("app").style.display = "flex";
var email = decodeEmail(getToken());
@@ -1070,45 +1193,28 @@ function initApp(preloaded){
th.addEventListener("click", function(){
if(sortCol === th.dataset.col) sortDir *= -1;
else { sortCol = th.dataset.col; sortDir = 1; }
render(allInstances);
render(filteredInstances());
});
});
document.querySelectorAll("#filters input").forEach(function(cb){
cb.addEventListener("change", loadInstances);
document.querySelectorAll("#filters input[value]").forEach(function(cb){
cb.addEventListener("change", function(){ cache = {}; render(filteredInstances()); });
});
if(preloaded && preloaded.length > 0){
allInstances = preloaded;
renderStats(allInstances);
render(allInstances);
} else {
loadInstances();
}
document.getElementById("chk-auxiliary").addEventListener("change", function(){
cache = {};
render(filteredInstances());
if(graphRunningData){ graphDrawing = false; drawGraph("running-graph", graphRunningData, ""); }
});
loadAllDetails();
scheduleGraphWarmup();
}
document.getElementById("btn-login").addEventListener("click", doLogin);
document.getElementById("btn-refresh").addEventListener("click", function(){
// Reset all caches so data is fetched fresh
allInstances = [];
cache = {};
graphAllData = null;
graphAllKey = "";
graphRunningData = null;
graphFullData = null;
graphWarmupScheduled = false;
loadInstances();
});
document.getElementById("btn-refresh").addEventListener("click", loadAllDetails);
document.getElementById("btn-running-graph").addEventListener("click", openRunningGraph);
document.getElementById("btn-theme").addEventListener("click", toggleTheme);
document.getElementById("btn-close-graph").addEventListener("click", closeRunningGraph);
document.getElementById("btn-exit").addEventListener("click", doLogout);
document.getElementById("chk-auxiliary").addEventListener("change", function(){
// Redraw current graph with updated auxiliary filter
if(graphRunningData){
graphDrawing = false;
drawGraph("running-graph", graphRunningData, "");
}
});
document.getElementById("graph-modal").addEventListener("click", function(e){
if(e.target && e.target.id === "graph-modal") closeRunningGraph();
});
@@ -1118,7 +1224,7 @@ applyTheme(localStorage.getItem("dashboard_theme") || "dark");
var envSelect = document.getElementById("deck-env");
if(envSelect) envSelect.value = getDeckEnv();
if(getToken()) initApp([]);
if(getToken()) initApp();
})();
</script>