This commit is contained in:
2026-07-08 19:32:16 +09:00
parent 9c40dec070
commit 8ffa9b097a
14 changed files with 238 additions and 50 deletions
+82 -12
View File
@@ -18,6 +18,7 @@ import {
createCompany,
fetchAllCompanies,
fetchAllJoinRequests,
fetchAllProjects,
fetchAllUsers,
fetchAuditLogs,
fetchCompanyJoinRequests,
@@ -52,6 +53,7 @@ interface DashboardState {
user: DashboardUser;
userProjects: ProjectItem[];
companyProjects: ProjectItem[];
allProjects: ProjectItem[];
company: CompanyInfo | null;
members: Member[];
joinRequests: JoinRequest[];
@@ -70,6 +72,7 @@ export async function renderB01Dashboard(root: HTMLElement): Promise<void> {
user,
userProjects: [],
companyProjects: [],
allProjects: [],
company: null,
members: [],
joinRequests: [],
@@ -90,18 +93,20 @@ export async function renderB01Dashboard(root: HTMLElement): Promise<void> {
async function loadRoleData(state: DashboardState): Promise<void> {
if (state.user.role === "SYSTEM_ADMIN") {
const [companies, users, requests, logs, resources] = await Promise.all([
const [companies, users, requests, logs, resources, projects] = await Promise.all([
fetchAllCompanies(),
fetchAllUsers(),
fetchAllJoinRequests(),
fetchAuditLogs(),
fetchSystemResources(),
fetchSystemResources(7),
fetchAllProjects(),
]);
state.allCompanies = companies;
state.allUsers = users;
state.allJoinRequests = requests;
state.auditLogs = logs;
state.resources = resources;
state.allProjects = projects;
return;
}
const [projects, company] = await Promise.all([fetchUserProjects(), fetchUserCompany()]);
@@ -130,8 +135,9 @@ function buildPage(state: DashboardState): HTMLElement {
if (state.user.role === "SYSTEM_ADMIN") {
grid.append(
section(L("B01_Dashboard_Resources"), buildResourcePanel(state.resources), true),
section(L("B01_Dashboard_Companies"), companyTable(state.allCompanies)),
section(L("B01_Dashboard_Users"), userTable(state.allUsers)),
section(L("B01_Dashboard_Companies"), companyTable(state.allCompanies), true),
section(L("B01_Dashboard_Users"), userTable(state.allUsers), true),
section(L("B01_Dashboard_Projects"), projectTable(state.allProjects), true),
section(L("B01_Dashboard_JoinRequests"), joinRequestTable(state.allJoinRequests, true)),
section(L("B01_Dashboard_AuditLogs"), auditLogTable(state.auditLogs), true),
);
@@ -415,32 +421,96 @@ function buildResourceMetrics(resources: ResourceData | null): HTMLElement {
function formatChartTime(iso: string | null | undefined): string {
if (!iso) return "";
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return "";
const pad = (n: number) => String(n).padStart(2, "0");
return `${pad(d.getMonth() + 1)}/${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
try {
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return "";
const pad = (n: number) => String(n).padStart(2, "0");
return `${pad(d.getMonth() + 1)}/${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
} catch {
return "";
}
}
function buildResourceChart(resources: ResourceData | null): HTMLElement {
const history = resources?.history ?? [];
// 7일간의 고정 타임라인 생성 (4시간 간격, 총 43개 포인트)
const points: { timestamp: Date; cpu: number | null; mem: number | null; disk: number | null }[] = [];
const now = new Date();
// 12시간 단위(정오 또는 자정)로 올림 정렬하여 기준점 계산
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++) {
// 7일 전부터 현재 정렬 시점까지 4시간 단위 버킷 생성
const time = new Date(baseDate.getTime() - (42 - i) * 4 * 60 * 60 * 1000);
points.push({ timestamp: time, cpu: null, mem: null, disk: null });
}
// DB에서 불러온 계측 이력 매핑
history.forEach((h) => {
if (!h.timestamp) return;
// DB에서 넘어오는 ISO 문자열을 UTC 기준으로 정확히 매핑하기 위해 Z를 보완하여 파싱
const tsStr = h.timestamp.endsWith("Z") ? h.timestamp : h.timestamp + "Z";
const hTime = new Date(tsStr);
// 가장 가까운 4시간 단위 버킷 매핑 (±2시간 내)
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;
}
});
// xLabels 생성 (Midnight: MM/DD, 그 외는 빈칸)
const xLabels = points.map((p) => {
const hours = p.timestamp.getHours();
if (hours === 0) {
return `${p.timestamp.getMonth() + 1}/${p.timestamp.getDate()}`;
}
return "";
});
const chart = createLineChart({
ariaLabel: L("B01_Dashboard_Resources_ChartAria"),
xLabels: history.map((p) => formatChartTime(p.timestamp)),
xLabels,
width: 800,
height: 140,
series: [
{
name: L("B01_Dashboard_Metric_Cpu"),
colorIndex: 0,
values: history.map((p) => p.cpu_usage_percent),
values: points.map((p) => p.cpu),
},
{
name: L("B01_Dashboard_Metric_Memory"),
colorIndex: 1,
values: history.map((p) => p.memory_usage_percent),
values: points.map((p) => p.mem),
},
{
name: L("B01_Dashboard_Metric_Disk"),
colorIndex: 2,
values: history.map((p) => p.disk_usage_percent),
values: points.map((p) => p.disk),
},
],
});