fix(mining-admin): 审计日志 keyword 搜索、管理员列、详情中文化

1. keyword 搜索实际生效:按 resourceId 和管理员用户名模糊匹配
2. LOGIN_FAILED 无账号时管理员列显示尝试的用户名(而非 unknown)
3. 详情列对 LOGIN_FAILED reason 做中文映射

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-10 01:27:31 -07:00
parent a72be1bbf3
commit ff82bddbc6
3 changed files with 31 additions and 3 deletions

View File

@ -28,6 +28,7 @@ export class AuditController {
adminId, adminId,
action, action,
resource, resource,
keyword,
page: page ?? 1, page: page ?? 1,
pageSize: pageSize ?? 20, pageSize: pageSize ?? 20,
}); });
@ -37,7 +38,7 @@ export class AuditController {
items: result.data.map((log: any) => ({ items: result.data.map((log: any) => ({
id: log.id, id: log.id,
adminId: log.adminId, adminId: log.adminId,
adminUsername: log.admin?.username || 'unknown', adminUsername: log.admin?.username || (log.action === 'LOGIN_FAILED' ? log.resourceId : null) || 'unknown',
action: log.action, action: log.action,
resource: log.resource, resource: log.resource,
resourceId: log.resourceId, resourceId: log.resourceId,

View File

@ -575,6 +575,7 @@ export class DashboardService {
adminId?: string; adminId?: string;
action?: string; action?: string;
resource?: string; resource?: string;
keyword?: string;
page?: number; page?: number;
pageSize?: number; pageSize?: number;
}): Promise<{ data: any[]; total: number; pagination: any }> { }): Promise<{ data: any[]; total: number; pagination: any }> {
@ -582,6 +583,12 @@ export class DashboardService {
if (options.adminId) where.adminId = options.adminId; if (options.adminId) where.adminId = options.adminId;
if (options.action) where.action = options.action; if (options.action) where.action = options.action;
if (options.resource) where.resource = options.resource; if (options.resource) where.resource = options.resource;
if (options.keyword) {
where.OR = [
{ resourceId: { contains: options.keyword, mode: 'insensitive' } },
{ admin: { username: { contains: options.keyword, mode: 'insensitive' } } },
];
}
const page = options.page ?? 1; const page = options.page ?? 1;
const pageSize = options.pageSize ?? 50; const pageSize = options.pageSize ?? 50;

View File

@ -41,6 +41,25 @@ const actionLabels: Record<string, { label: string; className: string }> = {
INIT: { label: '初始化', className: 'bg-slate-100 text-slate-600' }, INIT: { label: '初始化', className: 'bg-slate-100 text-slate-600' },
}; };
const loginFailedReasonLabels: Record<string, string> = {
user_not_found: '用户名不存在',
account_disabled: '账号已禁用',
wrong_password: '密码错误',
};
function formatDetails(details: string | null, action: string): string {
if (!details) return '-';
if (action === 'LOGIN_FAILED') {
try {
const parsed = JSON.parse(details);
return loginFailedReasonLabels[parsed.reason] || parsed.reason || details;
} catch {
return details;
}
}
return details;
}
const resourceLabels: Record<string, string> = { const resourceLabels: Record<string, string> = {
AUTH: '认证', AUTH: '认证',
CONFIG: '系统配置', CONFIG: '系统配置',
@ -145,6 +164,7 @@ export default function AuditLogsPage() {
) : ( ) : (
items.map((log) => { items.map((log) => {
const actionInfo = actionLabels[log.action] || { label: log.action, className: '' }; const actionInfo = actionLabels[log.action] || { label: log.action, className: '' };
const detailDisplay = formatDetails(log.details, log.action);
return ( return (
<TableRow key={log.id}> <TableRow key={log.id}>
<TableCell className="text-sm">{formatDateTime(log.createdAt)}</TableCell> <TableCell className="text-sm">{formatDateTime(log.createdAt)}</TableCell>
@ -158,8 +178,8 @@ export default function AuditLogsPage() {
</TableCell> </TableCell>
<TableCell>{resourceLabels[log.resource] || log.resource}</TableCell> <TableCell>{resourceLabels[log.resource] || log.resource}</TableCell>
<TableCell className="font-mono text-xs">{log.resourceId || '-'}</TableCell> <TableCell className="font-mono text-xs">{log.resourceId || '-'}</TableCell>
<TableCell className="max-w-[200px] truncate" title={log.details || ''}> <TableCell className="max-w-[200px] truncate" title={detailDisplay}>
{log.details || '-'} {detailDisplay}
</TableCell> </TableCell>
<TableCell className="font-mono text-xs">{log.ipAddress}</TableCell> <TableCell className="font-mono text-xs">{log.ipAddress}</TableCell>
</TableRow> </TableRow>