UI/документация: крошки, метрики узлов в stats, правки навигации и подвала

- Документация: хлебные крошки; секции H2 в одной карточке; заголовок вкладки от H1
- Навигация: активна только текущая пилюля (Панель без постоянного home-стиля)
- GET /api/v1/stats: cluster_resources (docker stats CPU/RAM/I/O по узлам kind)
- Панель: блок ресурсов в карточке статистики; убраны строки подвала про api_routes/clusters
- Удалён app/docs/README.md; крошки app/docs → api_routes.md; README корня обновлён
This commit is contained in:
Sergey Antropoff
2026-04-04 06:37:36 +03:00
parent 710b360e4a
commit 6d4bc65c8a
12 changed files with 495 additions and 62 deletions
+74
View File
@@ -248,6 +248,77 @@
}
}
/**
* Блок «Ресурсы узлов» из cluster_resources (CPU/RAM/I/O из docker stats).
*/
function renderStatsResources(s) {
const wrap = document.getElementById("stats-resources");
if (!wrap) return;
wrap.replaceChildren();
if (s.cluster_resources_error) {
const p = document.createElement("p");
p.className = "muted stats-resources-msg";
p.textContent = "Ресурсы узлов: " + s.cluster_resources_error;
wrap.appendChild(p);
return;
}
const list = s.cluster_resources || [];
if (!list.length) {
const p2 = document.createElement("p");
p2.className = "muted stats-resources-msg";
p2.textContent = "Нет кластеров в kind — метрики узлов пусты.";
wrap.appendChild(p2);
return;
}
const h3 = document.createElement("h3");
h3.className = "stats-resources-heading";
h3.textContent = "Ресурсы узлов (CPU, RAM, I/O)";
wrap.appendChild(h3);
list.forEach(function (cr) {
const sec = document.createElement("section");
sec.className = "stats-cluster-res";
const h4 = document.createElement("h4");
h4.className = "stats-cluster-res__title";
h4.textContent = cr.cluster_name;
sec.appendChild(h4);
const nodes = cr.nodes || [];
if (!nodes.length) {
const pn = document.createElement("p");
pn.className = "muted stats-cluster-res__note";
pn.textContent = cr.note || "Нет запущенных контейнеров узлов.";
sec.appendChild(pn);
} else {
nodes.forEach(function (n) {
const row = document.createElement("div");
row.className = "stats-node-res";
const code = document.createElement("code");
code.textContent = n.container_name;
row.appendChild(code);
const ul = document.createElement("ul");
ul.className = "stats-node-res__metrics";
function addMetric(label, val) {
if (val == null || val === "") return;
const li = document.createElement("li");
const strong = document.createElement("strong");
strong.textContent = label + ": ";
li.appendChild(strong);
li.appendChild(document.createTextNode(String(val)));
ul.appendChild(li);
}
addMetric("CPU", n.cpu_percent);
addMetric("Память", n.memory_usage);
addMetric("% RAM", n.memory_percent);
addMetric("Сеть", n.net_io);
addMetric("Диск", n.block_io);
addMetric("PID", n.pids != null ? String(n.pids) : null);
row.appendChild(ul);
sec.appendChild(row);
});
}
wrap.appendChild(sec);
});
}
async function loadStats() {
const dl = document.getElementById("stats-dl");
const errEl = document.getElementById("stats-err");
@@ -272,9 +343,12 @@
frag.appendChild(dd);
});
dl.replaceChildren(frag);
renderStatsResources(s);
} catch (e) {
errEl.textContent = "Статистика: " + e.message;
errEl.classList.remove("hidden");
const wrap = document.getElementById("stats-resources");
if (wrap) wrap.replaceChildren();
}
}