From 926c26b1ad8c730a840d11f02e2fe55f7a671e24 Mon Sep 17 00:00:00 2001 From: chan Date: Thu, 19 Mar 2026 13:15:50 +0900 Subject: [PATCH] feat: apply sticky header and inner scroll to audit logs page --- .../src/features/audit/AuditLogsPage.tsx | 794 +++++++++--------- 1 file changed, 403 insertions(+), 391 deletions(-) diff --git a/adminfront/src/features/audit/AuditLogsPage.tsx b/adminfront/src/features/audit/AuditLogsPage.tsx index a10c5675..0e90b4b1 100644 --- a/adminfront/src/features/audit/AuditLogsPage.tsx +++ b/adminfront/src/features/audit/AuditLogsPage.tsx @@ -158,8 +158,8 @@ function AuditLogsPage() { } return ( -
-
+
+
{t("ui.admin.audit.breadcrumb.section", "Audit")} @@ -194,409 +194,421 @@ function AuditLogsPage() {
-
- - -
- - {t("ui.admin.audit.registry.title", "Audit registry")} - - - {t( - "msg.admin.audit.registry.count", - "로드된 로그 {{count}}건", - { count: logs.length }, + + +
+ + {t("ui.admin.audit.registry.title", "Audit registry")} + + + {t("msg.admin.audit.registry.count", "로드된 로그 {{count}}건", { + count: logs.length, + })} + +
+ + {t("ui.common.badge.command_only", "Command only")} + +
+ +
+
+ + setFilterDraft(event.target.value)} + onKeyDown={(event) => { + if (event.key === "Enter") { + handleAddFilter(); + } + }} + placeholder={t( + "ui.admin.audit.filters.placeholder", + "필터 추가 (예: status:failure)", )} - + className="w-full bg-transparent text-sm text-foreground outline-none" + /> +
- - {t("ui.common.badge.command_only", "Command only")} - - - -
-
- - setFilterDraft(event.target.value)} - onKeyDown={(event) => { - if (event.key === "Enter") { - handleAddFilter(); + {filters.length === 0 ? ( + + {t("msg.admin.audit.filters.empty", "필터 없음")} + + ) : ( + filters.map((filter) => ( + + + {filter} + -
- {filters.length === 0 ? ( - - {t("msg.admin.audit.filters.empty", "필터 없음")} - - ) : ( - filters.map((filter) => ( - - - {filter} - - - )) - )} -
- - - - - {t("ui.admin.audit.table.time", "TIME")} - - - {t("ui.admin.audit.table.actor", "ACTOR (ID)")} - - - {t("ui.admin.audit.table.request", "REQUEST")} - - - {t("ui.admin.audit.table.path", "PATH")} - - - {t("ui.admin.audit.table.status", "STATUS")} - - - {t("ui.admin.audit.table.action_target", "Action / Target")} - - - - - - {isLoading && ( + × + + + )) + )} + +
+
+
+ - - {t("msg.common.loading", "로딩 중...")} - - - )} - {!isLoading && logs.length === 0 && ( - - + + {t("ui.admin.audit.table.time", "TIME")} + + + {t("ui.admin.audit.table.actor", "ACTOR (ID)")} + + + {t("ui.admin.audit.table.request", "REQUEST")} + + + {t("ui.admin.audit.table.path", "PATH")} + + + {t("ui.admin.audit.table.status", "STATUS")} + + {t( - "msg.admin.audit.empty", - "아직 수집된 감사 로그가 없습니다.", + "ui.admin.audit.table.action_target", + "Action / Target", )} - + + - )} - {logs.map((row, index) => { - const details = parseDetails(row.details); - const actionLabel = - details.action || - (details.method && details.path - ? `${details.method} ${details.path}` - : row.event_type); - const rowKey = `${row.event_id}-${row.timestamp}-${index}`; - const isExpanded = Boolean(expandedRows[rowKey]); - return ( - - - - {(() => { - const { date, time } = formatIsoDateTime( - row.timestamp, - ); - return ( -
-
{date}
-
{time}
-
- ); - })()} -
- -
- - {row.user_id || details.actor_id || "-"} - - {(row.user_id || details.actor_id) && ( - - )} -
-
- -
- - {formatCellValue(details.request_id)} - - {details.request_id && ( - - )} -
-
- -
- {formatCellValue(details.method)} -
-
- {formatCellValue(details.path)} -
-
- - - {row.status} - - - -
- {actionLabel} -
- {details.target && ( +
+ + {isLoading && ( + + + {t("msg.common.loading", "로딩 중...")} + + + )} + {!isLoading && logs.length === 0 && ( + + + {t( + "msg.admin.audit.empty", + "아직 수집된 감사 로그가 없습니다.", + )} + + + )} + {logs.map((row, index) => { + const details = parseDetails(row.details); + const actionLabel = + details.action || + (details.method && details.path + ? `${details.method} ${details.path}` + : row.event_type); + const rowKey = `${row.event_id}-${row.timestamp}-${index}`; + const isExpanded = Boolean(expandedRows[rowKey]); + return ( + + + + {(() => { + const { date, time } = formatIsoDateTime( + row.timestamp, + ); + return ( +
+
{date}
+
{time}
+
+ ); + })()} +
+
- - {t( - "ui.admin.audit.target", - "Target · {{target}}", - { - target: details.target, - }, - )} - - -
- )} -
- - - -
- {isExpanded && ( - - -
-
-
- {t( - "ui.admin.audit.details.request", - "Request", + + {row.user_id || details.actor_id || "-"} + + {(row.user_id || details.actor_id) && ( +
-
-
- {t("ui.admin.audit.details.actor", "Actor")} -
-
- {t( - "ui.admin.audit.details.actor_id", - "Actor ID · {{value}}", - { - value: - row.user_id || details.actor_id || "-", - }, - )} -
-
- {t( - "ui.admin.audit.details.tenant", - "Tenant · {{value}}", - { - value: formatCellValue(details.tenant_id), - }, - )} -
-
- {t( - "ui.admin.audit.details.device", - "Device · {{value}}", - { - value: formatCellValue(row.device_id), - }, - )} -
-
-
-
- {t("ui.admin.audit.details.result", "Result")} -
-
- {t( - "ui.admin.audit.details.error", - "Error · {{value}}", - { - value: formatCellValue(details.error), - }, - )} -
-
- {t( - "ui.admin.audit.details.before", - "Before · {{value}}", - { - value: formatCellValue(details.before), - }, - )} -
-
- {t( - "ui.admin.audit.details.after", - "After · {{value}}", - { - value: formatCellValue(details.after), - }, - )} -
-
+ onClick={() => + handleCopy( + row.user_id || details.actor_id || "", + ) + } + > + + + )}
+ +
+ + {formatCellValue(details.request_id)} + + {details.request_id && ( + + )} +
+
+ +
+ {formatCellValue(details.method)} +
+
+ {formatCellValue(details.path)} +
+
+ + + {row.status} + + + +
+ {actionLabel} +
+ {details.target && ( +
+ + {t( + "ui.admin.audit.target", + "Target · {{target}}", + { + target: details.target, + }, + )} + + +
+ )} +
+ + + - )} - - ); - })} - -
-
- {hasNextPage ? ( - - ) : ( - - {t("msg.admin.audit.end", "End of audit feed")} - - )} + {isExpanded && ( + + +
+
+
+ {t( + "ui.admin.audit.details.request", + "Request", + )} +
+
+ {t( + "ui.admin.audit.details.request_id", + "Request ID · {{value}}", + { + value: formatCellValue( + details.request_id, + ), + }, + )} +
+
+ {t( + "ui.admin.audit.details.event_id", + "Event ID · {{value}}", + { + value: formatCellValue(row.event_id), + }, + )} +
+
+ {t( + "ui.admin.audit.details.ip", + "IP · {{value}}", + { + value: formatCellValue(row.ip_address), + }, + )} +
+
+ {t( + "ui.admin.audit.details.latency", + "Latency · {{value}}", + { + value: + details.latency_ms !== undefined + ? `${details.latency_ms}ms` + : "-", + }, + )} +
+
+
+
+ {t("ui.admin.audit.details.actor", "Actor")} +
+
+ {t( + "ui.admin.audit.details.actor_id", + "Actor ID · {{value}}", + { + value: + row.user_id || + details.actor_id || + "-", + }, + )} +
+
+ {t( + "ui.admin.audit.details.tenant", + "Tenant · {{value}}", + { + value: formatCellValue( + details.tenant_id, + ), + }, + )} +
+
+ {t( + "ui.admin.audit.details.device", + "Device · {{value}}", + { + value: formatCellValue(row.device_id), + }, + )} +
+
+
+
+ {t( + "ui.admin.audit.details.result", + "Result", + )} +
+
+ {t( + "ui.admin.audit.details.error", + "Error · {{value}}", + { + value: formatCellValue(details.error), + }, + )} +
+
+ {t( + "ui.admin.audit.details.before", + "Before · {{value}}", + { + value: formatCellValue(details.before), + }, + )} +
+
+ {t( + "ui.admin.audit.details.after", + "After · {{value}}", + { + value: formatCellValue(details.after), + }, + )} +
+
+
+
+
+ )} + + ); + })} + +
-
- -
+
+
+ {hasNextPage ? ( + + ) : ( + + {t("msg.admin.audit.end", "End of audit feed")} + + )} +
+ +
); }