feat: add platform audit alert notifications

This commit is contained in:
Codex
2026-06-30 06:46:35 +08:00
parent 5bb0512ba7
commit 7ab702f471
24 changed files with 1731 additions and 30 deletions

View File

@@ -5,6 +5,8 @@ import {
exportPlatformAuditLogs,
loadPlatformAuditAlerts,
loadPlatformAuditLogs,
loadPlatformAuditNotificationChannels,
loadPlatformAuditNotificationEvents,
loadPlatformInvoices,
loadPlatformOverview,
loadPlatformQuestionBankGrants,
@@ -13,6 +15,8 @@ import {
updatePlatformAuditAlertStatus,
type PlatformAuditAlertItem,
type PlatformAuditLogItem,
type PlatformAuditNotificationChannelItem,
type PlatformAuditNotificationEventItem,
type PlatformInvoiceItem,
type PlatformOverview,
type PlatformQuestionBankGrant,
@@ -42,6 +46,8 @@ export default function PlatformWorkbenchPage() {
const [invoices, setInvoices] = useState<PlatformInvoiceItem[]>([]);
const [auditLogs, setAuditLogs] = useState<PlatformAuditLogItem[]>([]);
const [auditAlerts, setAuditAlerts] = useState<PlatformAuditAlertItem[]>([]);
const [auditNotificationChannels, setAuditNotificationChannels] = useState<PlatformAuditNotificationChannelItem[]>([]);
const [auditNotificationEvents, setAuditNotificationEvents] = useState<PlatformAuditNotificationEventItem[]>([]);
const [banks, setBanks] = useState<PlatformQuestionBankItem[]>([]);
const [grants, setGrants] = useState<PlatformQuestionBankGrant[]>([]);
const [error, setError] = useState('');
@@ -56,7 +62,9 @@ export default function PlatformWorkbenchPage() {
loadPlatformQuestionBankGrants({ limit: 6 }).catch(() => ({ items: [] })),
loadPlatformAuditLogs({ limit: 6 }).catch(() => ({ items: [] })),
loadPlatformAuditAlerts({ status: 'open', limit: 6 }).catch(() => ({ items: [] })),
]).then(([overviewPayload, tenantPayload, invoicePayload, bankPayload, grantPayload, auditPayload, alertPayload]) => {
loadPlatformAuditNotificationChannels({ enabled: true, limit: 6 }).catch(() => ({ items: [] })),
loadPlatformAuditNotificationEvents({ limit: 6 }).catch(() => ({ items: [] })),
]).then(([overviewPayload, tenantPayload, invoicePayload, bankPayload, grantPayload, auditPayload, alertPayload, channelPayload, eventPayload]) => {
setOverview(overviewPayload.item || null);
setTenants(tenantPayload.items || []);
setInvoices(invoicePayload.items || []);
@@ -64,6 +72,8 @@ export default function PlatformWorkbenchPage() {
setGrants(grantPayload.items || []);
setAuditLogs(auditPayload.items || []);
setAuditAlerts(alertPayload.items || []);
setAuditNotificationChannels(channelPayload.items || []);
setAuditNotificationEvents(eventPayload.items || []);
}).catch(nextError => setError(nextError instanceof Error ? nextError.message : '平台后台加载失败'));
}, []);
@@ -196,6 +206,30 @@ export default function PlatformWorkbenchPage() {
</View>
{!auditAlerts.length ? <View className='platform-empty'></View> : null}
</View>
<View className='platform-section'>
<Text className='platform-section-title'></Text>
<View className='platform-grid'>
<View className='platform-metric'><Text className='platform-metric-label'></Text><Text className='platform-metric-value'>{String(auditNotificationChannels.length)}</Text></View>
<View className='platform-metric'><Text className='platform-metric-label'></Text><Text className='platform-metric-value'>{String(auditNotificationEvents.length)}</Text></View>
<View className='platform-metric'><Text className='platform-metric-label'></Text><Text className='platform-metric-value'>{String(auditNotificationEvents.filter(item => item.status === 'failed').length)}</Text></View>
<View className='platform-metric'><Text className='platform-metric-label'></Text><Text className='platform-metric-value'>{String(auditNotificationEvents.filter(item => item.status === 'retrying').length)}</Text></View>
</View>
<View className='platform-list'>
{auditNotificationChannels.map(item => (
<View className='platform-row' key={item.id}>
<Text className='platform-row-main'>{item.name || item.channelCode || '-'}</Text>
<Text className='platform-row-meta'>{item.provider || '-'} · {item.minSeverity || '-'} · {item.webhook?.host || '-'} · {item.statusFilter?.join('/') || '-'}</Text>
</View>
))}
{auditNotificationEvents.map(item => (
<View className='platform-row' key={item.id}>
<Text className='platform-row-main'>{item.alertTitle || item.alertAction || '-'}</Text>
<Text className='platform-row-meta'>{item.channelName || item.channelCode || '-'} · {item.status || '-'} · HTTP {String(item.lastHttpCode || '-')} · {item.tenantName || item.tenantSlug || '平台'}</Text>
</View>
))}
</View>
{!auditNotificationChannels.length && !auditNotificationEvents.length ? <View className='platform-empty'></View> : null}
</View>
{error ? <Text className='platform-error'>{error}</Text> : null}
</View>
</View>

View File

@@ -281,6 +281,53 @@ export interface PlatformAuditAlertItem {
updatedAt?: string | null;
}
export interface PlatformAuditNotificationChannelItem {
id: string;
channelCode?: string | null;
name?: string | null;
description?: string | null;
enabled?: boolean | null;
provider?: string | null;
secretRef?: string | null;
minSeverity?: string | null;
statusFilter?: string[] | null;
actionPatterns?: string[] | null;
tenantIds?: string[] | null;
timeoutSec?: number | string | null;
webhook?: {
protocol?: string | null;
host?: string | null;
pathname?: string | null;
} | null;
metadata?: Record<string, unknown> | null;
createdAt?: string | null;
updatedAt?: string | null;
}
export interface PlatformAuditNotificationEventItem {
id: string;
channelId?: string | null;
channelCode?: string | null;
channelName?: string | null;
alertId?: string | null;
auditLogId?: string | null;
provider?: string | null;
status?: string | null;
attempts?: number | string | null;
lastHttpCode?: number | string | null;
lastError?: string | null;
lastResponseSummary?: string | null;
alertSeverity?: string | null;
alertStatus?: string | null;
alertTitle?: string | null;
alertAction?: string | null;
tenantName?: string | null;
tenantSlug?: string | null;
sentAt?: string | null;
createdAt?: string | null;
updatedAt?: string | null;
}
export interface CreatePlatformTenantInput {
slug: string;
name: string;
@@ -450,6 +497,30 @@ export async function updatePlatformAuditAlertStatus(input: {
});
}
export async function loadPlatformAuditNotificationChannels(query: {
enabled?: boolean;
provider?: string;
limit?: number;
} = {}) {
return apiRequest<{ items?: PlatformAuditNotificationChannelItem[] }>('/api/platform-admin/audit-notification-channels', {
query: { ...query, limit: query.limit || 50 },
tenantId: null,
});
}
export async function loadPlatformAuditNotificationEvents(query: {
channelId?: string;
alertId?: string;
status?: string;
provider?: string;
limit?: number;
} = {}) {
return apiRequest<{ items?: PlatformAuditNotificationEventItem[] }>('/api/platform-admin/audit-notification-events', {
query: { ...query, limit: query.limit || 50 },
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 },