Compare commits
No commits in common. "7c802bdf470f02832fb743a2f98b1bab70b284f9" and "9e36c10d31ae8f8c8a3413284fc5a8f1b37cfaa7" have entirely different histories.
7c802bdf47
...
9e36c10d31
1
.gitignore
vendored
1
.gitignore
vendored
@ -22,4 +22,3 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
.env
|
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
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 }
|
|
||||||
}
|
|
||||||
@ -2,7 +2,7 @@ import { useState } from 'react'
|
|||||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { Card, Row, Col, Progress, Table, Tag, Typography, Button, Space, Tooltip, App } from 'antd'
|
import { Card, Row, Col, Progress, Table, Tag, Typography, Button, Space, Tooltip, App } from 'antd'
|
||||||
import { CloudServerOutlined, ReloadOutlined, CopyOutlined, GlobalOutlined } from '@ant-design/icons'
|
import { CloudServerOutlined, ReloadOutlined, CopyOutlined, GlobalOutlined } from '@ant-design/icons'
|
||||||
import { getServerMetrics, getServerHealth, getWorkerStatus, type ServerInfo, type ProcessInfo, type ServerHealth } from '@/services/server-api'
|
import { getServerMetrics, getServerHealth, type ServerInfo, type ProcessInfo, type ServerHealth } from '@/services/server-api'
|
||||||
|
|
||||||
const { Text, Title } = Typography
|
const { Text, Title } = Typography
|
||||||
|
|
||||||
@ -86,60 +86,6 @@ 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 (
|
|
||||||
<div style={{ marginBottom: 20 }}>
|
|
||||||
<Title level={5} style={{ marginBottom: 12 }}>
|
|
||||||
🟢 Worker 实例
|
|
||||||
<Tag style={{ marginLeft: 8 }}>{workers.length} 个实例</Tag>
|
|
||||||
</Title>
|
|
||||||
{workers.length === 0 && !isLoading ? (
|
|
||||||
<Card size="small"><Text type="secondary">暂无 Worker 在线(Heartbeat 未检测到)</Text></Card>
|
|
||||||
) : (
|
|
||||||
<Row gutter={[16, 16]}>
|
|
||||||
{workers.map(w => (
|
|
||||||
<Col xs={24} md={12} key={w.instanceId}>
|
|
||||||
<Card size="small" title={
|
|
||||||
<Space>
|
|
||||||
<Tag color={statusColor(w.status)}>{statusLabel(w.status)}</Tag>
|
|
||||||
<Text code style={{ fontSize: 12 }}>{w.instanceId?.slice(0, 40)}...</Text>
|
|
||||||
</Space>
|
|
||||||
}>
|
|
||||||
<Row gutter={[8, 4]}>
|
|
||||||
<Col span={12}><Text type="secondary">版本</Text><br /><Text>{w.appVersion}</Text></Col>
|
|
||||||
<Col span={12}><Text type="secondary">主机</Text><br /><Text>{w.hostname}</Text></Col>
|
|
||||||
<Col span={12}><Text type="secondary">启动时间</Text><br /><Text style={{ fontSize: 12 }}>{w.startedAt ? new Date(w.startedAt).toLocaleString() : '-'}</Text></Col>
|
|
||||||
<Col span={12}><Text type="secondary">最后心跳</Text><br /><Text>{w.lastHeartbeatAgo}s 前</Text></Col>
|
|
||||||
</Row>
|
|
||||||
<div style={{ marginTop: 8 }}>
|
|
||||||
<Text type="secondary" style={{ fontSize: 12 }}>队列</Text>
|
|
||||||
<div style={{ marginTop: 4 }}>
|
|
||||||
<Space wrap size={[4, 4]}>
|
|
||||||
{w.queues?.map(q => <Tag key={q} color="blue" style={{ fontSize: 11 }}>{q}</Tag>)}
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ServersContent() {
|
function ServersContent() {
|
||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
const [refreshing, setRefreshing] = useState(false)
|
const [refreshing, setRefreshing] = useState(false)
|
||||||
@ -176,9 +122,6 @@ function ServersContent() {
|
|||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{/* Worker Status Section */}
|
|
||||||
<WorkerStatusSection />
|
|
||||||
|
|
||||||
{/* Health Check Section */}
|
{/* Health Check Section */}
|
||||||
{health?.length ? (
|
{health?.length ? (
|
||||||
<div style={{ marginTop: 20 }}>
|
<div style={{ marginTop: 20 }}>
|
||||||
|
|||||||
@ -15,7 +15,6 @@ const AnomalyPage = lazy(() => import('@/pages/learning/DataPages').then(m => ({
|
|||||||
const UserTimelinePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserTimelinePage })))
|
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 UserDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.UserDiagnosePage })))
|
||||||
const MaterialDiagnosePage = lazy(() => import('@/pages/learning/DataPages').then(m => ({ default: m.MaterialDiagnosePage })))
|
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 KnowledgeSourcesPage = lazy(() => import('@/pages/KnowledgeSources'))
|
||||||
const LearningReplay = lazy(() => import('@/pages/learning/ReplayPage'))
|
const LearningReplay = lazy(() => import('@/pages/learning/ReplayPage'))
|
||||||
|
|
||||||
@ -38,7 +37,6 @@ export const routeConfig: RouteConfig[] = [
|
|||||||
{ path: '/ai-costs', title: 'AI 调用与成本', element: UserManagement },
|
{ path: '/ai-costs', title: 'AI 调用与成本', element: UserManagement },
|
||||||
{ path: '/files', title: '文件与 COS', element: UserManagement },
|
{ path: '/files', title: '文件与 COS', element: UserManagement },
|
||||||
{ path: '/settings', title: '系统配置', element: UserManagement, requiredRole: 'ADMIN' },
|
{ path: '/settings', title: '系统配置', element: UserManagement, requiredRole: 'ADMIN' },
|
||||||
{ path: '/servers', title: '服务器运维', element: ServersPage, requiredRole: 'SUPER_ADMIN' },
|
|
||||||
{ path: '/audit', title: '审计日志', element: UserManagement, requiredRole: 'ADMIN' },
|
{ path: '/audit', title: '审计日志', element: UserManagement, requiredRole: 'ADMIN' },
|
||||||
// ── Learning Info ──
|
// ── Learning Info ──
|
||||||
{ path: '/learning', title: '学习 Dashboard', element: LearningDashboard },
|
{ path: '/learning', title: '学习 Dashboard', element: LearningDashboard },
|
||||||
|
|||||||
@ -26,17 +26,3 @@ export function getServerMetrics(): Promise<{ servers: ServerInfo[] }> {
|
|||||||
export function getServerHealth(): Promise<ServerHealth[]> {
|
export function getServerHealth(): Promise<ServerHealth[]> {
|
||||||
return api.get('/admin-api/servers/health')
|
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')
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import { defineConfig, loadEnv } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig({
|
||||||
const env = loadEnv(mode, process.cwd(), '')
|
|
||||||
const apiBase = env.VITE_API_BASE_URL || 'https://api.longde.cloud'
|
|
||||||
|
|
||||||
return {
|
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: { '@': path.resolve(import.meta.dirname, 'src') },
|
alias: { '@': path.resolve(import.meta.dirname, 'src') },
|
||||||
@ -28,9 +24,8 @@ export default defineConfig(({ mode }) => {
|
|||||||
server: {
|
server: {
|
||||||
port: 5174, host: true,
|
port: 5174, host: true,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': apiBase,
|
'/api': 'https://api.longde.cloud',
|
||||||
'/admin-api': apiBase,
|
'/admin-api': 'https://api.longde.cloud',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user