feat: M0-04+M0-05 — security events + throttle admin pages
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 7s

This commit is contained in:
WangDL 2026-05-23 20:47:34 +08:00
parent c803fefde6
commit 64a77791c1
5 changed files with 79 additions and 0 deletions

View File

@ -14,6 +14,8 @@ const KnowledgeBasesPage = lazy(() => import('./pages/KnowledgeBases'))
const BillingPage = lazy(() => import('./pages/Billing')) const BillingPage = lazy(() => import('./pages/Billing'))
const GiteaEmbed = lazy(() => import('./pages/GiteaEmbed')) const GiteaEmbed = lazy(() => import('./pages/GiteaEmbed'))
const ConfigPage = lazy(() => import("./pages/Config")) const ConfigPage = lazy(() => import("./pages/Config"))
const SecurityEventsPage = lazy(() => import('./pages/SecurityEvents'))
const ThrottlePage = lazy(() => import('./pages/Throttle'))
const SecretsPage = lazy(() => import('./pages/Secrets')) const SecretsPage = lazy(() => import('./pages/Secrets'))
const MembershipPage = lazy(() => import("./pages/Membership")) const MembershipPage = lazy(() => import("./pages/Membership"))
const FilesAdminPage = lazy(() => import("./pages/FilesAdmin")) const FilesAdminPage = lazy(() => import("./pages/FilesAdmin"))
@ -73,6 +75,14 @@ function App() {
} }
/> />
<Route path="users/members" element={<Placeholder title="普通用户" />} /> <Route path="users/members" element={<Placeholder title="普通用户" />} />
<Route
path="throttle"
element={<PermissionGuard requiredRole="SUPER_ADMIN"><Suspense fallback={<PageLoading />}><ThrottlePage /></Suspense></PermissionGuard>}
/>
<Route
path="security-events"
element={<PermissionGuard requiredRole="SUPER_ADMIN"><Suspense fallback={<PageLoading />}><SecurityEventsPage /></Suspense></PermissionGuard>}
/>
<Route <Route
path="secrets" path="secrets"
element={<PermissionGuard requiredRole="SUPER_ADMIN"><Suspense fallback={<PageLoading />}><SecretsPage /></Suspense></PermissionGuard>} element={<PermissionGuard requiredRole="SUPER_ADMIN"><Suspense fallback={<PageLoading />}><SecretsPage /></Suspense></PermissionGuard>}

View File

@ -30,6 +30,8 @@ export const adminMenuItems: AdminMenuItem[] = [
{ path: '/billing', name: 'API 用量', icon: <DollarOutlined />, requiredRole: 'SUPER_ADMIN' }, { path: '/billing', name: 'API 用量', icon: <DollarOutlined />, requiredRole: 'SUPER_ADMIN' },
{ path: '/git', name: '代码仓库', icon: <CodeOutlined /> }, { path: '/git', name: '代码仓库', icon: <CodeOutlined /> },
{ path: '/ops', name: '系统运维', icon: <CloudServerOutlined />, requiredRole: 'SUPER_ADMIN', children: [ { path: '/ops', name: '系统运维', icon: <CloudServerOutlined />, requiredRole: 'SUPER_ADMIN', children: [
{ path: '/throttle', name: '限流管理' },
{ path: '/security-events', name: '安全事件' },
{ path: '/secrets', name: '密钥管理' }, { path: '/secrets', name: '密钥管理' },
{ path: '/metrics', name: '接口监控' }, { path: '/metrics', name: '接口监控' },
{ path: '/ai-gateway', name: 'AI Gateway' }, { path: '/ai-gateway', name: 'AI Gateway' },

View File

@ -25,6 +25,8 @@ const breadcrumbMap: Record<string, string> = {
'/git': '代码仓库', '/git': '代码仓库',
'/servers': '服务器运维', '/servers': '服务器运维',
'/config': '配置管理', '/config': '配置管理',
'/throttle': '限流管理',
'/security-events': '安全事件',
'/secrets': '密钥管理', '/secrets': '密钥管理',
'/metrics': '接口监控', '/metrics': '接口监控',
'/ai-gateway': 'AI Gateway', '/ai-gateway': 'AI Gateway',

View File

@ -0,0 +1,31 @@
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Table, Button, Typography, Tag, App } from 'antd'
import { ReloadOutlined, SafetyOutlined } from '@ant-design/icons'
import { api } from '@/services/http-client'
import dayjs from 'dayjs'
const { Title } = Typography
export default function SecurityEventsPage() {
const qc = useQueryClient()
const { data } = useQuery({ queryKey: ['security-events'], queryFn: (): Promise<any> => api.get('/admin-api/content-safety/checks'), staleTime: 10_000 })
const cols = [
{ title: '时间', dataIndex: 'createdAt', width: 140, render: (d: string) => dayjs(d).format('MM-DD HH:mm:ss') },
{ title: '类型', dataIndex: 'contentType', width: 100 },
{ title: '用户', dataIndex: 'userId', width: 100, ellipsis: true },
{ title: '风险', dataIndex: 'riskLevel', width: 70, render: (v: string) => <Tag color={v==='critical'?'red':v==='high'?'orange':'default'}>{v}</Tag> },
{ title: '结果', dataIndex: 'result', width: 80, render: (v: string) => <Tag color={v==='passed'?'green':v==='blocked'?'red':'gold'}>{v}</Tag> },
{ title: '匹配词', dataIndex: 'matchedWords', ellipsis: true, width: 150, render: (v: string) => v || '-' },
]
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<Title level={5} style={{ margin: 0 }}><SafetyOutlined /> </Title>
<Button icon={<ReloadOutlined />} onClick={() => qc.invalidateQueries({ queryKey: ['security-events'] })}></Button>
</div>
<Table dataSource={data || []} columns={cols} rowKey="id" pagination={{ pageSize: 20 }} size="small" />
</div>
)
}

34
src/pages/Throttle.tsx Normal file
View File

@ -0,0 +1,34 @@
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Card, Row, Col, Statistic, Table, Button, Typography, Tag } from 'antd'
import { ReloadOutlined, ThunderboltOutlined } from '@ant-design/icons'
import { api } from '@/services/http-client'
const { Title } = Typography
export default function ThrottlePage() {
const qc = useQueryClient()
const { data } = useQuery({ queryKey: ['throttle'], queryFn: (): Promise<any> => api.get('/admin-api/throttle/status'), staleTime: 30_000 })
const rules = data?.rules || []
const cols = [
{ title: '名称', dataIndex: 'name', width: 100 },
{ title: '说明', dataIndex: 'desc', width: 150 },
{ title: '窗口', dataIndex: 'ttl', width: 80 },
{ title: '上限', dataIndex: 'limit', width: 80, render: (v: number) => <Tag color={v <= 5 ? 'red' : v <= 20 ? 'orange' : 'blue'}>{v}</Tag> },
]
return (
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<Title level={5} style={{ margin: 0 }}><ThunderboltOutlined /> </Title>
<Button icon={<ReloadOutlined />} onClick={() => qc.invalidateQueries({ queryKey: ['throttle'] })}></Button>
</div>
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
<Col span={6}><Card size="small"><Statistic title="存储" value={data?.storage || 'Redis'} /></Card></Col>
<Col span={6}><Card size="small"><Statistic title="状态" value={data?.enabled ? '启用' : '禁用'} valueStyle={{ color: data?.enabled ? '#52c41a' : '#ff4d4f' }} /></Card></Col>
<Col span={6}><Card size="small"><Statistic title="规则数" value={rules.length} /></Card></Col>
</Row>
<Card size="small" title="限流规则"><Table dataSource={rules} columns={cols} rowKey="name" pagination={false} size="small" /></Card>
</div>
)
}