Files
abot/admin/dashboard/templates/system_status.html
liuwei c49f5e509c 新增转图运行时健康监控与手动预热
变更项:\n1. 在 markdown_to_image 增加 get_md2img_health_snapshot 健康快照能力,输出 runtime 线程、事件循环、浏览器连接、启动来源与 PID 状态。\n2. 新增系统接口 GET /api/system/md2img_health,支持后台查询转图运行时健康信息。\n3. 新增系统接口 POST /api/system/md2img_warmup,支持后台手动触发转图预热并返回最新状态。\n4. 在资源监控页面接入转图健康状态条,展示运行时在线状态、浏览器连接状态及关键摘要信息。\n5. 在资源监控页面增加转图预热与状态刷新按钮,便于线上快速自愈与排障。\n6. 补充中文注释与错误提示,保持后端与前端可观测性一致。
2026-04-17 10:04:18 +08:00

214 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}资源监控 - 机器人管理后台{% endblock %}
{% block content %}
<div class="page-shell system-page">
<div class="page-hero">
<div class="page-hero-copy">
<div class="page-eyebrow">System Workspace</div>
<h1>资源监控</h1>
<p>直接在后台查看系统资源变化与运行状态,保持监控入口简洁清晰。</p>
<div class="md2img-health-inline" v-loading="md2imgLoading">
<span class="health-title">转图运行时</span>
<el-tag size="mini" :type="runtimeTagType">{% raw %}{{ runtimeTagText }}{% endraw %}</el-tag>
<el-tag size="mini" :type="browserTagType">{% raw %}{{ browserTagText }}{% endraw %}</el-tag>
<span class="health-brief">{% raw %}{{ md2imgBrief }}{% endraw %}</span>
<span class="health-time" v-if="md2imgHealth && md2imgHealth.timestamp">
{% raw %}{{ md2imgHealth.timestamp }}{% endraw %}
</span>
</div>
</div>
<div class="page-hero-actions">
<el-button type="success" plain :loading="md2imgWarming" @click="warmupMd2Img">
<i class="el-icon-magic-stick"></i> 预热转图
</el-button>
<el-button type="info" plain :loading="md2imgLoading" @click="loadMd2ImgHealth">
<i class="el-icon-refresh"></i> 刷新转图状态
</el-button>
<el-button type="primary" plain @click="reloadIframe"><i class="el-icon-refresh"></i> 刷新面板</el-button>
<el-button type="primary" @click="openInNewTab"><i class="el-icon-top-right"></i> 新窗口打开</el-button>
<el-button type="danger" @click="confirmRestart"><i class="el-icon-refresh-left"></i> 重启服务</el-button>
</div>
</div>
<el-card class="iframe-shell-card" shadow="hover">
<div slot="header" class="workspace-header">
<div>
<h3>监控面板</h3>
<p>直接在控制台内查看系统资源变化与运行状态。</p>
</div>
<div class="iframe-url">{{ src_url }}</div>
</div>
<div class="iframe-shell">
<iframe ref="monitorFrame" src="{{ src_url }}" frameborder="0"></iframe>
</div>
</el-card>
</div>
{% endblock %}
{% block scripts %}
<script>
new Vue({
el: '#app',
mixins: [baseApp],
data() {
return {
currentView: '14',
frameUrl: '{{ src_url }}',
restarting: false,
md2imgLoading: false,
md2imgWarming: false,
md2imgHealth: null
}
},
computed: {
runtimeTagText() {
const runtime = this.md2imgHealth && this.md2imgHealth.runtime ? this.md2imgHealth.runtime : {};
return runtime.loop_running ? '运行时在线' : '运行时未就绪';
},
runtimeTagType() {
const runtime = this.md2imgHealth && this.md2imgHealth.runtime ? this.md2imgHealth.runtime : {};
return runtime.loop_running ? 'success' : 'warning';
},
browserTagText() {
const browser = this.md2imgHealth && this.md2imgHealth.browser ? this.md2imgHealth.browser : {};
return browser.connected ? '浏览器已连接' : '浏览器未连接';
},
browserTagType() {
const browser = this.md2imgHealth && this.md2imgHealth.browser ? this.md2imgHealth.browser : {};
return browser.connected ? 'success' : 'info';
},
md2imgBrief() {
if (!this.md2imgHealth) return '尚未获取状态';
const runtime = this.md2imgHealth.runtime || {};
const browser = this.md2imgHealth.browser || {};
const loopText = runtime.loop_id ? `loop=${runtime.loop_id}` : 'loop=-';
const pidText = browser.pid ? `pid=${browser.pid}` : 'pid=-';
const sourceText = browser.launch_source ? `source=${browser.launch_source}` : 'source=-';
return `${loopText} · ${pidText} · ${sourceText}`;
}
},
mounted() {
this.currentView = '14';
this.loadMd2ImgHealth();
},
methods: {
reloadIframe() {
if (this.$refs.monitorFrame) {
this.$refs.monitorFrame.src = this.frameUrl;
}
},
openInNewTab() {
window.open(this.frameUrl, '_blank');
},
confirmRestart() {
this.$confirm('确认执行 ./restart.sh 重启服务吗?这会中断当前服务几秒钟。', '重启确认', {
confirmButtonText: '确认重启',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.triggerRestart();
}).catch(() => {});
},
async triggerRestart() {
if (this.restarting) return;
this.restarting = true;
try {
const response = await axios.post('/api/restart_service');
if (response.data.success) {
this.$message.success(response.data.message || '已触发重启');
} else {
this.$message.error(response.data.message || '重启失败');
}
} catch (error) {
this.$message.error(error.response?.data?.message || '触发重启失败');
} finally {
this.restarting = false;
}
},
async loadMd2ImgHealth() {
if (this.md2imgLoading) return;
this.md2imgLoading = true;
try {
// 默认不强制拉起 runtime避免纯查看状态时引入副作用。
const response = await axios.get('/api/system/md2img_health');
if (response.data && response.data.success) {
this.md2imgHealth = response.data.data || null;
} else {
this.$message.error(response.data?.message || '获取转图状态失败');
}
} catch (error) {
this.$message.error(error.response?.data?.message || '获取转图状态失败');
} finally {
this.md2imgLoading = false;
}
},
async warmupMd2Img() {
if (this.md2imgWarming) return;
this.md2imgWarming = true;
try {
const response = await axios.post('/api/system/md2img_warmup', { timeout_seconds: 60 });
if (response.data && response.data.success) {
this.$message.success(response.data.message || '转图预热成功');
this.md2imgHealth = response.data.data || this.md2imgHealth;
} else {
this.$message.error(response.data?.message || '转图预热失败');
}
} catch (error) {
this.$message.error(error.response?.data?.message || '转图预热失败');
} finally {
this.md2imgWarming = false;
this.loadMd2ImgHealth();
}
}
}
});
</script>
<style>
.page-shell { display: flex; flex-direction: column; gap: 16px; }
.page-hero {
display: flex; align-items: flex-end; justify-content: space-between; gap: 18px; padding: 24px 26px; border-radius: 24px;
background: linear-gradient(135deg, rgba(79,70,229,0.10), rgba(59,130,246,0.08), rgba(255,255,255,0.9));
border: 1px solid rgba(148, 163, 184, 0.16); box-shadow: 0 18px 40px rgba(15, 23, 42, 0.06);
}
.page-hero-actions { display: flex; align-items: center; gap: 12px; }
.page-eyebrow { font-size: 12px; text-transform: uppercase; letter-spacing: .08em; color: #6366f1; font-weight: 700; margin-bottom: 8px; }
.page-hero-copy h1 { font-size: 30px; line-height: 1.1; margin-bottom: 10px; color: #0f172a; }
.page-hero-copy p { color: #64748b; font-size: 14px; }
.md2img-health-inline {
margin-top: 12px;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
padding: 8px 10px;
border-radius: 12px;
background: rgba(255,255,255,0.72);
border: 1px solid rgba(148, 163, 184, 0.18);
}
.health-title { font-size: 12px; font-weight: 700; color: #334155; }
.health-brief { font-size: 12px; color: #475569; }
.health-time { font-size: 12px; color: #94a3b8; }
.workspace-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.workspace-header h3 { font-size: 18px; margin-bottom: 4px; }
.workspace-header p { font-size: 13px; color: #64748b; }
.iframe-url {
max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px;
color: #94a3b8; padding: 8px 12px; border-radius: 999px; background: rgba(248,250,252,0.9); border: 1px solid rgba(148,163,184,0.12);
}
.iframe-shell-card { height: calc(100vh - 230px); }
.iframe-shell-card .el-card__body { height: calc(100% - 73px); }
.iframe-shell { height: 100%; border-radius: 18px; overflow: hidden; border: 1px solid rgba(148,163,184,0.12); background: rgba(248,250,252,0.82); }
.iframe-shell iframe { width: 100%; height: 100%; border: none; display: block; background: #fff; }
@media (max-width: 960px) {
.page-hero { flex-direction: column; align-items: flex-start; }
.workspace-header { flex-direction: column; align-items: flex-start; }
.page-hero-actions { flex-wrap: wrap; }
.iframe-url { max-width: 100%; }
.md2img-health-inline { width: 100%; }
}
</style>
{% endblock %}