diff --git a/src/constants/table.ts b/src/constants/table.ts
new file mode 100644
index 0000000..7bbfc10
--- /dev/null
+++ b/src/constants/table.ts
@@ -0,0 +1,13 @@
+import type { TablePaginationConfig } from 'antd'
+
+/** 全局默认分页配置 */
+export const defaultPagination: TablePaginationConfig = {
+ defaultPageSize: 20,
+ showSizeChanger: true,
+ pageSizeOptions: ['10', '20', '50', '100'],
+}
+
+/** 合并默认分页配置 */
+export function pagination(custom?: TablePaginationConfig): TablePaginationConfig {
+ return { ...defaultPagination, ...custom }
+}
diff --git a/src/pages/Servers.tsx b/src/pages/Servers.tsx
index babe4d9..965c4d6 100644
--- a/src/pages/Servers.tsx
+++ b/src/pages/Servers.tsx
@@ -2,7 +2,7 @@ import { useState } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Card, Row, Col, Progress, Table, Tag, Typography, Button, Space, Tooltip, App } from 'antd'
import { CloudServerOutlined, ReloadOutlined, CopyOutlined, GlobalOutlined } from '@ant-design/icons'
-import { getServerMetrics, getServerHealth, type ServerInfo, type ProcessInfo, type ServerHealth } from '@/services/server-api'
+import { getServerMetrics, getServerHealth, getWorkerStatus, type ServerInfo, type ProcessInfo, type ServerHealth } from '@/services/server-api'
const { Text, Title } = Typography
@@ -86,6 +86,60 @@ function ServerCard({ server }: { server: ServerInfo }) {
)
}
+function WorkerStatusSection() {
+ const { data, isLoading } = useQuery({
+ queryKey: ['servers', 'workers'],
+ queryFn: getWorkerStatus,
+ staleTime: 15_000,
+ refetchInterval: 15_000,
+ })
+
+ const workers = data?.workers ?? []
+
+ const statusColor = (s: string) => s === 'online' ? 'green' : s === 'stale' ? 'orange' : 'red'
+ const statusLabel = (s: string) => s === 'online' ? '在线' : s === 'stale' ? '延迟' : '离线'
+
+ return (
+
+
+ 🟢 Worker 实例
+ {workers.length} 个实例
+
+ {workers.length === 0 && !isLoading ? (
+
暂无 Worker 在线(Heartbeat 未检测到)
+ ) : (
+
+ {workers.map(w => (
+
+
+ {statusLabel(w.status)}
+ {w.instanceId?.slice(0, 40)}...
+
+ }>
+
+ 版本
{w.appVersion}
+ 主机
{w.hostname}
+ 启动时间
{w.startedAt ? new Date(w.startedAt).toLocaleString() : '-'}
+ 最后心跳
{w.lastHeartbeatAgo}s 前
+
+
+
队列
+
+
+ {w.queues?.map(q => {q})}
+
+
+
+
+
+ ))}
+
+ )}
+
+ )
+}
+
function ServersContent() {
const qc = useQueryClient()
const [refreshing, setRefreshing] = useState(false)
@@ -122,6 +176,9 @@ function ServersContent() {
))}
+ {/* Worker Status Section */}
+
+
{/* Health Check Section */}
{health?.length ? (
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
index 485dd47..94ea49a 100644
--- a/src/routes/index.tsx
+++ b/src/routes/index.tsx
@@ -15,6 +15,7 @@ const AnomalyPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({
const UserTimelinePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserTimelinePage })))
const UserDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserDiagnosePage })))
const MaterialDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.MaterialDiagnosePage })))
+const ServersPage = lazy(() => import('@/pages/Servers'))
const KnowledgeSourcesPage = lazy(() => import('@/pages/KnowledgeSources'))
const LearningReplay = lazy(() => import('@/pages/learning/ReplayPage'))
@@ -37,6 +38,7 @@ export const routeConfig: RouteConfig[] = [
{ path: '/ai-costs', title: 'AI 调用与成本', element: UserManagement },
{ path: '/files', title: '文件与 COS', element: UserManagement },
{ path: '/settings', title: '系统配置', element: UserManagement, requiredRole: 'ADMIN' },
+ { path: '/servers', title: '服务器运维', element: ServersPage, requiredRole: 'SUPER_ADMIN' },
{ path: '/audit', title: '审计日志', element: UserManagement, requiredRole: 'ADMIN' },
// ── Learning Info ──
{ path: '/learning', title: '学习 Dashboard', element: LearningDashboard },
diff --git a/src/services/server-api.ts b/src/services/server-api.ts
index 71349eb..63baa71 100644
--- a/src/services/server-api.ts
+++ b/src/services/server-api.ts
@@ -26,3 +26,17 @@ export function getServerMetrics(): Promise<{ servers: ServerInfo[] }> {
export function getServerHealth(): Promise {
return api.get('/admin-api/servers/health')
}
+
+export interface WorkerInfo {
+ instanceId: string
+ appVersion: string
+ startedAt: string
+ queues: string[]
+ hostname: string
+ lastHeartbeatAgo: number
+ status: 'online' | 'stale' | 'offline'
+}
+
+export function getWorkerStatus(): Promise<{ workers: WorkerInfo[] }> {
+ return api.get('/admin-api/servers/workers')
+}