forked from baron/baron-sso
감사 로그 테이블 공통 컬럼 통일
This commit is contained in:
@@ -30,59 +30,20 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "../../components/ui/table";
|
||||
import {
|
||||
formatAuditDateParts,
|
||||
formatAuditValue,
|
||||
parseAuditDetails,
|
||||
resolveAuditAction,
|
||||
resolveAuditActor,
|
||||
resolveAuditTarget,
|
||||
} from "../../../../common/core/audit";
|
||||
import { SearchFilterBar } from "../../../../common/ui/search-filter-bar";
|
||||
import { PageHeader } from "../../../../common/core/components/page";
|
||||
import type { DevAuditLog } from "../../lib/devApi";
|
||||
import { fetchDevAuditLogs } from "../../lib/devApi";
|
||||
import { t } from "../../lib/i18n";
|
||||
|
||||
type AuditDetails = {
|
||||
request_id?: string;
|
||||
method?: string;
|
||||
path?: string;
|
||||
tenant_id?: string;
|
||||
action?: string;
|
||||
target_id?: string;
|
||||
before?: unknown;
|
||||
after?: unknown;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
function parseDetails(details?: string): AuditDetails {
|
||||
if (!details) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(details);
|
||||
if (parsed && typeof parsed === "object") {
|
||||
return parsed as AuditDetails;
|
||||
}
|
||||
} catch {}
|
||||
return {};
|
||||
}
|
||||
|
||||
function formatValue(value: unknown): string {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return "-";
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
function formatDateTime(value: string): string {
|
||||
const parsed = new Date(value);
|
||||
if (Number.isNaN(parsed.getTime())) {
|
||||
return value;
|
||||
}
|
||||
return parsed.toLocaleString("ko-KR");
|
||||
}
|
||||
|
||||
function toCsv(logs: DevAuditLog[]) {
|
||||
const header = [
|
||||
"timestamp",
|
||||
@@ -95,7 +56,7 @@ function toCsv(logs: DevAuditLog[]) {
|
||||
"request_id",
|
||||
];
|
||||
const rows = logs.map((logItem) => {
|
||||
const details = parseDetails(logItem.details);
|
||||
const details = parseAuditDetails(logItem.details);
|
||||
return [
|
||||
logItem.timestamp,
|
||||
logItem.user_id || "",
|
||||
@@ -184,7 +145,7 @@ function AuditLogsPage() {
|
||||
axiosError.response?.data?.error ?? (query.error as Error).message;
|
||||
return (
|
||||
<div className="p-8 text-center text-red-500">
|
||||
{t("msg.dev.audit.load_error", "Error loading logs: {{error}}", {
|
||||
{t("msg.common.audit.load_error", "Error loading logs: {{error}}", {
|
||||
error: errMsg,
|
||||
})}
|
||||
</div>
|
||||
@@ -194,8 +155,8 @@ function AuditLogsPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
eyebrow={t("ui.dev.audit.registry.title", "Audit registry")}
|
||||
title={t("ui.dev.audit.title", "Audit Logs")}
|
||||
eyebrow={t("ui.common.audit.registry.title", "Audit registry")}
|
||||
title={t("ui.common.audit.title", "Audit Logs")}
|
||||
description={t(
|
||||
"msg.dev.audit.subtitle",
|
||||
"Shows DevFront activity history within current tenant/app scope.",
|
||||
@@ -291,19 +252,19 @@ function AuditLogsPage() {
|
||||
<TableHeader className={commonStickyTableHeaderClass}>
|
||||
<TableRow>
|
||||
<TableHead className="w-[190px]">
|
||||
{t("ui.dev.audit.table.time", "Time")}
|
||||
{t("ui.common.audit.table.time", "Time")}
|
||||
</TableHead>
|
||||
<TableHead className="w-[180px]">
|
||||
{t("ui.dev.audit.table.actor", "Actor")}
|
||||
{t("ui.common.audit.table.actor", "Actor")}
|
||||
</TableHead>
|
||||
<TableHead className="w-[180px]">
|
||||
{t("ui.dev.audit.table.action", "Action")}
|
||||
{t("ui.common.audit.table.action", "Action")}
|
||||
</TableHead>
|
||||
<TableHead className="w-[260px]">
|
||||
{t("ui.dev.audit.table.target", "Target")}
|
||||
{t("ui.common.audit.table.target", "Target")}
|
||||
</TableHead>
|
||||
<TableHead className="w-[120px]">
|
||||
{t("ui.dev.audit.table.status", "Status")}
|
||||
{t("ui.common.audit.table.status", "Status")}
|
||||
</TableHead>
|
||||
<TableHead className="w-[80px]" />
|
||||
</TableRow>
|
||||
@@ -315,7 +276,7 @@ function AuditLogsPage() {
|
||||
colSpan={6}
|
||||
className="py-8 text-center text-muted-foreground"
|
||||
>
|
||||
{t("msg.dev.audit.loading", "Loading audit logs...")}
|
||||
{t("msg.common.audit.loading", "Loading audit logs...")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : logs.length === 0 ? (
|
||||
@@ -324,31 +285,42 @@ function AuditLogsPage() {
|
||||
colSpan={6}
|
||||
className="text-center text-muted-foreground"
|
||||
>
|
||||
{t("msg.dev.audit.empty", "No audit logs found.")}
|
||||
{t("msg.common.audit.empty", "No audit logs found.")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
logs.map((row, index) => {
|
||||
const details = parseDetails(row.details);
|
||||
const actionLabel = details.action || row.event_type;
|
||||
const targetValue = details.target_id || "-";
|
||||
const details = parseAuditDetails(row.details);
|
||||
const actionLabel = resolveAuditAction(row, details);
|
||||
const actorLabel = resolveAuditActor(row, details);
|
||||
const targetValue = resolveAuditTarget(details);
|
||||
const rowKey = `${row.event_id}-${row.timestamp}-${index}`;
|
||||
const expanded = Boolean(expandedRows[rowKey]);
|
||||
return (
|
||||
<React.Fragment key={rowKey}>
|
||||
<TableRow>
|
||||
<TableCell className="text-xs text-muted-foreground">
|
||||
{formatDateTime(row.timestamp)}
|
||||
{(() => {
|
||||
const { date, time } = formatAuditDateParts(
|
||||
row.timestamp,
|
||||
);
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div>{date}</div>
|
||||
<div>{time}</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{row.user_id || "-"}</span>
|
||||
{row.user_id ? (
|
||||
<span>{actorLabel}</span>
|
||||
{actorLabel !== "-" ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7 text-muted-foreground"
|
||||
onClick={() => handleCopy(row.user_id)}
|
||||
onClick={() => handleCopy(actorLabel)}
|
||||
>
|
||||
<Copy className="h-3 w-3" />
|
||||
</Button>
|
||||
@@ -411,31 +383,64 @@ function AuditLogsPage() {
|
||||
colSpan={6}
|
||||
className="text-xs text-muted-foreground"
|
||||
>
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<div className="space-y-1">
|
||||
<div>
|
||||
<div className="uppercase tracking-[0.16em]">
|
||||
Request
|
||||
</div>
|
||||
<div className="break-all">
|
||||
Request ID:{" "}
|
||||
{formatValue(details.request_id)}
|
||||
{formatAuditValue(details.request_id)}
|
||||
</div>
|
||||
<div className="break-all">
|
||||
Event ID:{" "}
|
||||
{formatAuditValue(row.event_id)}
|
||||
</div>
|
||||
<div>IP: {formatAuditValue(row.ip_address)}</div>
|
||||
<div className="break-all">
|
||||
Method: {formatAuditValue(details.method)}
|
||||
</div>
|
||||
<div className="break-all">
|
||||
Path: {formatAuditValue(details.path)}
|
||||
</div>
|
||||
<div>
|
||||
Method: {formatValue(details.method)}
|
||||
Latency:{" "}
|
||||
{details.latency_ms !== undefined
|
||||
? `${details.latency_ms}ms`
|
||||
: "-"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="uppercase tracking-[0.16em]">
|
||||
Actor
|
||||
</div>
|
||||
<div>Actor ID: {actorLabel}</div>
|
||||
<div>
|
||||
Tenant:{" "}
|
||||
{formatAuditValue(details.tenant_id)}
|
||||
</div>
|
||||
<div>
|
||||
Path: {formatValue(details.path)}
|
||||
Device:{" "}
|
||||
{formatAuditValue(row.device_id)}
|
||||
</div>
|
||||
<div>
|
||||
Tenant: {formatValue(details.tenant_id)}
|
||||
<div className="break-all">
|
||||
Target: {targetValue}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1 break-all">
|
||||
<div>
|
||||
Before: {formatValue(details.before)}
|
||||
<div className="uppercase tracking-[0.16em]">
|
||||
Result
|
||||
</div>
|
||||
<div>
|
||||
After: {formatValue(details.after)}
|
||||
Error:{" "}
|
||||
{formatAuditValue(details.error)}
|
||||
</div>
|
||||
<div>
|
||||
Error: {formatValue(details.error)}
|
||||
Before:{" "}
|
||||
{formatAuditValue(details.before)}
|
||||
</div>
|
||||
<div>
|
||||
After: {formatAuditValue(details.after)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -461,7 +466,7 @@ function AuditLogsPage() {
|
||||
>
|
||||
{query.isFetchingNextPage
|
||||
? t("msg.common.loading", "Loading...")
|
||||
: t("ui.dev.audit.load_more", "Load more")}
|
||||
: t("ui.common.audit.load_more", "Load more")}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
Reference in New Issue
Block a user