149 lines
4.7 KiB
TypeScript
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;
|
|
}
|