Files
Aislo/B01_Dashboard/B01_Dashboard_UI_Resources.ts
T

149 lines
4.7 KiB
TypeScript

import { ui_locales, currentLanguageIndex } from "@ui/ui_template_locale";
import { createLineChart } from "@ui/ui_template_elements";
import type { ResourceData } from "./B01_Dashboard_Api_Fetch";
function L(key: keyof typeof ui_locales): string {
return ui_locales[key][currentLanguageIndex];
}
function percent(value?: number | null): string {
return value == null ? "-" : `${Math.round(value)}%`;
}
export function buildResourcePanel(resources: ResourceData | null): HTMLElement {
const wrap = document.createElement("div");
wrap.className = "b01-dashboard__resource-panel";
wrap.append(buildResourceMetrics(resources), buildResourceChart(resources));
return wrap;
}
function buildResourceMetrics(resources: ResourceData | null): HTMLElement {
const values = resources?.current;
const grid = document.createElement("div");
grid.className = "b01-dashboard__metric-grid";
const items = [
[L("B01_Dashboard_Metric_Cpu"), percent(values?.cpu_usage_percent)],
[L("B01_Dashboard_Metric_Memory"), percent(values?.memory_usage_percent)],
[L("B01_Dashboard_Metric_Disk"), percent(values?.disk_usage_percent)],
[L("B01_Dashboard_Metric_ActiveUsers"), values?.active_user_count ?? 0],
[L("B01_Dashboard_Metric_ActiveProjects"), values?.active_project_count ?? 0],
];
for (const [label, value] of items) {
const box = document.createElement("div");
box.className = "b01-dashboard__metric";
const labelEl = document.createElement("span");
labelEl.className = "b01-dashboard__metric-label";
labelEl.textContent = String(label);
const valueEl = document.createElement("span");
valueEl.className = "b01-dashboard__metric-value";
valueEl.textContent = String(value);
box.append(labelEl, valueEl);
grid.append(box);
}
return grid;
}
function buildResourceChart(resources: ResourceData | null): HTMLElement {
const box = document.createElement("div");
box.className = "b01-dashboard__chart";
const caption = document.createElement("p");
caption.className = "b01-dashboard__chart-caption";
caption.textContent = L("B01_Dashboard_Resources_ChartCaption");
box.append(caption);
const chartWrap = document.createElement("div");
chartWrap.className = "b01-dashboard__chart-content";
box.append(chartWrap);
const history = resources?.history ?? [];
const points: { timestamp: Date; cpu: number | null; mem: number | null; disk: number | null }[] =
[];
const now = new Date();
const baseDate = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate(),
now.getHours() >= 12 ? 24 : 12,
0,
0,
0,
);
for (let i = 0; i <= 42; i++) {
const time = new Date(baseDate.getTime() - (42 - i) * 4 * 60 * 60 * 1000);
points.push({ timestamp: time, cpu: null, mem: null, disk: null });
}
history.forEach((h) => {
if (!h.timestamp) return;
const tsStr = h.timestamp.endsWith("Z") ? h.timestamp : h.timestamp + "Z";
const hTime = new Date(tsStr);
let closestIdx = -1;
let minDiff = Infinity;
points.forEach((p, idx) => {
const diff = Math.abs(p.timestamp.getTime() - hTime.getTime());
if (diff < minDiff && diff <= 2 * 60 * 60 * 1000) {
minDiff = diff;
closestIdx = idx;
}
});
if (closestIdx !== -1) {
const p = points[closestIdx];
p.cpu = h.cpu_usage_percent;
p.mem = h.memory_usage_percent;
p.disk = h.disk_usage_percent;
}
});
const xLabels = points.map((p) => {
const hours = p.timestamp.getHours();
return hours === 0 ? `${p.timestamp.getMonth() + 1}/${p.timestamp.getDate()}` : "";
});
let resizeTimeout: number | null = null;
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const width = Math.floor(entry.contentRect.width);
if (width <= 0) continue;
if (resizeTimeout) {
window.cancelAnimationFrame(resizeTimeout);
}
resizeTimeout = window.requestAnimationFrame(() => {
chartWrap.innerHTML = "";
const chart = createLineChart({
ariaLabel: L("B01_Dashboard_Resources_ChartAria"),
xLabels,
width: width,
height: 140,
series: [
{
name: L("B01_Dashboard_Metric_Cpu"),
colorIndex: 0,
values: points.map((p) => p.cpu),
},
{
name: L("B01_Dashboard_Metric_Memory"),
colorIndex: 1,
values: points.map((p) => p.mem),
},
{
name: L("B01_Dashboard_Metric_Disk"),
colorIndex: 2,
values: points.map((p) => p.disk),
},
],
});
chartWrap.append(chart);
});
}
});
observer.observe(box);
return box;
}