feat(audit): полноценный writer в audit_log + admin UI (audit + outbox DLQ)
До этого audit_log entity была определена, но никто туда не писал — реальный аудит шёл только через Hibernate Envers (без user/IP/trace). Теперь: - AuditLogger service пишет в той же транзакции что и CRUD (DictionaryRecordService). Захватывает: user (JWT sub либо preferred_username), scope, IP (X-Forwarded-For), UA, trace_id (MDC), request_id. Не ломает основной flow при сбое — только громко логирует. - AuditLogRepository: гибкий search() с nullable фильтрами (dictionary, user, action, businessKey, from, to) + Pageable. - AuditAdminController: GET /admin/audit (paginated) + /admin/audit/export.csv (cap 10k строк против OOM). Frontend: - /audit route: фильтр-панель, таблица с цветными бейджами (CREATE/UPDATE/CLOSE), expand-row с JSON before/after diff, footer IP/UA/req_id, кнопка экспорта CSV. Pagination 50/100/200. - /outbox route: stat-cards pending/DLQ + DLQ-таблица с last_error. - Nav links + i18n (RU + EN).
This commit is contained in:
@@ -107,3 +107,67 @@ export type CreateRecordRequest = {
|
||||
validFrom?: string
|
||||
validTo?: string
|
||||
}
|
||||
|
||||
export type AuditAction = 'CREATE' | 'UPDATE' | 'CLOSE'
|
||||
|
||||
export type AuditEntry = {
|
||||
id: number
|
||||
eventTime: string
|
||||
eventType: string
|
||||
action: AuditAction | string
|
||||
userId?: string | null
|
||||
userScope?: DataScope | null
|
||||
dictionaryName?: string | null
|
||||
dictionaryId?: string | null
|
||||
recordId?: string | null
|
||||
businessKey?: string | null
|
||||
payloadBefore?: Record<string, unknown> | null
|
||||
payloadAfter?: Record<string, unknown> | null
|
||||
traceId?: string | null
|
||||
requestId?: string | null
|
||||
ipAddress?: string | null
|
||||
userAgent?: string | null
|
||||
}
|
||||
|
||||
export type AuditPage = {
|
||||
content: AuditEntry[]
|
||||
totalElements: number
|
||||
totalPages: number
|
||||
number: number
|
||||
size: number
|
||||
first: boolean
|
||||
last: boolean
|
||||
}
|
||||
|
||||
export type AuditFilters = {
|
||||
dictionaryName?: string
|
||||
userId?: string
|
||||
action?: AuditAction | ''
|
||||
businessKey?: string
|
||||
from?: string
|
||||
to?: string
|
||||
page?: number
|
||||
size?: number
|
||||
}
|
||||
|
||||
export type OutboxStats = { pending: number; dlq: number }
|
||||
|
||||
export type DlqEntry = {
|
||||
id: number
|
||||
eventType: string
|
||||
dictionaryName?: string | null
|
||||
aggregateId: string
|
||||
kafkaTopic: string
|
||||
attemptCount: number
|
||||
lastError?: string | null
|
||||
createdAt: string
|
||||
dlqAt: string
|
||||
}
|
||||
|
||||
export type DlqPage = {
|
||||
content: DlqEntry[]
|
||||
totalElements: number
|
||||
totalPages: number
|
||||
number: number
|
||||
size: number
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user