fix: CPU显示修复 + 报表面板添加浏览器池状态
1. CPU 显示修复: - routes/admin_api/core.py: 新增 _get_server_cpu_percent() - 首次调用使用 interval=0.1 避免返回 0.0 - 后续调用使用缓存,TTL 1秒 2. 报表面板浏览器池状态: - admin-frontend/src/api/browser_pool.js: 新增 API 调用 - ReportPage.vue: 添加浏览器池状态卡片 - 显示总/活跃/空闲 Worker 数和队列等待数 - Worker 表格带状态颜色标签(活跃/空闲/异常) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
7
admin-frontend/src/api/browser_pool.js
Normal file
7
admin-frontend/src/api/browser_pool.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { api } from './client'
|
||||
|
||||
export async function fetchBrowserPoolStats() {
|
||||
const { data } = await api.get('/browser_pool/stats')
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { fetchFeedbackStats } from '../api/feedbacks'
|
||||
import { fetchEmailStats } from '../api/email'
|
||||
import { fetchDockerStats, fetchRunningTasks, fetchServerInfo, fetchTaskStats } from '../api/tasks'
|
||||
import { fetchBrowserPoolStats } from '../api/browser_pool'
|
||||
import { fetchSystemConfig } from '../api/system'
|
||||
|
||||
const refreshStats = inject('refreshStats', null)
|
||||
@@ -32,6 +33,7 @@ const emailStats = ref(null)
|
||||
const feedbackStats = ref(null)
|
||||
const serverInfo = ref(null)
|
||||
const dockerStats = ref(null)
|
||||
const browserPoolStats = ref(null)
|
||||
const systemConfig = ref(null)
|
||||
const queueTab = ref('running')
|
||||
|
||||
@@ -97,6 +99,40 @@ const queuingTaskList = computed(() => runningTasks.value?.queuing || [])
|
||||
const runningCount = computed(() => normalizeCount(runningTasks.value?.running_count))
|
||||
const queuingCount = computed(() => normalizeCount(runningTasks.value?.queuing_count))
|
||||
|
||||
const browserPoolWorkers = computed(() => {
|
||||
const workers = browserPoolStats.value?.workers
|
||||
if (!Array.isArray(workers)) return []
|
||||
return [...workers].sort((a, b) => normalizeCount(a?.worker_id) - normalizeCount(b?.worker_id))
|
||||
})
|
||||
|
||||
const browserPoolTotalWorkers = computed(() => normalizeCount(browserPoolStats.value?.total_workers))
|
||||
const browserPoolActiveWorkers = computed(() => browserPoolWorkers.value.filter((w) => Boolean(w?.has_browser)).length)
|
||||
const browserPoolIdleWorkers = computed(() => normalizeCount(browserPoolStats.value?.idle_workers))
|
||||
const browserPoolQueueSize = computed(() => normalizeCount(browserPoolStats.value?.queue_size))
|
||||
const browserPoolBusyWorkers = computed(() => normalizeCount(browserPoolStats.value?.active_workers))
|
||||
|
||||
function workerPoolStatusType(worker) {
|
||||
if (!worker?.thread_alive) return 'danger'
|
||||
if (worker?.has_browser) return 'success'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function workerPoolStatusLabel(worker) {
|
||||
if (!worker?.thread_alive) return '异常'
|
||||
if (worker?.has_browser) return '活跃'
|
||||
return '空闲'
|
||||
}
|
||||
|
||||
function workerRunTagType(worker) {
|
||||
if (!worker?.thread_alive) return 'danger'
|
||||
return worker?.idle ? 'info' : 'warning'
|
||||
}
|
||||
|
||||
function workerRunLabel(worker) {
|
||||
if (!worker?.thread_alive) return '停止'
|
||||
return worker?.idle ? '空闲' : '忙碌'
|
||||
}
|
||||
|
||||
const taskTodaySuccessRate = computed(() => {
|
||||
const success = normalizeCount(taskToday.value.success_tasks)
|
||||
const failed = normalizeCount(taskToday.value.failed_tasks)
|
||||
@@ -156,6 +192,7 @@ async function refreshAll() {
|
||||
feedbackResult,
|
||||
serverResult,
|
||||
dockerResult,
|
||||
browserPoolResult,
|
||||
configResult,
|
||||
] = await Promise.allSettled([
|
||||
fetchTaskStats(),
|
||||
@@ -164,6 +201,7 @@ async function refreshAll() {
|
||||
fetchFeedbackStats(),
|
||||
fetchServerInfo(),
|
||||
fetchDockerStats(),
|
||||
fetchBrowserPoolStats(),
|
||||
fetchSystemConfig(),
|
||||
])
|
||||
|
||||
@@ -173,6 +211,7 @@ async function refreshAll() {
|
||||
feedbackStats.value = feedbackResult.status === 'fulfilled' ? feedbackResult.value : null
|
||||
serverInfo.value = serverResult.status === 'fulfilled' ? serverResult.value : null
|
||||
dockerStats.value = dockerResult.status === 'fulfilled' ? dockerResult.value : null
|
||||
browserPoolStats.value = browserPoolResult.status === 'fulfilled' ? browserPoolResult.value : null
|
||||
systemConfig.value = configResult.status === 'fulfilled' ? configResult.value : null
|
||||
|
||||
await refreshStats?.()
|
||||
@@ -548,6 +587,67 @@ onUnmounted(() => {
|
||||
<el-descriptions-item label="内存">{{ dockerStats?.memory_usage || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="内存占比">{{ dockerStats?.memory_percent || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="panel-head">
|
||||
<div class="head-left">
|
||||
<div class="head-text">
|
||||
<div class="panel-title">浏览器池</div>
|
||||
<div class="panel-sub app-muted">
|
||||
活跃(有浏览器){{ browserPoolActiveWorkers }} · 忙碌 {{ browserPoolBusyWorkers }} · 队列 {{ browserPoolQueueSize }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-tag v-if="browserPoolStats?.server_time_cst" effect="light" type="info">{{ browserPoolStats.server_time_cst }}</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="tile-grid tile-grid--4">
|
||||
<div class="tile">
|
||||
<div class="tile-v">{{ browserPoolTotalWorkers }}</div>
|
||||
<div class="tile-k app-muted">总 Worker</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="tile-v ok">{{ browserPoolActiveWorkers }}</div>
|
||||
<div class="tile-k app-muted">活跃(有浏览器)</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="tile-v">{{ browserPoolIdleWorkers }}</div>
|
||||
<div class="tile-k app-muted">空闲(无任务)</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="tile-v warn">{{ browserPoolQueueSize }}</div>
|
||||
<div class="tile-k app-muted">队列等待</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<el-table :data="browserPoolWorkers" size="small" border>
|
||||
<el-table-column prop="worker_id" label="Worker" width="90" />
|
||||
<el-table-column label="状态" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="workerPoolStatusType(row)" effect="light">{{ workerPoolStatusLabel(row) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="执行" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="workerRunTagType(row)" effect="light">{{ workerRunLabel(row) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="任务" width="120">
|
||||
<template #default="{ row }">
|
||||
<span>{{ normalizeCount(row?.total_tasks) }}</span>
|
||||
<span class="app-muted"> / </span>
|
||||
<span :class="normalizeCount(row?.failed_tasks) ? 'err' : 'app-muted'">{{ normalizeCount(row?.failed_tasks) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="browser_use_count" label="复用" width="90" />
|
||||
<el-table-column prop="last_active_at" label="最近活跃" min-width="160" />
|
||||
<el-table-column prop="browser_created_at" label="浏览器创建" min-width="160" />
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
@@ -901,6 +1001,10 @@ onUnmounted(() => {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.tile-grid--4 {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.tile {
|
||||
border: 1px solid rgba(17, 24, 39, 0.08);
|
||||
border-radius: 16px;
|
||||
@@ -1072,6 +1176,10 @@ onUnmounted(() => {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.tile-grid--4 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.resource-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user