fix: onclick navigateToDep uid interpolation; clear cache on status filter change
This commit is contained in:
+181
-75
@@ -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">↻ Обновить</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 ? " · через <code>" + d.param + "</code>" : "") +
|
||||
(d.descr ? " · " + d.descr : "") + "</div></div>";
|
||||
var did = depUid(d);
|
||||
depsHtml += "<div class=\"dep-item\" onclick=\"navigateToDep('" + did + "')\"><div class=\"dep-name\">► " + escHtml(d.displayName||d.uid) +
|
||||
"</div><div class=\"dep-meta\">" + escHtml(d.svc||"") +
|
||||
(d.param ? " · через <code>" + escHtml(d.param) + "</code>" : "") +
|
||||
(d.descr ? " · " + 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 ? " · через <code>" + d.param + "</code>" : "") + "</div></div>";
|
||||
var did = depUid(d);
|
||||
depOnHtml += "<div class=\"dep-item\" onclick=\"navigateToDep('" + did + "')\"><div class=\"dep-name\">► " + escHtml(d.displayName||d.uid) +
|
||||
"</div><div class=\"dep-meta\">" + escHtml(d.svc||"") +
|
||||
(d.param ? " · через <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>⚠ Инстанс скрыт текущими фильтрами</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>
|
||||
|
||||
Reference in New Issue
Block a user