forked from baron/baron-sso
204 lines
5.8 KiB
TypeScript
204 lines
5.8 KiB
TypeScript
import {
|
|
formatAuditValue,
|
|
parseAuditDetails,
|
|
resolveAuditActor,
|
|
type AuditDetails,
|
|
type CommonAuditLog,
|
|
} from "../../../../common/core/audit";
|
|
import { t } from "../../lib/i18n";
|
|
import type { ClientSummary, DevAuditLog } from "../../lib/devApi";
|
|
|
|
export type RecentClientChange = {
|
|
eventId: string;
|
|
clientId: string;
|
|
clientName: string;
|
|
actorId: string;
|
|
action: string;
|
|
actionLabel: string;
|
|
timestamp: string;
|
|
detailLabels: Array<{ label: string; value: string }>;
|
|
};
|
|
|
|
const recentClientActions = new Set([
|
|
"CREATE_CLIENT",
|
|
"UPDATE_CLIENT",
|
|
"UPDATE_CLIENT_STATUS",
|
|
"ROTATE_SECRET",
|
|
"ADD_RELATION",
|
|
"REMOVE_RELATION",
|
|
"DELETE_CLIENT",
|
|
]);
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
}
|
|
|
|
export function getRecentClientActionLabel(action: string) {
|
|
switch (action) {
|
|
case "CREATE_CLIENT":
|
|
return t("ui.dev.clients.recent_changes.guide.create", "앱 생성");
|
|
case "UPDATE_CLIENT":
|
|
return t("ui.dev.clients.recent_changes.guide.settings", "설정 변경");
|
|
case "UPDATE_CLIENT_STATUS":
|
|
return t("ui.dev.clients.recent_changes.guide.status", "상태 변경");
|
|
case "ROTATE_SECRET":
|
|
return t(
|
|
"ui.dev.clients.recent_changes.guide.secret",
|
|
"클라이언트 시크릿 재발급",
|
|
);
|
|
case "ADD_RELATION":
|
|
return t("ui.dev.clients.relationships.add_title", "관계 추가");
|
|
case "REMOVE_RELATION":
|
|
return t("ui.common.remove", "Remove");
|
|
case "DELETE_CLIENT":
|
|
return t("ui.dev.clients.recent_changes.guide.delete", "앱 삭제");
|
|
default:
|
|
return action;
|
|
}
|
|
}
|
|
|
|
function getRecentClientFieldLabel(key: string) {
|
|
switch (key) {
|
|
case "name":
|
|
return t("ui.dev.clients.table.application", "Application");
|
|
case "type":
|
|
return t("ui.dev.clients.table.type", "Type");
|
|
case "status":
|
|
return t("ui.dev.clients.table.status", "Status");
|
|
case "relation":
|
|
return t("ui.dev.clients.relationships.relation", "관계");
|
|
case "subject":
|
|
return t("ui.dev.clients.relationships.subject", "대상");
|
|
case "client_secret":
|
|
return t(
|
|
"ui.dev.clients.details.credentials.client_secret",
|
|
"클라이언트 시크릿",
|
|
);
|
|
default:
|
|
return key;
|
|
}
|
|
}
|
|
|
|
export function buildRecentClientChangeDetails(
|
|
action: string,
|
|
details: AuditDetails,
|
|
) {
|
|
const before = isRecord(details.before) ? details.before : {};
|
|
const after = isRecord(details.after) ? details.after : {};
|
|
|
|
if (action === "ROTATE_SECRET") {
|
|
return [
|
|
{
|
|
label: getRecentClientFieldLabel("client_secret"),
|
|
value: t("msg.dev.clients.details.secret_rotated", "재발급"),
|
|
},
|
|
];
|
|
}
|
|
|
|
if (action === "ADD_RELATION" || action === "REMOVE_RELATION") {
|
|
const source = action === "ADD_RELATION" ? after : before;
|
|
return [
|
|
...(source.relation
|
|
? [
|
|
{
|
|
label: getRecentClientFieldLabel("relation"),
|
|
value: formatAuditValue(source.relation),
|
|
},
|
|
]
|
|
: []),
|
|
...(source.subject
|
|
? [
|
|
{
|
|
label: getRecentClientFieldLabel("subject"),
|
|
value: formatAuditValue(source.subject),
|
|
},
|
|
]
|
|
: []),
|
|
];
|
|
}
|
|
|
|
const keys = Array.from(
|
|
new Set([...Object.keys(before), ...Object.keys(after)]),
|
|
);
|
|
|
|
const changes = keys
|
|
.map((key) => {
|
|
const beforeValue = before[key];
|
|
const afterValue = after[key];
|
|
|
|
if (action !== "CREATE_CLIENT" && action !== "DELETE_CLIENT") {
|
|
if (JSON.stringify(beforeValue) === JSON.stringify(afterValue)) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const label = getRecentClientFieldLabel(key);
|
|
if (action === "CREATE_CLIENT") {
|
|
if (afterValue === undefined) {
|
|
return null;
|
|
}
|
|
return { label, value: formatAuditValue(afterValue) };
|
|
}
|
|
if (action === "DELETE_CLIENT") {
|
|
if (beforeValue === undefined) {
|
|
return null;
|
|
}
|
|
return { label, value: formatAuditValue(beforeValue) };
|
|
}
|
|
if (beforeValue === undefined && afterValue === undefined) {
|
|
return null;
|
|
}
|
|
if (beforeValue === undefined) {
|
|
return { label, value: formatAuditValue(afterValue) };
|
|
}
|
|
if (afterValue === undefined) {
|
|
return { label, value: formatAuditValue(beforeValue) };
|
|
}
|
|
return {
|
|
label,
|
|
value: `${formatAuditValue(beforeValue)} → ${formatAuditValue(afterValue)}`,
|
|
};
|
|
})
|
|
.filter((item): item is { label: string; value: string } => Boolean(item));
|
|
|
|
return changes.slice(0, 3);
|
|
}
|
|
|
|
export function buildRecentClientChanges(
|
|
auditLogs: DevAuditLog[],
|
|
clients: ClientSummary[],
|
|
) {
|
|
const clientNameById = new Map(
|
|
clients.map((client) => [client.id, client.name || client.id]),
|
|
);
|
|
|
|
return auditLogs
|
|
.map((item) => {
|
|
const details = parseAuditDetails(item.details);
|
|
const action = details.action || "";
|
|
const clientId = String(details.target_id || "");
|
|
if (!recentClientActions.has(action) || !clientId) {
|
|
return null;
|
|
}
|
|
return {
|
|
eventId: item.event_id,
|
|
clientId,
|
|
clientName: clientNameById.get(clientId) || clientId,
|
|
actorId: resolveAuditActor(
|
|
item as Pick<CommonAuditLog, "user_id">,
|
|
details,
|
|
),
|
|
action,
|
|
actionLabel: getRecentClientActionLabel(action),
|
|
timestamp: item.timestamp,
|
|
detailLabels: buildRecentClientChangeDetails(action, details),
|
|
} satisfies RecentClientChange;
|
|
})
|
|
.filter((item): item is RecentClientChange => Boolean(item))
|
|
.sort(
|
|
(left, right) =>
|
|
new Date(right.timestamp).getTime() -
|
|
new Date(left.timestamp).getTime(),
|
|
);
|
|
}
|