PyObservability 0.1.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyobservability/config/settings.py +31 -2
- pyobservability/main.py +11 -34
- pyobservability/monitor.py +136 -81
- pyobservability/static/app.js +322 -247
- pyobservability/static/styles.css +60 -7
- pyobservability/templates/index.html +16 -10
- pyobservability/transport.py +71 -0
- pyobservability/version.py +1 -1
- {pyobservability-0.1.0.dist-info → pyobservability-1.0.0.dist-info}/METADATA +1 -1
- pyobservability-1.0.0.dist-info/RECORD +16 -0
- pyobservability-0.1.0.dist-info/RECORD +0 -15
- {pyobservability-0.1.0.dist-info → pyobservability-1.0.0.dist-info}/WHEEL +0 -0
- {pyobservability-0.1.0.dist-info → pyobservability-1.0.0.dist-info}/entry_points.txt +0 -0
- {pyobservability-0.1.0.dist-info → pyobservability-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {pyobservability-0.1.0.dist-info → pyobservability-1.0.0.dist-info}/top_level.txt +0 -0
pyobservability/static/app.js
CHANGED
|
@@ -5,6 +5,43 @@
|
|
|
5
5
|
// ------------------------------------------------------------
|
|
6
6
|
const MAX_POINTS = 60;
|
|
7
7
|
const targets = window.MONITOR_TARGETS || [];
|
|
8
|
+
const DEFAULT_PAGE_SIZE = 15;
|
|
9
|
+
const panelSpinners = {};
|
|
10
|
+
|
|
11
|
+
// ------------------------------------------------------------
|
|
12
|
+
// VISUAL SPINNERS
|
|
13
|
+
// ------------------------------------------------------------
|
|
14
|
+
function attachSpinners() {
|
|
15
|
+
// panels (charts/tables)
|
|
16
|
+
document.querySelectorAll(".panel").forEach(box => {
|
|
17
|
+
const overlay = document.createElement("div");
|
|
18
|
+
overlay.className = "loading-overlay";
|
|
19
|
+
overlay.innerHTML = `<div class="spinner"></div>`;
|
|
20
|
+
box.style.position = "relative";
|
|
21
|
+
box.appendChild(overlay);
|
|
22
|
+
panelSpinners[box.id || box.querySelector('table,canvas')?.id || Symbol()] = overlay;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// meta cards (system/static metrics)
|
|
26
|
+
document.querySelectorAll(".meta-card").forEach(card => {
|
|
27
|
+
const overlay = document.createElement("div");
|
|
28
|
+
overlay.className = "loading-overlay";
|
|
29
|
+
overlay.innerHTML = `<div class="spinner"></div>`;
|
|
30
|
+
card.style.position = "relative";
|
|
31
|
+
card.appendChild(overlay);
|
|
32
|
+
panelSpinners[card.querySelector(".meta-value")?.id || Symbol()] = overlay;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function showAllSpinners() {
|
|
37
|
+
Object.keys(panelSpinners).forEach(id => showSpinner(id));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hideSpinners() {
|
|
41
|
+
document.querySelectorAll(".loading-overlay").forEach(x => {
|
|
42
|
+
x.classList.add("hidden");
|
|
43
|
+
});
|
|
44
|
+
}
|
|
8
45
|
|
|
9
46
|
// ------------------------------------------------------------
|
|
10
47
|
// DOM REFERENCES
|
|
@@ -26,10 +63,13 @@
|
|
|
26
63
|
|
|
27
64
|
const coresGrid = document.getElementById("cores-grid");
|
|
28
65
|
|
|
66
|
+
const servicesTable = document.getElementById("services-table");
|
|
29
67
|
const servicesTableBody = document.querySelector("#services-table tbody");
|
|
30
68
|
const svcFilter = document.getElementById("svc-filter");
|
|
69
|
+
const svcGetAll = document.getElementById("get-all-services");
|
|
31
70
|
|
|
32
|
-
const
|
|
71
|
+
const processesTable = document.getElementById("processes-table");
|
|
72
|
+
const processesTableBody = processesTable.querySelector("tbody");
|
|
33
73
|
const procFilter = document.getElementById("proc-filter");
|
|
34
74
|
|
|
35
75
|
const dockerTable = document.getElementById("docker-table");
|
|
@@ -37,19 +77,156 @@
|
|
|
37
77
|
const dockerTableBody = dockerTable.querySelector("tbody");
|
|
38
78
|
|
|
39
79
|
const disksTable = document.getElementById("disks-table");
|
|
40
|
-
const disksTableHead = disksTable.querySelector("thead")
|
|
80
|
+
const disksTableHead = disksTable.querySelector("thead");
|
|
41
81
|
const disksTableBody = disksTable.querySelector("tbody");
|
|
42
82
|
|
|
43
|
-
const pyudiskTable = document.getElementById("pyudisk-table")
|
|
44
|
-
const pyudiskTableHead = pyudiskTable.querySelector("thead")
|
|
45
|
-
const pyudiskTableBody = pyudiskTable.querySelector("tbody")
|
|
83
|
+
const pyudiskTable = document.getElementById("pyudisk-table");
|
|
84
|
+
const pyudiskTableHead = pyudiskTable.querySelector("thead");
|
|
85
|
+
const pyudiskTableBody = pyudiskTable.querySelector("tbody");
|
|
46
86
|
|
|
47
|
-
const certsTable = document.getElementById("certificates-table")
|
|
87
|
+
const certsTable = document.getElementById("certificates-table");
|
|
48
88
|
const certsTableHead = certsTable.querySelector("thead");
|
|
49
89
|
const certsTableBody = certsTable.querySelector("tbody");
|
|
50
90
|
|
|
51
91
|
const showCoresCheckbox = document.getElementById("show-cores");
|
|
52
92
|
|
|
93
|
+
// ------------------------------------------------------------
|
|
94
|
+
// PAGINATION HELPERS
|
|
95
|
+
// ------------------------------------------------------------
|
|
96
|
+
function createPaginatedTable(tableEl, headEl, bodyEl, pageSize = DEFAULT_PAGE_SIZE) {
|
|
97
|
+
const info = document.createElement("div");
|
|
98
|
+
info.className = "pagination-info";
|
|
99
|
+
tableEl.insertAdjacentElement("beforebegin", info);
|
|
100
|
+
|
|
101
|
+
const state = {
|
|
102
|
+
data: [],
|
|
103
|
+
page: 1,
|
|
104
|
+
pageSize
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const pagination = document.createElement("div");
|
|
108
|
+
pagination.className = "pagination";
|
|
109
|
+
tableEl.insertAdjacentElement("afterend", pagination);
|
|
110
|
+
|
|
111
|
+
function render() {
|
|
112
|
+
const rows = state.data;
|
|
113
|
+
const pages = Math.ceil(rows.length / state.pageSize) || 1;
|
|
114
|
+
state.page = Math.max(1, Math.min(state.page, pages));
|
|
115
|
+
|
|
116
|
+
const start = (state.page - 1) * state.pageSize;
|
|
117
|
+
const chunk = rows.slice(start, start + state.pageSize);
|
|
118
|
+
|
|
119
|
+
info.textContent =
|
|
120
|
+
`Showing ${start + 1} to ${Math.min(start + state.pageSize, rows.length)} of ${rows.length} entries`;
|
|
121
|
+
|
|
122
|
+
bodyEl.innerHTML = "";
|
|
123
|
+
chunk.forEach(r => bodyEl.insertAdjacentHTML("beforeend", r));
|
|
124
|
+
|
|
125
|
+
renderPagination(pages);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function renderPagination(pages) {
|
|
129
|
+
pagination.innerHTML = "";
|
|
130
|
+
|
|
131
|
+
const makeBtn = (txt, cb, active = false, disabled = false) => {
|
|
132
|
+
const b = document.createElement("button");
|
|
133
|
+
b.textContent = txt;
|
|
134
|
+
if (active) b.classList.add("active");
|
|
135
|
+
if (disabled) {
|
|
136
|
+
b.disabled = true;
|
|
137
|
+
b.style.opacity = "0.5";
|
|
138
|
+
b.style.cursor = "default";
|
|
139
|
+
}
|
|
140
|
+
b.onclick = disabled ? null : cb;
|
|
141
|
+
pagination.appendChild(b);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// --- Previous ---
|
|
145
|
+
makeBtn("Previous", () => { state.page--; render(); }, false, state.page === 1);
|
|
146
|
+
|
|
147
|
+
const maxVisible = 5;
|
|
148
|
+
|
|
149
|
+
if (pages <= maxVisible + 2) {
|
|
150
|
+
// Show all
|
|
151
|
+
for (let p = 1; p <= pages; p++) {
|
|
152
|
+
makeBtn(p, () => { state.page = p; render(); }, p === state.page);
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
// Big list → use ellipsis
|
|
156
|
+
const showLeft = 3;
|
|
157
|
+
const showRight = 3;
|
|
158
|
+
|
|
159
|
+
if (state.page <= showLeft) {
|
|
160
|
+
// First pages
|
|
161
|
+
for (let p = 1; p <= showLeft + 1; p++) {
|
|
162
|
+
makeBtn(p, () => { state.page = p; render(); }, p === state.page);
|
|
163
|
+
}
|
|
164
|
+
addEllipsis();
|
|
165
|
+
makeBtn(pages, () => { state.page = pages; render(); });
|
|
166
|
+
} else if (state.page >= pages - showRight + 1) {
|
|
167
|
+
// Last pages
|
|
168
|
+
makeBtn(1, () => { state.page = 1; render(); });
|
|
169
|
+
addEllipsis();
|
|
170
|
+
for (let p = pages - showRight; p <= pages; p++) {
|
|
171
|
+
makeBtn(p, () => { state.page = p; render(); }, p === state.page);
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
// Middle
|
|
175
|
+
makeBtn(1, () => { state.page = 1; render(); });
|
|
176
|
+
addEllipsis();
|
|
177
|
+
for (let p = state.page - 1; p <= state.page + 1; p++) {
|
|
178
|
+
makeBtn(p, () => { state.page = p; render(); }, p === state.page);
|
|
179
|
+
}
|
|
180
|
+
addEllipsis();
|
|
181
|
+
makeBtn(pages, () => { state.page = pages; render(); });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// --- Next ---
|
|
186
|
+
makeBtn("Next", () => { state.page++; render(); }, false, state.page === pages);
|
|
187
|
+
|
|
188
|
+
function addEllipsis() {
|
|
189
|
+
const e = document.createElement("span");
|
|
190
|
+
e.textContent = "…";
|
|
191
|
+
e.style.padding = "4px 6px";
|
|
192
|
+
pagination.appendChild(e);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function setData(arr, columns) {
|
|
197
|
+
if (JSON.stringify(state.data) === JSON.stringify(arr)) {
|
|
198
|
+
return; // do not re-render if data didn't change
|
|
199
|
+
}
|
|
200
|
+
headEl.innerHTML = "<tr>" + columns.map(c => `<th>${c}</th>`).join("") + "</tr>";
|
|
201
|
+
state.data = arr.map(row => {
|
|
202
|
+
return "<tr>" + columns.map(c => `<td>${row[c] ?? ""}</td>`).join("") + "</tr>";
|
|
203
|
+
});
|
|
204
|
+
render();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return { setData };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Instances for each table
|
|
211
|
+
const PAG_SERVICES = createPaginatedTable(
|
|
212
|
+
servicesTable, servicesTable.querySelector("thead"), servicesTableBody, 5
|
|
213
|
+
);
|
|
214
|
+
const PAG_PROCESSES = createPaginatedTable(
|
|
215
|
+
processesTable, processesTable.querySelector("thead"), processesTableBody
|
|
216
|
+
);
|
|
217
|
+
const PAG_DOCKER = createPaginatedTable(
|
|
218
|
+
dockerTable, dockerTableHead, dockerTableBody
|
|
219
|
+
);
|
|
220
|
+
const PAG_DISKS = createPaginatedTable(
|
|
221
|
+
disksTable, disksTableHead, disksTableBody
|
|
222
|
+
);
|
|
223
|
+
const PAG_PYUDISK = createPaginatedTable(
|
|
224
|
+
pyudiskTable, pyudiskTableHead, pyudiskTableBody
|
|
225
|
+
);
|
|
226
|
+
const PAG_CERTS = createPaginatedTable(
|
|
227
|
+
certsTable, certsTableHead, certsTableBody
|
|
228
|
+
);
|
|
229
|
+
|
|
53
230
|
// ------------------------------------------------------------
|
|
54
231
|
// CHART HELPERS
|
|
55
232
|
// ------------------------------------------------------------
|
|
@@ -73,20 +250,9 @@
|
|
|
73
250
|
]
|
|
74
251
|
},
|
|
75
252
|
options: {
|
|
76
|
-
animation: false,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
spanGaps: false,
|
|
80
|
-
scales: {
|
|
81
|
-
x: { display: false },
|
|
82
|
-
y: {
|
|
83
|
-
beginAtZero: true,
|
|
84
|
-
suggestedMax: 100
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
plugins: {
|
|
88
|
-
legend: { display: false }
|
|
89
|
-
}
|
|
253
|
+
animation: false, responsive: true, maintainAspectRatio: false,
|
|
254
|
+
scales: { x: { display: false }, y: { beginAtZero: true, suggestedMax: 100 }},
|
|
255
|
+
plugins: { legend: { display: false } }
|
|
90
256
|
}
|
|
91
257
|
});
|
|
92
258
|
}
|
|
@@ -112,7 +278,6 @@
|
|
|
112
278
|
responsive: false,
|
|
113
279
|
interaction: false,
|
|
114
280
|
events: [],
|
|
115
|
-
spanGaps: false,
|
|
116
281
|
plugins: { legend: { display: false } },
|
|
117
282
|
scales: {
|
|
118
283
|
x: { display: false },
|
|
@@ -123,11 +288,11 @@
|
|
|
123
288
|
}
|
|
124
289
|
|
|
125
290
|
const cpuAvgChart = makeMainChart(cpuAvgCtx, "CPU Avg");
|
|
126
|
-
const memChart = makeMainChart(memCtx,
|
|
127
|
-
const loadChart = makeMainChart(loadCtx,
|
|
291
|
+
const memChart = makeMainChart(memCtx, "Memory %");
|
|
292
|
+
const loadChart = makeMainChart(loadCtx, "CPU Load");
|
|
128
293
|
|
|
129
294
|
// ------------------------------------------------------------
|
|
130
|
-
// CORE
|
|
295
|
+
// CORE CHARTS
|
|
131
296
|
// ------------------------------------------------------------
|
|
132
297
|
const coreMini = {};
|
|
133
298
|
|
|
@@ -139,6 +304,7 @@
|
|
|
139
304
|
<canvas width="120" height="40"></canvas>
|
|
140
305
|
<div class="value">—</div>
|
|
141
306
|
`;
|
|
307
|
+
wrapper.style.display = showCoresCheckbox.checked ? "block" : "none";
|
|
142
308
|
coresGrid.appendChild(wrapper);
|
|
143
309
|
|
|
144
310
|
const canvas = wrapper.querySelector("canvas");
|
|
@@ -166,135 +332,106 @@
|
|
|
166
332
|
// ------------------------------------------------------------
|
|
167
333
|
// RESET UI
|
|
168
334
|
// ------------------------------------------------------------
|
|
335
|
+
function resetTables() {
|
|
336
|
+
// Clear all table data immediately
|
|
337
|
+
PAG_SERVICES.setData([], []);
|
|
338
|
+
PAG_PROCESSES.setData([], []);
|
|
339
|
+
PAG_DOCKER.setData([], []);
|
|
340
|
+
PAG_DISKS.setData([], []);
|
|
341
|
+
PAG_PYUDISK.setData([], []);
|
|
342
|
+
PAG_CERTS.setData([], []);
|
|
343
|
+
}
|
|
344
|
+
|
|
169
345
|
function resetUI() {
|
|
170
|
-
|
|
346
|
+
firstMessage = true;
|
|
347
|
+
hideSpinners();
|
|
171
348
|
const EMPTY_DATA = Array(MAX_POINTS).fill(null);
|
|
172
349
|
const EMPTY_LABELS = Array(MAX_POINTS).fill("");
|
|
173
350
|
|
|
174
|
-
|
|
351
|
+
const resetChart = chart => {
|
|
175
352
|
chart.data.labels = [...EMPTY_LABELS];
|
|
176
353
|
chart.data.datasets[0].data = [...EMPTY_DATA];
|
|
177
354
|
chart.update();
|
|
178
|
-
}
|
|
355
|
+
};
|
|
179
356
|
|
|
180
|
-
// Reset main charts (CPU Avg, Memory %, CPU Load)
|
|
181
357
|
resetChart(cpuAvgChart);
|
|
182
358
|
resetChart(memChart);
|
|
183
359
|
resetChart(loadChart);
|
|
184
360
|
|
|
185
|
-
// Remove all per-core mini charts
|
|
186
361
|
for (const name of Object.keys(coreMini)) {
|
|
187
362
|
try { coreMini[name].chart.destroy(); } catch {}
|
|
188
363
|
coreMini[name].el.remove();
|
|
189
364
|
delete coreMini[name];
|
|
190
365
|
}
|
|
191
366
|
|
|
192
|
-
// Reset static UI fields
|
|
193
367
|
systemEl.textContent = "-";
|
|
194
368
|
ipEl.textContent = "—";
|
|
195
369
|
processorEl.textContent = "—";
|
|
196
370
|
memEl.textContent = "—";
|
|
197
371
|
diskEl.textContent = "—";
|
|
198
372
|
loadEl.textContent = "—";
|
|
199
|
-
|
|
200
|
-
servicesTableBody.innerHTML = "";
|
|
201
|
-
processesTableBody.innerHTML = "";
|
|
202
|
-
|
|
203
|
-
dockerTableHead.innerHTML = "";
|
|
204
|
-
dockerTableBody.innerHTML = "";
|
|
205
|
-
|
|
206
|
-
disksTableHead.innerHTML = "";
|
|
207
|
-
disksTableBody.innerHTML = "";
|
|
208
|
-
|
|
209
|
-
pyudiskTableHead.innerHTML = "";
|
|
210
|
-
pyudiskTableBody.innerHTML = "";
|
|
211
|
-
|
|
212
|
-
certsTableHead.innerHTML = "";
|
|
213
|
-
certsTableBody.innerHTML = "";
|
|
214
373
|
}
|
|
215
374
|
|
|
216
375
|
// ------------------------------------------------------------
|
|
217
|
-
// HELPERS
|
|
376
|
+
// MISC HELPERS
|
|
218
377
|
// ------------------------------------------------------------
|
|
219
378
|
function pushPoint(chart, value) {
|
|
220
379
|
const ts = new Date().toLocaleTimeString();
|
|
221
380
|
chart.data.labels.push(ts);
|
|
222
381
|
chart.data.datasets[0].data.push(isFinite(value) ? Number(value) : NaN);
|
|
223
|
-
|
|
224
382
|
if (chart.data.labels.length > MAX_POINTS) {
|
|
225
383
|
chart.data.labels.shift();
|
|
226
384
|
chart.data.datasets[0].data.shift();
|
|
227
385
|
}
|
|
228
|
-
|
|
229
386
|
chart.update("none");
|
|
230
387
|
}
|
|
231
388
|
|
|
232
|
-
function num(x) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function round2(x) {
|
|
238
|
-
const n = Number(x);
|
|
239
|
-
return Number.isFinite(n) ? n.toFixed(2) : "—";
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function formatBytes(x) {
|
|
389
|
+
function num(x) { const n = Number(x); return Number.isFinite(n) ? n : null; }
|
|
390
|
+
const round2 = x => Number(x).toFixed(2);
|
|
391
|
+
const formatBytes = x => {
|
|
243
392
|
if (x == null) return "—";
|
|
244
|
-
const
|
|
245
|
-
let i = 0;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
393
|
+
const u = ["B","KB","MB","GB","TB"];
|
|
394
|
+
let i = 0, n = Number(x);
|
|
395
|
+
while (n > 1024 && i < u.length-1) { n/=1024; i++; }
|
|
396
|
+
return n.toFixed(2) + " " + u[i];
|
|
397
|
+
};
|
|
398
|
+
const objectToString = (...vals) => {
|
|
399
|
+
for (const v of vals) {
|
|
400
|
+
if (v && typeof v === "object")
|
|
401
|
+
return Object.entries(v).map(([a,b])=>`${a}: ${b}`).join("<br>");
|
|
402
|
+
if (v != null) return v;
|
|
250
403
|
}
|
|
251
|
-
return
|
|
252
|
-
}
|
|
404
|
+
return "—";
|
|
405
|
+
};
|
|
253
406
|
|
|
254
|
-
function
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (!Array.isArray(dataList) || dataList.length === 0) {
|
|
259
|
-
tableBody.innerHTML = `<tr><td colspan="10">NO DATA</td></tr>`;
|
|
260
|
-
} else {
|
|
261
|
-
const columns = Object.keys(dataList[0]);
|
|
262
|
-
tableHead.innerHTML =
|
|
263
|
-
"<tr>" + columns.map(c => `<th>${c}</th>`).join("") + "</tr>";
|
|
264
|
-
dataList.forEach(c => {
|
|
265
|
-
const row = "<tr>" +
|
|
266
|
-
columns.map(col => `<td>${c[col] ?? ""}</td>`).join("") +
|
|
267
|
-
"</tr>";
|
|
268
|
-
tableBody.insertAdjacentHTML("beforeend", row);
|
|
269
|
-
});
|
|
270
|
-
}
|
|
407
|
+
function showSpinner(panelOrTableId) {
|
|
408
|
+
const overlay = panelSpinners[panelOrTableId];
|
|
409
|
+
if (overlay) overlay.classList.remove("hidden");
|
|
271
410
|
}
|
|
272
411
|
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if (typeof v === "object") {
|
|
277
|
-
return Object.entries(v)
|
|
278
|
-
.map(([k, val]) => `${k}: ${val}`)
|
|
279
|
-
.join("<br>");
|
|
280
|
-
}
|
|
281
|
-
return String(v);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return "—";
|
|
412
|
+
function hideSpinner(panelOrTableId) {
|
|
413
|
+
const overlay = panelSpinners[panelOrTableId];
|
|
414
|
+
if (overlay) overlay.classList.add("hidden");
|
|
285
415
|
}
|
|
286
416
|
|
|
287
417
|
// ------------------------------------------------------------
|
|
288
|
-
// METRICS
|
|
418
|
+
// HANDLE METRICS
|
|
289
419
|
// ------------------------------------------------------------
|
|
420
|
+
let firstMessage = true;
|
|
421
|
+
|
|
290
422
|
function handleMetrics(list) {
|
|
423
|
+
if (firstMessage) {
|
|
424
|
+
hideSpinners();
|
|
425
|
+
firstMessage = false;
|
|
426
|
+
}
|
|
427
|
+
|
|
291
428
|
const now = new Date().toLocaleTimeString();
|
|
292
429
|
|
|
293
430
|
for (const host of list) {
|
|
294
431
|
if (host.base_url !== selectedBase) continue;
|
|
295
432
|
const m = host.metrics || {};
|
|
296
433
|
|
|
297
|
-
//
|
|
434
|
+
// ------------ Static fields ------------
|
|
298
435
|
systemEl.textContent =
|
|
299
436
|
`Node: ${m.node || "-"}\n` +
|
|
300
437
|
`OS: ${m.system || "-"}\n` +
|
|
@@ -302,191 +439,118 @@
|
|
|
302
439
|
`CPU Cores: ${m.cores || "-"}\n` +
|
|
303
440
|
`Up Time: ${m.uptime || "-"}\n`;
|
|
304
441
|
|
|
305
|
-
|
|
306
|
-
if (m.ip_info) {
|
|
442
|
+
if (m.ip_info)
|
|
307
443
|
ipEl.textContent =
|
|
308
444
|
`Private: ${m.ip_info.private || "-"}\n\n` +
|
|
309
445
|
`Public: ${m.ip_info.public || "-"}`;
|
|
310
|
-
} else {
|
|
311
|
-
ipEl.textContent = "-";
|
|
312
|
-
}
|
|
313
446
|
|
|
314
|
-
// ------------------- CPU / GPU -------------------
|
|
315
447
|
processorEl.textContent =
|
|
316
448
|
`CPU: ${m.cpu_name || "-"}\n\n` +
|
|
317
449
|
`GPU: ${m.gpu_name || "-"}`;
|
|
318
450
|
|
|
319
|
-
|
|
320
|
-
if (Array.isArray(m.disk_info) && m.disk_info.length > 0) {
|
|
451
|
+
if (m.disk_info && m.disk_info[0]) {
|
|
321
452
|
const d = m.disk_info[0];
|
|
322
453
|
diskEl.textContent =
|
|
323
454
|
`Total: ${formatBytes(d.total)}\n` +
|
|
324
455
|
`Used: ${formatBytes(d.used)}\n` +
|
|
325
456
|
`Free: ${formatBytes(d.free)}`;
|
|
326
|
-
} else if (m.disk) {
|
|
327
|
-
diskEl.textContent =
|
|
328
|
-
`Total: ${m.disk.total}\nUsed: ${m.disk.used}\nFree: ${m.disk.free}`;
|
|
329
|
-
} else {
|
|
330
|
-
diskEl.textContent = "NO DATA";
|
|
331
457
|
}
|
|
332
458
|
|
|
333
|
-
// ------------------- MEMORY -------------------
|
|
334
459
|
if (m.memory_info) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
memEl.textContent = `Total: ${total}\nUsed: ${used}\nPercent: ${percent}%`;
|
|
460
|
+
memEl.textContent =
|
|
461
|
+
`Total: ${formatBytes(m.memory_info.total)}\n` +
|
|
462
|
+
`Used: ${formatBytes(m.memory_info.used)}\n` +
|
|
463
|
+
`Percent: ${round2(m.memory_info.percent)}%`;
|
|
340
464
|
pushPoint(memChart, num(m.memory_info.percent));
|
|
341
|
-
|
|
342
|
-
} else if (m.memory) {
|
|
343
|
-
// fallback to old
|
|
344
|
-
const used = m.memory.ram_used || "";
|
|
345
|
-
const percent = m.memory.ram_usage ?? m.memory.percent ?? "—";
|
|
346
|
-
const totalMem = m.memory.ram_total ?? m.memory.total ?? "—";
|
|
347
|
-
memEl.textContent = `Total: ${totalMem}\nUsed: ${used}\nPercent: ${percent}%`;
|
|
348
|
-
pushPoint(memChart, num(percent));
|
|
349
|
-
} else {
|
|
350
|
-
memEl.textContent = "NO DATA";
|
|
351
465
|
}
|
|
352
466
|
|
|
353
|
-
//
|
|
467
|
+
// ------------ CPU ------------
|
|
354
468
|
let avg = null;
|
|
355
|
-
|
|
356
|
-
if (Array.isArray(m.cpu_usage)) {
|
|
469
|
+
if (m.cpu_usage) {
|
|
357
470
|
const values = m.cpu_usage.map(num);
|
|
358
|
-
avg = values.reduce((a,
|
|
359
|
-
|
|
360
|
-
pruneOldCores(values.map((_, i) => "cpu" + (i + 1)));
|
|
361
|
-
|
|
362
|
-
values.forEach((v, i) => {
|
|
363
|
-
const coreName = "cpu" + (i + 1);
|
|
364
|
-
const c = getCoreChart(coreName);
|
|
471
|
+
avg = values.reduce((a,b)=>a+(b||0),0) / values.length;
|
|
472
|
+
pruneOldCores(values.map((_,i)=>"cpu"+(i+1)));
|
|
365
473
|
|
|
366
|
-
|
|
367
|
-
|
|
474
|
+
values.forEach((v,i)=>{
|
|
475
|
+
const core = getCoreChart("cpu"+(i+1));
|
|
368
476
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
477
|
+
core.chart.data.labels.push(now);
|
|
478
|
+
core.chart.data.datasets[0].data.push(v||0);
|
|
479
|
+
if (core.chart.data.labels.length > MAX_POINTS) {
|
|
480
|
+
core.chart.data.labels.shift();
|
|
481
|
+
core.chart.data.datasets[0].data.shift();
|
|
372
482
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
c.valEl.textContent = `${(v ?? 0).toFixed(1)}%`;
|
|
483
|
+
core.chart.update({ lazy: true });
|
|
484
|
+
core.valEl.textContent = `${(v||0).toFixed(1)}%`;
|
|
376
485
|
});
|
|
377
|
-
|
|
378
|
-
} else if (m.cpu) {
|
|
379
|
-
// fallback to old
|
|
380
|
-
const detail = m.cpu.detail || m.cpu;
|
|
381
|
-
if (typeof detail === "object") {
|
|
382
|
-
const names = Object.keys(detail);
|
|
383
|
-
pruneOldCores(names);
|
|
384
|
-
|
|
385
|
-
const values = [];
|
|
386
|
-
for (const [core, val] of Object.entries(detail)) {
|
|
387
|
-
const v = num(val);
|
|
388
|
-
values.push(v);
|
|
389
|
-
|
|
390
|
-
const c = getCoreChart(core);
|
|
391
|
-
c.chart.data.labels.push(now);
|
|
392
|
-
c.chart.data.datasets[0].data.push(v ?? 0);
|
|
393
|
-
|
|
394
|
-
if (c.chart.data.labels.length > MAX_POINTS) {
|
|
395
|
-
c.chart.data.labels.shift();
|
|
396
|
-
c.chart.data.datasets[0].data.shift();
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
c.chart.update("none");
|
|
400
|
-
c.valEl.textContent = `${(v ?? 0).toFixed(1)}%`;
|
|
401
|
-
}
|
|
402
|
-
avg = values.reduce((a, b) => a + (b ?? 0), 0) / values.length;
|
|
403
|
-
}
|
|
404
486
|
}
|
|
405
|
-
|
|
406
487
|
if (avg != null) pushPoint(cpuAvgChart, avg);
|
|
407
488
|
|
|
408
|
-
//
|
|
489
|
+
// ------------ LOAD ------------
|
|
409
490
|
if (m.load_averages) {
|
|
410
491
|
const la = m.load_averages;
|
|
411
492
|
loadEl.textContent =
|
|
412
493
|
`${round2(la.m1)} / ${round2(la.m5)} / ${round2(la.m15)}`;
|
|
413
494
|
pushPoint(loadChart, num(la.m1));
|
|
414
|
-
} else if (m.cpu_load) {
|
|
415
|
-
const load = m.cpu_load.detail || m.cpu_load;
|
|
416
|
-
const m1 = load.m1 ?? load[0];
|
|
417
|
-
const m5 = load.m5 ?? load[1];
|
|
418
|
-
const m15 = load.m15 ?? load[2];
|
|
419
|
-
loadEl.textContent = `${round2(m1)} / ${round2(m5)} / ${round2(m15)}`;
|
|
420
|
-
pushPoint(loadChart, num(m1));
|
|
421
|
-
} else {
|
|
422
|
-
loadEl.textContent = "NO DATA";
|
|
423
495
|
}
|
|
424
496
|
|
|
425
|
-
//
|
|
426
|
-
const services = m.service_stats || m.services || []
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
`;
|
|
445
|
-
servicesTableBody.appendChild(tr);
|
|
446
|
-
}
|
|
497
|
+
// ------------ SERVICES (paginated) ------------
|
|
498
|
+
const services = (m.service_stats || m.services || []).filter(s =>
|
|
499
|
+
(s.pname || s.Name || "").toLowerCase().includes(
|
|
500
|
+
svcFilter.value.trim().toLowerCase()
|
|
501
|
+
)
|
|
502
|
+
);
|
|
503
|
+
if (services.length) {
|
|
504
|
+
const columns = ["PID","Name","Status","CPU","Memory","Threads","Open Files"];
|
|
505
|
+
const cleaned = services.map(s => ({
|
|
506
|
+
PID: s.PID ?? s.pid ?? "",
|
|
507
|
+
Name: s.pname ?? s.Name ?? s.name ?? "",
|
|
508
|
+
Status: s.Status ?? s.active ?? s.status ?? s.Active ?? "—",
|
|
509
|
+
CPU: objectToString(s.CPU, s.cpu),
|
|
510
|
+
Memory: objectToString(s.Memory, s.memory),
|
|
511
|
+
Threads: s.Threads ?? s.threads ?? "—",
|
|
512
|
+
"Open Files": s["Open Files"] ?? s.open_files ?? "—"
|
|
513
|
+
}));
|
|
514
|
+
PAG_SERVICES.setData(cleaned, columns);
|
|
515
|
+
hideSpinner("services-table");
|
|
447
516
|
}
|
|
448
517
|
|
|
449
|
-
//
|
|
450
|
-
const processes = m.process_stats || []
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const tr = document.createElement("tr");
|
|
460
|
-
tr.innerHTML = `
|
|
461
|
-
<td>${p.PID ?? ""}</td>
|
|
462
|
-
<td>${name}</td>
|
|
463
|
-
<td>${p.Status ?? p.status ?? "—"}</td>
|
|
464
|
-
<td>${p.CPU ?? p.cpu ?? "—"}</td>
|
|
465
|
-
<td>${p.Memory ?? p.memory ?? "—"}</td>
|
|
466
|
-
<td>${p.Uptime ?? p.uptime ?? "—"}</td>
|
|
467
|
-
<td>${p.Threads ?? p.threads ?? "—"}</td>
|
|
468
|
-
<td>${p["Open Files"] ?? p.open_files ?? "—"}</td>
|
|
469
|
-
`;
|
|
470
|
-
processesTableBody.appendChild(tr);
|
|
471
|
-
}
|
|
518
|
+
// ------------ PROCESSES (paginated) ------------
|
|
519
|
+
const processes = (m.process_stats || []).filter(p =>
|
|
520
|
+
(p.Name || "").toLowerCase().includes(
|
|
521
|
+
procFilter.value.trim().toLowerCase()
|
|
522
|
+
)
|
|
523
|
+
);
|
|
524
|
+
if (processes.length) {
|
|
525
|
+
const columns = ["PID","Name","Status","CPU","Memory","Uptime","Threads","Open Files"];
|
|
526
|
+
PAG_PROCESSES.setData(processes, columns);
|
|
527
|
+
hideSpinner("processes-table");
|
|
472
528
|
}
|
|
473
529
|
|
|
474
|
-
//
|
|
475
|
-
|
|
476
|
-
|
|
530
|
+
// ------------ DOCKER, DISKS, PYUDISK, CERTIFICATES ------------
|
|
531
|
+
if (m.docker_stats) {
|
|
532
|
+
const cols = Object.keys(m.docker_stats[0] || {});
|
|
533
|
+
PAG_DOCKER.setData(m.docker_stats, cols);
|
|
534
|
+
hideSpinner("docker-table");
|
|
535
|
+
}
|
|
477
536
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
537
|
+
if (m.disks_info) {
|
|
538
|
+
const cols = Object.keys(m.disks_info[0] || {});
|
|
539
|
+
PAG_DISKS.setData(m.disks_info, cols);
|
|
540
|
+
hideSpinner("disks-table");
|
|
541
|
+
}
|
|
482
542
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
543
|
+
if (m.pyudisk_stats) {
|
|
544
|
+
const cols = Object.keys(m.pyudisk_stats[0] || {});
|
|
545
|
+
PAG_PYUDISK.setData(m.pyudisk_stats, cols);
|
|
546
|
+
hideSpinner("pyudisk-table");
|
|
547
|
+
}
|
|
486
548
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
549
|
+
if (m.certificates) {
|
|
550
|
+
const cols = Object.keys(m.certificates[0] || {});
|
|
551
|
+
PAG_CERTS.setData(m.certificates, cols);
|
|
552
|
+
hideSpinner("certificates-table");
|
|
553
|
+
}
|
|
490
554
|
}
|
|
491
555
|
}
|
|
492
556
|
|
|
@@ -500,22 +564,26 @@
|
|
|
500
564
|
nodeSelect.appendChild(opt);
|
|
501
565
|
});
|
|
502
566
|
|
|
503
|
-
let selectedBase =
|
|
504
|
-
nodeSelect.value || (targets[0] && targets[0].base_url);
|
|
567
|
+
let selectedBase = nodeSelect.value || (targets[0] && targets[0].base_url);
|
|
505
568
|
nodeSelect.value = selectedBase;
|
|
506
569
|
|
|
507
570
|
nodeSelect.addEventListener("change", () => {
|
|
508
571
|
selectedBase = nodeSelect.value;
|
|
509
572
|
resetUI();
|
|
573
|
+
resetTables();
|
|
574
|
+
showAllSpinners();
|
|
575
|
+
ws.send(JSON.stringify({ type: "select_target", base_url: selectedBase }));
|
|
510
576
|
});
|
|
511
577
|
|
|
578
|
+
svcGetAll.addEventListener("change", () => {
|
|
579
|
+
ws.send(JSON.stringify({ type: "update_flags", all_services: svcGetAll.checked }));
|
|
580
|
+
})
|
|
581
|
+
|
|
512
582
|
refreshBtn.addEventListener("click", resetUI);
|
|
513
583
|
|
|
514
584
|
showCoresCheckbox.addEventListener("change", () => {
|
|
515
585
|
const visible = showCoresCheckbox.checked;
|
|
516
|
-
Object.values(coreMini).forEach(c =>
|
|
517
|
-
c.el.style.display = visible ? "block" : "none";
|
|
518
|
-
});
|
|
586
|
+
Object.values(coreMini).forEach(c => c.el.style.display = visible ? "block" : "none");
|
|
519
587
|
});
|
|
520
588
|
|
|
521
589
|
// ------------------------------------------------------------
|
|
@@ -524,10 +592,15 @@
|
|
|
524
592
|
const protocol = location.protocol === "https:" ? "wss" : "ws";
|
|
525
593
|
const ws = new WebSocket(`${protocol}://${location.host}/ws`);
|
|
526
594
|
|
|
595
|
+
ws.onopen = () => {
|
|
596
|
+
ws.send(JSON.stringify({ type: "select_target", base_url: selectedBase }));
|
|
597
|
+
};
|
|
598
|
+
|
|
527
599
|
ws.onmessage = evt => {
|
|
528
600
|
try {
|
|
529
601
|
const msg = JSON.parse(evt.data);
|
|
530
602
|
if (msg.type === "metrics") handleMetrics(msg.data);
|
|
603
|
+
if (msg.type === "error") alert(msg.message);
|
|
531
604
|
} catch (err) {
|
|
532
605
|
console.error("WS parse error:", err);
|
|
533
606
|
}
|
|
@@ -536,5 +609,7 @@
|
|
|
536
609
|
// ------------------------------------------------------------
|
|
537
610
|
// INIT
|
|
538
611
|
// ------------------------------------------------------------
|
|
539
|
-
|
|
612
|
+
attachSpinners();
|
|
613
|
+
resetUI(); // reset UI, keep spinners visible
|
|
614
|
+
showAllSpinners(); // show spinners until first metrics arrive
|
|
540
615
|
})();
|