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; }