Skip to content

Commit 1bdedb5

Browse files
authored
Merge pull request #2636 from benderl/system-info
initial release of system info page
2 parents e5968dd + 8438b2b commit 1bdedb5

5 files changed

Lines changed: 522 additions & 0 deletions

File tree

web/maintenance/chart.umd.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/maintenance/chart.umd.min.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/maintenance/style.css

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
body {
2+
font-family: Arial, sans-serif;
3+
margin: 30px;
4+
}
5+
h1 {
6+
color: #333;
7+
}
8+
.card-container {
9+
display: flex;
10+
flex-wrap: wrap;
11+
gap: 20px;
12+
margin-bottom: 40px;
13+
}
14+
.card {
15+
background: #fff;
16+
border: 1px solid #ccc;
17+
border-radius: 8px;
18+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
19+
padding: 18px 22px;
20+
min-width: 220px;
21+
margin-bottom: 0;
22+
flex: 0 0 360px;
23+
max-width: 360px; /* wieder auf Standardbreite begrenzt */
24+
box-sizing: border-box;
25+
}
26+
#content .card {
27+
flex: 1 1 400px; /* Karten füllen den verfügbaren Platz */
28+
max-width: 500px; /* Keine maximale Breite */
29+
}
30+
.card h2 {
31+
font-size: 1.1em;
32+
margin-top: 0;
33+
margin-bottom: 12px;
34+
}
35+
.card table {
36+
width: 100%; /* wieder volle Kartenbreite */
37+
max-width: 100%; /* wieder volle Kartenbreite */
38+
}
39+
table {
40+
border-collapse: collapse;
41+
width: 100%;
42+
max-width: 600px;
43+
}
44+
th,
45+
td {
46+
border: 1px solid #ccc;
47+
padding: 8px 12px;
48+
text-align: left;
49+
}
50+
th {
51+
background: #f4f4f4;
52+
}
53+
#error {
54+
color: red;
55+
margin-top: 10px;
56+
}
57+
.chart-row {
58+
display: flex;
59+
align-items: center;
60+
margin-bottom: 30px;
61+
flex-wrap: wrap;
62+
}
63+
.chart-cell {
64+
display: flex;
65+
flex-direction: column;
66+
align-items: center;
67+
margin-right: 30px;
68+
}

web/maintenance/systeminfo.html

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
<!DOCTYPE html>
2+
<html lang="de">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Systeminfo</title>
6+
<link rel="stylesheet" href="style.css" />
7+
</head>
8+
<body>
9+
<h1>Systeminformationen</h1>
10+
<div id="content">
11+
<p>Lade Daten...</p>
12+
</div>
13+
<div id="error"></div>
14+
<script>
15+
const fetchInterval = 5000; // 5 Sekunden
16+
const maxHistoryLength = 60; // z.B. 5 Minuten bei 5s Intervall
17+
18+
// Verlaufsspeicher für alle Daten
19+
const history = {};
20+
21+
// Chart.js laden
22+
const chartJsScript = document.createElement("script");
23+
chartJsScript.src = "chart.umd.min.js";
24+
document.head.appendChild(chartJsScript);
25+
26+
// Warten bis Chart.js geladen ist
27+
function onChartJsReady(cb) {
28+
if (window.Chart) cb();
29+
else chartJsScript.onload = cb;
30+
}
31+
32+
// Diagramm-Container
33+
const charts = {};
34+
35+
// Nur für diese Abschnitte werden Diagramme erstellt:
36+
const chartSections = ["cpuLoad", "memory", "storage"];
37+
38+
function updateHistory(data) {
39+
for (const key in data) {
40+
if (!Object.hasOwn(data, key)) continue;
41+
const value = data[key];
42+
if (typeof value === "object" && value !== null) {
43+
if (!history[key]) history[key] = {};
44+
for (const subKey in value) {
45+
if (!Object.hasOwn(value, subKey)) continue;
46+
if (!history[key][subKey]) history[key][subKey] = [];
47+
history[key][subKey].push({ t: Date.now(), v: value[subKey] });
48+
if (history[key][subKey].length > maxHistoryLength)
49+
history[key][subKey].shift();
50+
}
51+
} else {
52+
if (!history[key]) history[key] = [];
53+
history[key].push({ t: Date.now(), v: value });
54+
if (history[key].length > maxHistoryLength) history[key].shift();
55+
}
56+
}
57+
}
58+
59+
function displayData(data) {
60+
if (typeof data !== "object" || data === null) {
61+
document.getElementById("content").innerHTML =
62+
"<p>Keine gültigen Daten erhalten.</p>";
63+
return;
64+
}
65+
let html = `<h2>Aktuelle Messwerte <span style="color:#888;font-size:0.95em">(${new Date().toLocaleString()})</span></h2>`;
66+
html += '<div class="card-container">';
67+
for (const key in data) {
68+
if (Object.hasOwn(data, key)) {
69+
const value = data[key];
70+
html += `<div class="card"><h2>${key}</h2>`;
71+
if (typeof value === "object" && value !== null) {
72+
html += '<table style="margin:0; width:100%;">';
73+
for (const subKey in value) {
74+
if (Object.hasOwn(value, subKey)) {
75+
const v = value[subKey];
76+
if (
77+
v &&
78+
typeof v === "object" &&
79+
"value" in v &&
80+
"unit" in v
81+
) {
82+
let num = Number(v.value);
83+
let formatted = isFinite(num) ? num.toFixed(1) : v.value;
84+
html += `<tr><th>${subKey}</th><td>${formatted} <span style="color:#888;font-size:0.95em">${v.unit}</span></td></tr>`;
85+
} else {
86+
html += `<tr><th>${subKey}</th><td>${v}</td></tr>`;
87+
}
88+
}
89+
}
90+
html += "</table>";
91+
} else {
92+
html += `<div style="font-size:1.3em;">${value}</div>`;
93+
}
94+
html += "</div>";
95+
}
96+
}
97+
html += "</div>";
98+
document.getElementById("content").innerHTML = html;
99+
}
100+
101+
function renderCharts() {
102+
const chartDivId = "charts";
103+
let chartDiv = document.getElementById(chartDivId);
104+
if (!chartDiv) {
105+
chartDiv = document.createElement("div");
106+
chartDiv.id = chartDivId;
107+
chartDiv.style.marginTop = "40px";
108+
document.body.appendChild(chartDiv);
109+
}
110+
111+
// Vor dem Neuzeichnen alle alten Charts zerstören
112+
for (const key in charts) {
113+
if (charts[key]) {
114+
charts[key].destroy();
115+
}
116+
}
117+
Object.keys(charts).forEach((k) => delete charts[k]);
118+
119+
chartDiv.innerHTML = "<h2>Verlaufsdiagramme</h2>";
120+
121+
let chartsHtml = '<div class="card-container">';
122+
for (const key in history) {
123+
if (!Object.hasOwn(history, key)) continue;
124+
if (!chartSections.includes(key)) continue;
125+
const value = history[key];
126+
// Prüfe, ob es sich um mehrere Reihen handelt (Objekt mit value/unit) oder Array
127+
if (Array.isArray(value)) {
128+
const canvasId = `chart_${key}`;
129+
// Versuche Einheit aus aktuellem API-Datensatz zu holen
130+
let unit = "";
131+
if (
132+
window.latestApiData &&
133+
window.latestApiData[key] &&
134+
window.latestApiData[key].unit
135+
) {
136+
unit = window.latestApiData[key].unit;
137+
}
138+
chartsHtml += `
139+
<div class="card">
140+
<h2>${key}</h2>
141+
<div class="chart-row">
142+
<div class="chart-cell">
143+
<canvas id="${canvasId}" height="120" width="300"></canvas>
144+
</div>
145+
</div>
146+
</div>`;
147+
} else if (typeof value === "object" && value !== null) {
148+
let rowHtml = `<div class="card"><h2>${key}</h2><div class="chart-row">`;
149+
for (const subKey in value) {
150+
if (!Object.hasOwn(value, subKey)) continue;
151+
const canvasId = `chart_${key}_${subKey}`;
152+
// Einheit aus aktuellem API-Datensatz holen
153+
let unit = "";
154+
if (
155+
window.latestApiData &&
156+
window.latestApiData[key] &&
157+
window.latestApiData[key][subKey] &&
158+
typeof window.latestApiData[key][subKey] === "object" &&
159+
"unit" in window.latestApiData[key][subKey]
160+
) {
161+
unit = window.latestApiData[key][subKey].unit;
162+
}
163+
rowHtml += `
164+
<div class="chart-cell">
165+
<div style="margin-bottom:4px"><b>${subKey}</b></div>
166+
<canvas id="${canvasId}" height="120" width="300"></canvas>
167+
</div>`;
168+
}
169+
rowHtml += `</div></div>`;
170+
chartsHtml += rowHtml;
171+
}
172+
}
173+
chartsHtml += "</div>";
174+
chartDiv.innerHTML += chartsHtml;
175+
176+
// Diagramme zeichnen
177+
for (const key in history) {
178+
if (!Object.hasOwn(history, key)) continue;
179+
if (!chartSections.includes(key)) continue;
180+
const value = history[key];
181+
if (Array.isArray(value)) {
182+
const canvas = document.getElementById(`chart_${key}`);
183+
if (!canvas) continue;
184+
const labels = value.map((e) => new Date(e.t).toLocaleTimeString());
185+
const data = value.map((e) =>
186+
typeof e.v === "object" && e.v !== null && "value" in e.v
187+
? Number(e.v.value)
188+
: Number(e.v)
189+
);
190+
// Einheit für Achsenlabel holen
191+
let unit = "";
192+
if (
193+
window.latestApiData &&
194+
window.latestApiData[key] &&
195+
window.latestApiData[key].unit
196+
) {
197+
unit = window.latestApiData[key].unit;
198+
}
199+
if (!charts[key]) {
200+
charts[key] = new Chart(canvas, {
201+
type: "line",
202+
data: {
203+
labels,
204+
datasets: [
205+
{
206+
label: key,
207+
data,
208+
fill: false,
209+
borderColor: "blue",
210+
pointRadius: 1,
211+
tension: 0.2,
212+
},
213+
],
214+
},
215+
options: {
216+
scales: {
217+
x: { display: false }, // Keine unit-Beschriftung für X-Achse
218+
y: {
219+
title: {
220+
display: !!unit,
221+
text: unit,
222+
},
223+
},
224+
},
225+
plugins: { legend: { display: false } },
226+
animation: false,
227+
},
228+
});
229+
} else {
230+
charts[key].data.labels = labels;
231+
charts[key].data.datasets[0].data = data;
232+
charts[key].update("none");
233+
}
234+
} else if (typeof value === "object" && value !== null) {
235+
for (const subKey in value) {
236+
if (!Object.hasOwn(value, subKey)) continue;
237+
const canvas = document.getElementById(`chart_${key}_${subKey}`);
238+
if (!canvas) continue;
239+
const arr = value[subKey];
240+
const labels = arr.map((e) => new Date(e.t).toLocaleTimeString());
241+
const data = arr.map((e) =>
242+
typeof e.v === "object" && e.v !== null && "value" in e.v
243+
? Number(e.v.value)
244+
: Number(e.v)
245+
);
246+
const chartKey = `${key}_${subKey}`;
247+
// Einheit für Achsenlabel holen
248+
let unit = "";
249+
if (
250+
window.latestApiData &&
251+
window.latestApiData[key] &&
252+
window.latestApiData[key][subKey] &&
253+
typeof window.latestApiData[key][subKey] === "object" &&
254+
"unit" in window.latestApiData[key][subKey]
255+
) {
256+
unit = window.latestApiData[key][subKey].unit;
257+
}
258+
if (!charts[chartKey]) {
259+
charts[chartKey] = new Chart(canvas, {
260+
type: "line",
261+
data: {
262+
labels,
263+
datasets: [
264+
{
265+
label: `${key} / ${subKey}`,
266+
data,
267+
fill: false,
268+
borderColor: "green",
269+
pointRadius: 1,
270+
tension: 0.2,
271+
},
272+
],
273+
},
274+
options: {
275+
scales: {
276+
x: { display: true }, // Keine unit-Beschriftung für X-Achse
277+
y: {
278+
title: {
279+
display: !!unit,
280+
text: unit,
281+
},
282+
},
283+
},
284+
plugins: { legend: { display: false } },
285+
animation: false,
286+
},
287+
});
288+
} else {
289+
charts[chartKey].data.labels = labels;
290+
charts[chartKey].data.datasets[0].data = data;
291+
charts[chartKey].update("none");
292+
}
293+
}
294+
}
295+
}
296+
}
297+
298+
// Hook in fetchSystemInfo
299+
const origDisplayData = displayData;
300+
displayData = function (data) {
301+
window.latestApiData = data; // Für Einheitenzugriff in renderCharts
302+
updateHistory(data);
303+
origDisplayData(data);
304+
onChartJsReady(renderCharts);
305+
};
306+
307+
async function fetchSystemInfo() {
308+
try {
309+
const response = await fetch("systeminfo_api.php", {
310+
cache: "no-store",
311+
});
312+
if (!response.ok) throw new Error("Fehler beim Laden der Daten");
313+
const data = await response.json();
314+
displayData(data);
315+
document.getElementById("error").textContent = "";
316+
} catch (err) {
317+
document.getElementById("error").textContent = err.message;
318+
}
319+
}
320+
321+
fetchSystemInfo();
322+
setInterval(fetchSystemInfo, fetchInterval);
323+
</script>
324+
</body>
325+
</html>

0 commit comments

Comments
 (0)