forked from wangziqi/gongxue-base
feat: add platform audit alerts
This commit is contained in:
@@ -3,12 +3,15 @@ import Taro from '@tarojs/taro';
|
||||
import { Button, Text, View } from '@tarojs/components';
|
||||
import {
|
||||
exportPlatformAuditLogs,
|
||||
loadPlatformAuditAlerts,
|
||||
loadPlatformAuditLogs,
|
||||
loadPlatformInvoices,
|
||||
loadPlatformOverview,
|
||||
loadPlatformQuestionBankGrants,
|
||||
loadPlatformQuestionBanks,
|
||||
loadPlatformTenants,
|
||||
updatePlatformAuditAlertStatus,
|
||||
type PlatformAuditAlertItem,
|
||||
type PlatformAuditLogItem,
|
||||
type PlatformInvoiceItem,
|
||||
type PlatformOverview,
|
||||
@@ -38,6 +41,7 @@ export default function PlatformWorkbenchPage() {
|
||||
const [tenants, setTenants] = useState<PlatformTenantItem[]>([]);
|
||||
const [invoices, setInvoices] = useState<PlatformInvoiceItem[]>([]);
|
||||
const [auditLogs, setAuditLogs] = useState<PlatformAuditLogItem[]>([]);
|
||||
const [auditAlerts, setAuditAlerts] = useState<PlatformAuditAlertItem[]>([]);
|
||||
const [banks, setBanks] = useState<PlatformQuestionBankItem[]>([]);
|
||||
const [grants, setGrants] = useState<PlatformQuestionBankGrant[]>([]);
|
||||
const [error, setError] = useState('');
|
||||
@@ -51,13 +55,15 @@ export default function PlatformWorkbenchPage() {
|
||||
loadPlatformQuestionBanks({ limit: 6 }).catch(() => ({ items: [] })),
|
||||
loadPlatformQuestionBankGrants({ limit: 6 }).catch(() => ({ items: [] })),
|
||||
loadPlatformAuditLogs({ limit: 6 }).catch(() => ({ items: [] })),
|
||||
]).then(([overviewPayload, tenantPayload, invoicePayload, bankPayload, grantPayload, auditPayload]) => {
|
||||
loadPlatformAuditAlerts({ status: 'open', limit: 6 }).catch(() => ({ items: [] })),
|
||||
]).then(([overviewPayload, tenantPayload, invoicePayload, bankPayload, grantPayload, auditPayload, alertPayload]) => {
|
||||
setOverview(overviewPayload.item || null);
|
||||
setTenants(tenantPayload.items || []);
|
||||
setInvoices(invoicePayload.items || []);
|
||||
setBanks(bankPayload.items || []);
|
||||
setGrants(grantPayload.items || []);
|
||||
setAuditLogs(auditPayload.items || []);
|
||||
setAuditAlerts(alertPayload.items || []);
|
||||
}).catch(nextError => setError(nextError instanceof Error ? nextError.message : '平台后台加载失败'));
|
||||
}, []);
|
||||
|
||||
@@ -84,6 +90,25 @@ export default function PlatformWorkbenchPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAuditAlert(alertId: string, status: 'acknowledged' | 'resolved') {
|
||||
setBusy(`alert-${alertId}-${status}`);
|
||||
setError('');
|
||||
try {
|
||||
await updatePlatformAuditAlertStatus({
|
||||
alertId,
|
||||
status,
|
||||
resolutionNote: status === 'resolved' ? '平台后台工作台处理' : undefined,
|
||||
});
|
||||
const payload = await loadPlatformAuditAlerts({ status: 'open', limit: 6 });
|
||||
setAuditAlerts(payload.items || []);
|
||||
Taro.showToast({ title: status === 'resolved' ? '已解决' : '已确认', icon: 'success' });
|
||||
} catch (nextError) {
|
||||
setError(nextError instanceof Error ? nextError.message : '告警状态更新失败');
|
||||
} finally {
|
||||
setBusy('');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='platform-page'>
|
||||
<View className='platform-shell'>
|
||||
@@ -155,6 +180,22 @@ export default function PlatformWorkbenchPage() {
|
||||
</View>
|
||||
{!auditLogs.length ? <View className='platform-empty'>暂无审计记录。</View> : null}
|
||||
</View>
|
||||
<View className='platform-section'>
|
||||
<Text className='platform-section-title'>平台审计告警</Text>
|
||||
<View className='platform-list'>
|
||||
{auditAlerts.map(item => (
|
||||
<View className='platform-row' key={item.id}>
|
||||
<Text className='platform-row-main'>{item.title || item.action || '-'}</Text>
|
||||
<Text className='platform-row-meta'>{item.severity || '-'} · {item.tenantName || item.tenantSlug || '平台'} · {item.summary || item.targetType || '-'} · {String(item.auditCreatedAt || item.createdAt || '').slice(0, 19).replace('T', ' ')}</Text>
|
||||
<View className='platform-actions'>
|
||||
<Button className='platform-button' size='mini' loading={busy === `alert-${item.id}-acknowledged`} onClick={() => updateAuditAlert(item.id, 'acknowledged')}>确认</Button>
|
||||
<Button className='platform-button' size='mini' loading={busy === `alert-${item.id}-resolved`} onClick={() => updateAuditAlert(item.id, 'resolved')}>解决</Button>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
{!auditAlerts.length ? <View className='platform-empty'>暂无开放审计告警。</View> : null}
|
||||
</View>
|
||||
{error ? <Text className='platform-error'>{error}</Text> : null}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -235,6 +235,52 @@ export interface PlatformAuditExportItem {
|
||||
filters?: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
export interface PlatformAuditAlertRuleItem {
|
||||
id: string;
|
||||
code?: string | null;
|
||||
name?: string | null;
|
||||
description?: string | null;
|
||||
enabled?: boolean | null;
|
||||
severity?: string | null;
|
||||
actionPatterns?: string[] | null;
|
||||
targetTypes?: string[] | null;
|
||||
tenantId?: string | null;
|
||||
conditions?: Record<string, unknown> | null;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
createdAt?: string | null;
|
||||
updatedAt?: string | null;
|
||||
}
|
||||
|
||||
export interface PlatformAuditAlertItem {
|
||||
id: string;
|
||||
ruleId?: string | null;
|
||||
ruleCode?: string | null;
|
||||
ruleName?: string | null;
|
||||
auditLogId?: string | null;
|
||||
tenantId?: string | null;
|
||||
tenantSlug?: string | null;
|
||||
tenantName?: string | null;
|
||||
severity?: string | null;
|
||||
status?: string | null;
|
||||
action?: string | null;
|
||||
targetType?: string | null;
|
||||
targetId?: string | null;
|
||||
title?: string | null;
|
||||
summary?: string | null;
|
||||
details?: Record<string, unknown> | null;
|
||||
actorUserId?: string | null;
|
||||
actorUsername?: string | null;
|
||||
actorName?: string | null;
|
||||
ipAddress?: string | null;
|
||||
userAgent?: string | null;
|
||||
auditCreatedAt?: string | null;
|
||||
acknowledgedAt?: string | null;
|
||||
resolvedAt?: string | null;
|
||||
resolutionNote?: string | null;
|
||||
createdAt?: string | null;
|
||||
updatedAt?: string | null;
|
||||
}
|
||||
|
||||
export interface CreatePlatformTenantInput {
|
||||
slug: string;
|
||||
name: string;
|
||||
@@ -371,6 +417,39 @@ export async function exportPlatformAuditLogs(query: {
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadPlatformAuditAlertRules(query: { enabled?: boolean; limit?: number } = {}) {
|
||||
return apiRequest<{ items?: PlatformAuditAlertRuleItem[] }>('/api/platform-admin/audit-alert-rules', {
|
||||
query,
|
||||
tenantId: null,
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadPlatformAuditAlerts(query: {
|
||||
tenantId?: string;
|
||||
status?: string;
|
||||
severity?: string;
|
||||
ruleCode?: string;
|
||||
q?: string;
|
||||
limit?: number;
|
||||
} = {}) {
|
||||
return apiRequest<{ items?: PlatformAuditAlertItem[] }>('/api/platform-admin/audit-alerts', {
|
||||
query: { ...query, limit: query.limit || 50 },
|
||||
tenantId: null,
|
||||
});
|
||||
}
|
||||
|
||||
export async function updatePlatformAuditAlertStatus(input: {
|
||||
alertId: string;
|
||||
status: 'acknowledged' | 'resolved' | 'ignored';
|
||||
resolutionNote?: string;
|
||||
}) {
|
||||
return apiRequest<{ item?: PlatformAuditAlertItem }>('/api/platform-admin/audit-alerts/status', {
|
||||
method: 'POST',
|
||||
body: input,
|
||||
tenantId: null,
|
||||
});
|
||||
}
|
||||
|
||||
export async function loadPlatformInvoices(query: { tenantId?: string; status?: string; limit?: number } = {}) {
|
||||
return apiRequest<{ items?: PlatformInvoiceItem[] }>('/api/platform-admin/invoices', {
|
||||
query: { ...query, limit: query.limit || 80 },
|
||||
|
||||
Reference in New Issue
Block a user