v9.44: remove all graph code (backend endpoint, frontend SVG/panzoom/CSS, ~536 lines dead code)

This commit is contained in:
Naeel
2026-04-17 08:56:53 +03:00
parent b778f5554c
commit f6d15eb7e8
3 changed files with 3 additions and 536 deletions
+2 -360
View File
@@ -36,12 +36,8 @@
.filter-label input { cursor: pointer; width: 18px; height: 18px; accent-color: #58a6ff; }
.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; }
.btn-graph:hover { background: #2563eb; }
.btn-theme { padding: 5px 10px; background: #21262d; border: 1px solid #30363d; border-radius: 6px; color: #e1e4e8; cursor: pointer; font-size: 14px; line-height: 1; }
.btn-theme:hover { background: #30363d; }
.btn-graph { padding: 5px 12px; background: #21262d; border: 1px solid #30363d; border-radius: 6px; color: #e1e4e8; cursor: pointer; font-size: 12px; }
.btn-graph:hover { background: #30363d; }
.header-user { display: flex; align-items: center; gap: 10px; }
.user-email { font-size: 12px; color: #8b949e; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.btn-exit { padding: 5px 12px; background: #21262d; border: 1px solid #30363d; border-radius: 6px; color: #f85149; cursor: pointer; font-size: 12px; }
@@ -100,20 +96,6 @@
body.light .panel-history { background: #dcfce7; border-top: 2px solid #4ade80; }
body.light .panel-deps { background: #fef3c7; border-top: 2px solid #f59e0b; }
.panel.active { display: block; }
.inst-graph { height: 420px; border: 1px solid #30363d; border-radius: 8px; background: #0b0f14; }
.graph-hint { color: #8b949e; font-size: 12px; margin-bottom: 8px; }
.graph-loading { display: flex; align-items: center; justify-content: center; height: 100%; color: #9fb3c8; font-size: 13px; }
.graph-loading::before { content: ""; width: 14px; height: 14px; border: 2px solid #3b82f6; border-top-color: transparent; border-radius: 50%; display: inline-block; margin-right: 8px; animation: spin .9s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.graph-modal { position: fixed; inset: 0; background: rgba(2, 8, 20, .75); display: none; align-items: center; justify-content: center; z-index: 1200; }
.graph-modal.open { display: flex; }
.graph-modal-box { width: min(98vw, 1700px); height: min(95vh, 1100px); background: #0d1117; border: 1px solid #30363d; border-radius: 12px; display: flex; flex-direction: column; }
.graph-modal-head { padding: 10px 14px; border-bottom: 1px solid #30363d; display: flex; align-items: center; gap: 10px; }
.graph-modal-title { font-size: 14px; font-weight: 600; }
.graph-modal-close { margin-left: auto; padding: 4px 10px; background: #21262d; border: 1px solid #30363d; color: #e1e4e8; border-radius: 6px; cursor: pointer; }
.graph-modal-body { flex: 1; min-height: 0; }
.running-graph { width: 100%; height: 100%; }
.graph-inline-note { color: #8b949e; font-size: 12px; margin-top: 8px; }
.params-grid { display: grid; grid-template-columns: max-content max-content; gap: 14px 68px; max-width: none; margin: 0; justify-content: start; align-content: start; }
.params-section { min-width: 0; }
.params-section h4 { font-size: 11px; font-weight: 600; color: #8b949e; text-transform: uppercase; margin-bottom: 8px; }
@@ -169,8 +151,8 @@
body.light .header-logo .logo-text { color: #0f172a; }
body.light .header-logo .logo-sub, body.light .user-email { color: #475569; }
body.light .data-ts { color: #64748b; }
body.light .btn-refresh, body.light .btn-theme, body.light .btn-exit, body.light .btn-graph { background: #f8fafc; border-color: #cbd5e1; color: #0f172a; }
body.light .btn-refresh:hover, body.light .btn-theme:hover, body.light .btn-exit:hover, body.light .btn-graph:hover { background: #e2e8f0; }
body.light .btn-refresh, body.light .btn-theme, body.light .btn-exit { background: #f8fafc; border-color: #cbd5e1; color: #0f172a; }
body.light .btn-refresh:hover, body.light .btn-theme:hover, body.light .btn-exit:hover { background: #e2e8f0; }
body.light .main { background: #f3f6fb; }
body.light .stat-card { background: #ffffff; border-color: #cbd5e1; }
body.light .stat-card .lbl { color: #64748b; }
@@ -199,10 +181,6 @@
body.light .tabs { border-bottom-color: #b6c6e8; background: #dfe9fa; }
body.light .tab { color: #475569; }
body.light .tab.active { color: #1d4ed8; border-bottom-color: #1d4ed8; }
body.light .graph-modal { background: rgba(15, 23, 42, .35); }
body.light .graph-modal-box { background: #ffffff; border-color: #cbd5e1; }
body.light .graph-modal-head { border-bottom-color: #cbd5e1; }
body.light .graph-modal-close { background: #f8fafc; border-color: #cbd5e1; color: #0f172a; }
body.light .p-item { border-bottom-color: #e2e8f0; color: #0f172a; }
body.light .p-item:nth-child(odd) { background: rgba(148, 163, 184, .12); }
body.light .p-item:nth-child(even) { background: rgba(148, 163, 184, .18); }
@@ -304,7 +282,6 @@
</div>
</div>
<div id="js-err" style="display:none;position:fixed;bottom:0;left:0;right:0;background:#f85149;color:#fff;padding:10px;font-family:monospace;font-size:12px;z-index:9999"></div>
<script src="https://cdn.jsdelivr.net/npm/panzoom@9.4.3/dist/panzoom.min.js"></script>
<script>
window.onerror=function(m,s,l){var e=document.getElementById("js-err");if(e){e.style.display="block";e.textContent="JS ERROR line "+l+": "+m;}};
</script>
@@ -318,11 +295,6 @@ var sortCol = "displayName";
var sortDir = 1;
var expandedUid = null;
var cache = {};
var graphAllData = null;
var graphRunningData = null;
var graphFullData = null;
var graphAllKey = "";
var graphFetchInFlight = false;
var rawDetail = {};
var navBackUid = null;
var renderTimer = null;
@@ -435,332 +407,6 @@ function visibleUidSet(){
}
// ─── GRAPH: SVG + panzoom ─────────────────────────────────────────────────
// Status color for node border
function nodeStatusColor(status){
var m = {
"running": "#238636", "suspended": "#e3b341", "deleting": "#f85149",
"deleted": "#6e7681", "not created": "#8b949e", "pending": "#a371f7",
"creating": "#1f6feb"
};
return m[status] || "#8b949e";
}
// Service → background color
function nodeBgColor(svc){
var s = (svc || "").toLowerCase();
if(/postgres|sql/.test(s)) return "#0c1e36";
if(/kubernetes|k8s|шт[уy]рвал/.test(s)) return "#0f1e0f";
if(/s3|object storage|бакет/.test(s)) return "#1a1200";
if(/edge|шлюз/.test(s)) return "#200c1a";
if(/vm|виртуальн/.test(s)) return "#1a1014";
if(/redis/.test(s)) return "#200a0a";
if(/rabbit/.test(s)) return "#0a1a10";
if(/dns/.test(s)) return "#0a1220";
if(/cloud director|vdc|датацентр/.test(s)) return "#001430";
return "#161b22";
}
// Break text into lines fitting maxWidth (chars-based estimate)
function wrapText(text, maxChars){
var words = text.split(/(?=[_\-])/); // split on _ and - keeping delimiter
var lines = [];
var cur = "";
for(var i = 0; i < words.length; i++){
var w = words[i];
if((cur + w).length > maxChars && cur.length > 0){
lines.push(cur);
cur = w;
} else {
cur += w;
}
}
if(cur) lines.push(cur);
return lines.length ? lines : [text];
}
// Fetch graph data from backend
function fetchGraph(params){
if(graphFetchInFlight) return Promise.resolve(null);
graphFetchInFlight = true;
var p = new URLSearchParams();
Object.keys(params || {}).forEach(function(k){
var v = params[k];
if(Array.isArray(v)) v.forEach(function(x){ p.append(k, x); });
else if(v !== undefined && v !== null && v !== "") p.append(k, String(v));
});
return fetch(API + "/graph?" + p.toString(), { headers: apiHeaders() })
.then(function(r){
graphFetchInFlight = false;
if(r.status === 401){ doLogout(); return null; }
if(!r.ok) throw new Error("HTTP " + r.status);
return r.json();
})
.catch(function(e){
graphFetchInFlight = false;
throw e;
});
}
function ensureAllGraphLoaded(){
var statuses = getChecked().slice().sort();
var key = statuses.join(",");
if(key === graphAllKey && graphAllData) return Promise.resolve(graphAllData);
if(statuses.length === 0){
graphAllKey = key;
graphAllData = {nodes:[], edges:[]};
return Promise.resolve(graphAllData);
}
return fetchGraph({ status: statuses }).then(function(data){
graphAllKey = key;
graphAllData = data || {nodes:[], edges:[]};
return graphAllData;
});
}
function ensureFullGraphLoaded(){
if(graphFullData) return Promise.resolve(graphFullData);
return fetchGraph({}).then(function(data){
graphFullData = data || {nodes:[], edges:[]};
return graphFullData;
});
}
// Build subgraph from rawDetail dependencies (BFS, no edges needed)
function buildDepSubgraphFromDetail(rootUid){
var seen = new Set([rootUid]);
var frontier = new Set([rootUid]);
for(var i = 0; i < 3; i++){
var nxt = new Set();
frontier.forEach(function(u){
var det = rawDetail[u];
if(!det) return;
(det.dependencies || []).forEach(function(dep){
var duid = depUid(dep);
if(duid && !seen.has(duid)) nxt.add(duid);
});
(det.dependentInstances || []).forEach(function(dep){
var duid = depUid(dep);
if(duid && !seen.has(duid)) nxt.add(duid);
});
});
if(nxt.size === 0) break;
nxt.forEach(function(x){ seen.add(x); });
frontier = nxt;
}
var allNodes = (graphFullData && graphFullData.nodes) || [];
var filtered = allNodes.filter(function(n){ return seen.has(n.id); });
if(!filtered.some(function(n){ return n.id === rootUid; })){
for(var j = 0; j < allInstances.length; j++){
if(allInstances[j].instanceUid === rootUid){
var inst = allInstances[j];
filtered.push({
id: rootUid, label: inst.displayName || rootUid,
status: inst.explainedStatus || "unknown", svc: inst.svc || "",
lane: "regular", created: inst.instanceConfigDtCreated || inst.dtCreated || ""
});
break;
}
}
}
return { nodes: filtered, edges: [] };
}
// ─── MAIN DRAW FUNCTION ────────────────────────────────────────────────────
function drawGraph(containerId, data, focusUid){
var el = document.getElementById(containerId);
if(!el) return;
el.innerHTML = "";
var nodes = data.nodes || [];
if(nodes.length === 0){
el.innerHTML = '<div class="empty-msg" style="padding:20px">Нет данных</div>';
return;
}
var NODE_W = 200;
var NODE_H = 80;
var GAP_X = 30;
var GAP_Y = 50;
var MAX_COLS = 5;
var PAD = 40;
// Sort: platforms first, singletons second, regular last
var sorted = nodes.slice().sort(function(a, b){
var rank = function(n){ return n.lane === "platform" ? 0 : n.lane === "singleton" ? 1 : 2; };
return rank(a) - rank(b);
});
// Assign grid positions
var cols = Math.min(sorted.length, MAX_COLS);
sorted.forEach(function(n, idx){
n._col = idx % cols;
n._row = Math.floor(idx / cols);
});
var rows = Math.ceil(sorted.length / cols);
var svgW = PAD * 2 + cols * NODE_W + (cols - 1) * GAP_X;
var svgH = PAD * 2 + rows * NODE_H + (rows - 1) * GAP_Y;
// ── Create container with overflow scroll ──
var wrapper = document.createElement("div");
wrapper.style.cssText = "width:100%;height:100%;overflow:hidden;position:relative;background:#0d1117;";
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", svgW);
svg.setAttribute("height", svgH);
svg.setAttribute("viewBox", "0 0 " + svgW + " " + svgH);
svg.style.cssText = "display:block;cursor:grab;user-select:none;";
var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
sorted.forEach(function(n){
var x = PAD + n._col * (NODE_W + GAP_X);
var y = PAD + n._row * (NODE_H + GAP_Y);
var cx = x + NODE_W / 2;
var cy = y + NODE_H / 2;
var bg = nodeBgColor(n.svc || "");
var bc = nodeStatusColor(n.status || "");
var isFocus = focusUid && n.id === focusUid;
// Rect
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", x);
rect.setAttribute("y", y);
rect.setAttribute("width", NODE_W);
rect.setAttribute("height", NODE_H);
rect.setAttribute("rx", 8);
rect.setAttribute("fill", bg);
rect.setAttribute("stroke", isFocus ? "#facc15" : bc);
rect.setAttribute("stroke-width", isFocus ? 3 : 2);
rect.style.cursor = "pointer";
rect.addEventListener("click", function(e){
e.stopPropagation();
navigateToDep(n.id);
});
g.appendChild(rect);
// Name — wrapped
var nameLines = wrapText(n.label || n.id || "", 20);
var nameFs = nameLines.length > 2 ? 10 : nameLines.length > 1 ? 11 : 12;
var lineH = nameFs * 1.5;
var totalTextH = nameLines.length * lineH + (n.svc ? lineH : 0);
var startY = cy - totalTextH / 2 + nameFs;
nameLines.forEach(function(line, li){
var t = document.createElementNS("http://www.w3.org/2000/svg", "text");
t.setAttribute("x", cx);
t.setAttribute("y", startY + li * lineH);
t.setAttribute("text-anchor", "middle");
t.setAttribute("font-size", nameFs);
t.setAttribute("font-weight", "bold");
t.setAttribute("fill", "#dbeafe");
t.setAttribute("font-family", "system-ui,sans-serif");
t.style.pointerEvents = "none";
t.textContent = line;
g.appendChild(t);
});
// Service
if(n.svc){
var st = document.createElementNS("http://www.w3.org/2000/svg", "text");
st.setAttribute("x", cx);
st.setAttribute("y", startY + nameLines.length * lineH);
st.setAttribute("text-anchor", "middle");
st.setAttribute("font-size", 9);
st.setAttribute("font-style", "italic");
st.setAttribute("fill", "#93c5fd");
st.setAttribute("font-family", "system-ui,sans-serif");
st.style.pointerEvents = "none";
st.textContent = n.svc;
g.appendChild(st);
}
// Age
var age = humanAge(n.created || "");
if(age){
var at = document.createElementNS("http://www.w3.org/2000/svg", "text");
at.setAttribute("x", cx);
at.setAttribute("y", y + NODE_H - 6);
at.setAttribute("text-anchor", "middle");
at.setAttribute("font-size", 9);
at.setAttribute("fill", "#9ca3af");
at.setAttribute("font-family", "system-ui,sans-serif");
at.style.pointerEvents = "none";
at.textContent = "\u23f1 " + age;
g.appendChild(at);
}
});
svg.appendChild(g);
wrapper.appendChild(svg);
el.appendChild(wrapper);
// ── panzoom: pan + scroll-zoom ──
if(window.panzoom){
var instance = window.panzoom(g, {
smoothScroll: false,
bounds: false,
zoomDoubleClickSpeed: 1,
minZoom: 0.2,
maxZoom: 3
});
svg.addEventListener("dblclick", function(){
instance.moveTo(0, 0);
instance.zoomAbs(0, 0, 1);
});
}
}
// ── Open / Close running graph modal ──────────────────────────────────────
function openRunningGraph(){
var modal = document.getElementById("graph-modal");
modal.classList.add("open");
var titleEl = document.getElementById("graph-modal-title");
var statuses = getChecked();
if(titleEl) titleEl.textContent = "Общий граф зависимостей: " + (statuses.length ? statuses.join(", ") : "все");
var target = document.getElementById("running-graph");
if(target) target.innerHTML = '<div class="graph-loading">Собираю граф...</div>';
ensureAllGraphLoaded()
.then(function(data){ drawGraph("running-graph", data || {nodes:[], edges:[]}, ""); })
.catch(function(e){
var el2 = document.getElementById("running-graph");
if(el2) el2.innerHTML = '<div class="err-msg">' + escHtml(e.message) + '</div>';
});
}
function closeRunningGraph(){
document.getElementById("graph-modal").classList.remove("open");
}
function showGraphModal(title, data, focusUid){
var modal = document.getElementById("graph-modal");
var titleEl = document.getElementById("graph-modal-title");
if(titleEl) titleEl.textContent = title;
modal.classList.add("open");
drawGraph("running-graph", data || {nodes:[], edges:[]}, focusUid || "");
}
// ── Instance graph (вкладка Граф в детали) ─────────────────────────────────
function renderInstanceGraph(uid){
var holderId = "g-" + uid;
var holder = document.getElementById(holderId);
if(holder) holder.innerHTML = '<div class="graph-loading">Строю граф...</div>';
ensureFullGraphLoaded().then(function(){
var g = buildDepSubgraphFromDetail(uid);
if((g.nodes || []).length > 2){
showGraphModal("Граф зависимостей: " + escHtml(instanceName(uid)), g, uid);
} else {
drawGraph(holderId, g, uid);
}
}).catch(function(e){
var el = document.getElementById(holderId);
if(el) el.innerHTML = '<div class="err-msg">' + escHtml(e.message) + '</div>';
});
}
function getToken(){
return localStorage.getItem("deck_token") || "";
}
@@ -1236,10 +882,6 @@ function scheduleRender(){
allInstances = [];
cache = {};
rawDetail = {};
graphAllData = null;
graphRunningData = null;
graphFullData = null;
graphAllKey = "";
expandedUid = null;
navBackUid = null;
var nb = document.getElementById("nav-back");