feat: M0-04+M0-05 — security events + throttle admin pages
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 7s
Some checks failed
Deploy Admin Frontend / build-and-deploy (push) Failing after 7s
This commit is contained in:
parent
c803fefde6
commit
64a77791c1
10
src/App.tsx
10
src/App.tsx
@ -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>}
|
||||||
|
|||||||
@ -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' },
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
31
src/pages/SecurityEvents.tsx
Normal file
31
src/pages/SecurityEvents.tsx
Normal 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
34
src/pages/Throttle.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user