260707_1_대시보드정리 1차

This commit is contained in:
2026-07-08 19:04:20 +09:00
parent 3485eeb096
commit 9c40dec070
26 changed files with 1264 additions and 305 deletions
+118 -2
View File
@@ -6,6 +6,7 @@ import {
createButton,
createCard,
createInputField,
createLineChart,
createTag,
hideLoadingOverlay,
showLoadingOverlay,
@@ -13,6 +14,7 @@ import {
} from "@ui/ui_template_elements";
import {
addCompanyMember,
changePassword,
createCompany,
fetchAllCompanies,
fetchAllJoinRequests,
@@ -40,6 +42,8 @@ import {
} from "./B01_Dashboard_Api_Fetch";
import "./B01_Dashboard_UI_Style.css";
const PASSWORD_MIN_LENGTH = 8;
function L(key: keyof typeof ui_locales): string {
return ui_locales[key][currentLanguageIndex];
}
@@ -125,7 +129,7 @@ function buildPage(state: DashboardState): HTMLElement {
if (state.user.role === "SYSTEM_ADMIN") {
grid.append(
section(L("B01_Dashboard_Resources"), buildResourceMetrics(state.resources), true),
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_JoinRequests"), joinRequestTable(state.allJoinRequests, true)),
@@ -159,7 +163,10 @@ function buildPage(state: DashboardState): HTMLElement {
);
}
}
grid.append(section(L("B01_Dashboard_Profile"), buildProfileForm(state.user), true));
grid.append(
section(L("B01_Dashboard_Profile"), buildProfileForm(state.user)),
section(L("B01_Account_Section_Security"), buildSecurityForm()),
);
page.append(grid);
return page;
}
@@ -373,6 +380,13 @@ function actionPair(approve: () => void, reject: () => void): HTMLElement {
return row;
}
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");
@@ -399,6 +413,46 @@ function buildResourceMetrics(resources: ResourceData | null): HTMLElement {
return grid;
}
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())}`;
}
function buildResourceChart(resources: ResourceData | null): HTMLElement {
const history = resources?.history ?? [];
const chart = createLineChart({
ariaLabel: L("B01_Dashboard_Resources_ChartAria"),
xLabels: history.map((p) => formatChartTime(p.timestamp)),
series: [
{
name: L("B01_Dashboard_Metric_Cpu"),
colorIndex: 0,
values: history.map((p) => p.cpu_usage_percent),
},
{
name: L("B01_Dashboard_Metric_Memory"),
colorIndex: 1,
values: history.map((p) => p.memory_usage_percent),
},
{
name: L("B01_Dashboard_Metric_Disk"),
colorIndex: 2,
values: history.map((p) => p.disk_usage_percent),
},
],
});
const caption = document.createElement("p");
caption.className = "b01-dashboard__chart-caption";
caption.textContent = L("B01_Dashboard_Resources_ChartCaption");
const box = document.createElement("div");
box.className = "b01-dashboard__chart";
box.append(caption, chart);
return box;
}
function companyTable(companies: CompanyInfo[]): HTMLElement {
return table(
[
@@ -478,6 +532,68 @@ function buildProfileForm(user: DashboardUser): HTMLElement {
return wrap;
}
function buildSecurityForm(): HTMLElement {
const currentPassword = createInputField({
label: L("B01_Account_Field_CurrentPw"),
type: "password",
required: true,
});
const newPassword = createInputField({
label: L("B01_Account_Field_NewPw"),
type: "password",
required: true,
});
const confirmPassword = createInputField({
label: L("B01_Account_Field_ConfirmPw"),
type: "password",
required: true,
});
const grid = document.createElement("div");
grid.className = "b01-dashboard__form-grid";
grid.append(currentPassword.root, newPassword.root, confirmPassword.root);
const save = createButton({
label: L("B01_Account_Save_Password"),
variant: "ghost",
onClick: async function onB01_Password_Save_Click() {
currentPassword.setError();
newPassword.setError();
confirmPassword.setError();
const currentValue = currentPassword.input.value;
const nextValue = newPassword.input.value;
const confirmValue = confirmPassword.input.value;
if (isBlank(currentValue) || isBlank(nextValue) || isBlank(confirmValue)) {
currentPassword.setError(isBlank(currentValue) ? L("Common_Msg_RequiredField") : undefined);
newPassword.setError(isBlank(nextValue) ? L("Common_Msg_RequiredField") : undefined);
confirmPassword.setError(isBlank(confirmValue) ? L("Common_Msg_RequiredField") : undefined);
return;
}
if (nextValue.length < PASSWORD_MIN_LENGTH) {
newPassword.setError(L("B01_Account_Error_PwLength"));
return;
}
if (nextValue !== confirmValue) {
confirmPassword.setError(L("B01_Account_Error_PwMismatch"));
return;
}
await runRequest(() =>
changePassword({
current_password: currentValue,
new_password: nextValue,
new_password_confirm: confirmValue,
logout_all: false,
}),
);
currentPassword.input.value = "";
newPassword.input.value = "";
confirmPassword.input.value = "";
},
});
const wrap = document.createElement("div");
wrap.append(grid, save);
return wrap;
}
function openCreateCompanyModal(): void {
const name = createInputField({ label: L("B01_Dashboard_Table_Company"), required: true });
const number = createInputField({