OpsPilot:面向企业业务系统的智能运维 Agent 平台(6)
本周我主要做的工作不是单独开发某一个页面,而是围绕“接口层”去整理和思考:现有后端到底能提供什么能力,前端应该怎么接,哪些字段需要映射,哪些接口还需要补充。
我觉得这类工作虽然不如页面效果直观,但它决定了一个项目能不能真正从“前端原型 + 后端原型”变成“能够联动的系统”。
1. 项目现状
项目实际上已经有两个基础部分:
一个基于 FastAPI 的后端原型
一个基于 Vue 3 的前端监控界面
前端入口文件比较标准,使用了 Vue Router、Pinia、Element Plus 和 ECharts:
import { createApp } from 'vue'
import App from './App.vue'
import router, { setupRouterGuard } from './router'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import ECharts from 'vue-echarts'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { LineChart, BarChart } from 'echarts/charts'
import { GridComponent, TooltipComponent, LegendComponent, TitleComponent } from 'echarts/components'
use([CanvasRenderer, LineChart, BarChart, GridComponent, TooltipComponent, LegendComponent, TitleComponent])
const app = createApp(App)
const pinia = createPinia()
app.use(router)
app.use(pinia)
setupRouterGuard(pinia)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus)
app.component('v-chart', ECharts)
app.mount('#app')
也就是说,前端结构已经有了,页面也已经分成了 Dashboard、Alerts、Logs、Chat 等模块。
问题不在“有没有前端”,而在于“前端想调用的接口”和“后端实际提供的接口”并不一致。
2. 后端核心能力
我先梳理了后端到底已经实现了哪些能力。
2.1 健康检查接口
第一个基础接口是健康检查:
@app.get("/api/v1/health")
async def health_check():
"""健康检查"""
return {"status": "ok", "version": "0.1.0"}
这个接口的作用很简单,但对前端很有用。
前端可以在页面初始化时调用它,判断后端是否在线。
2.2 告警历史接口
第二个接口是告警历史:
@app.get("/api/v1/alerts/history")
async def get_alert_history():
"""获取已接收的告警历史"""
return {
"total": len(receiver.received_alerts),
"alerts": [a.to_dict() for a in receiver.received_alerts[-10:]] # 最近10条
}
这个接口负责返回最近接收到的告警。
虽然目前只是保存在内存中,没有接数据库,但已经足够支撑前端做“最近告警列表”的展示。
2.3 告警接收与分析接口
最核心的接口是告警接收接口:
@app.post("/api/v1/alerts/receive")
async def receive_alert(request: Request):
"""
接收Alertmanager webhook推送
"""
payload = await request.json()
alerts = receiver.parse_webhook(payload)
results = []
for alert in alerts:
# 检索知识库
matches = kb.search(alert, top_k=2)
# 根因分析
analysis = analyzer.analyze(alert, matches)
results.append({
"alert": alert.to_dict(),
"knowledge_matches": [
{"id": m.entry["id"], "title": m.entry["title"], "score": m.match_score}
for m in matches
],
"root_cause": {
"candidates": analysis.candidate_causes,
"confidence": analysis.confidence,
"remediation": analysis.remediation
}
})
return JSONResponse({
"status": "processed",
"count": len(results),
"results": results
})
这个接口其实已经把整个后端主链路打通了:
接收告警 JSON
调用 receiver.parse_webhook(payload) 解析原始告警
调用 kb.search(alert, top_k=2) 做知识库检索
调用 analyzer.analyze(alert, matches) 生成分析结果
返回给前端结构化结果
从接口设计的角度看,这已经是一个很适合演示的后端能力。
3. 告警解析模块给前端提供的了哪些字段
在后端中,告警解析逻辑主要在 alert_receiver.py 里。
它会把 webhook 里的原始告警解析成统一结构:
parsed = ParsedAlert(
alert_id=f"{labels.get('alertname', 'unknown')}-{alert.get('startsAt', '')}",
status=alert.get("status", "firing"),
severity=labels.get("severity", "P3"),
service=self._normalize_label(labels, "service"),
instance=self._normalize_label(labels, "instance"),
alertname=labels.get("alertname", "unknown"),
summary=annotations.get("summary", ""),
description=annotations.get("description", ""),
starts_at=alert.get("startsAt", ""),
generator_url=alert.get("generatorURL", ""),
raw_labels=labels,
raw_annotations=annotations
)
然后通过 to_dict() 返回给接口层:
def to_dict(self) -> Dict[str, Any]:
return {
"alert_id": self.alert_id,
"status": self.status,
"severity": self.severity,
"service": self.service,
"instance": self.instance,
"alertname": self.alertname,
"summary": self.summary,
"description": self.description,
"starts_at": self.starts_at,
"generator_url": self.generator_url
}
这一部分是我在做前后端整合时特别关注的,因为它直接决定了前端实际能拿到哪些字段。
4. 知识库和分析结果之间的组织
后端不仅返回告警本身,还会返回知识匹配和根因分析。
知识库检索模块本质上是根据告警名、症状和指标做匹配:
matches = kb.search(alert, top_k=2)
分析模块则返回类似这样的结构:
{
"root_cause": {
"candidates": analysis.candidate_causes,
"confidence": analysis.confidence,
"remediation": analysis.remediation
}
}
这个结果对前端很有价值,因为前端不再只是展示“有一条告警”,还可以展示:
可能的根因是什么
置信度是多少
建议立刻采取哪些处理措施
从产品角度来看,这也是这个项目区别于普通监控面板的地方。
5.我在整合时遇到的核心问题
5.1 前端预期接口与后端实际接口不一致
前端目前的请求封装在 api/request.js 中:
import axios from 'axios'
import { ElMessage } from 'element-plus'
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://127.0.0.1:5000/api',
timeout: 15000
})
这里默认的后端地址是 http://127.0.0.1:5000/api,但我现有的 FastAPI 后端实际上跑在 8080 端口,并且接口路径是 /api/v1/...。
另外,前端定义的监控接口是:
import request from './request'
// 获取主机列表及实时状态
export const getHostList = () => request.get('/hosts')
// 获取单个主机的监控指标,例如 CPU、内存等
export const getHostMetrics = (hostId, timeRange = '1h') =>
request.get(`/metrics/${hostId}`, { params: { range: timeRange } })
// 获取告警列表
export const getAlerts = () => request.get('/alerts')
但当前后端并没有实现:
/hosts
/metrics/:hostId
/alerts
它只有:
/api/v1/health
/api/v1/alerts/history
/api/v1/alerts/receive
所以我的一个核心判断是:
这个阶段不能简单地“让前端照旧去调后端”,而是必须先统一接口设计。
5.2 前端字段与后端字段不一致
前端 Alerts.vue 需要的字段更偏页面展示:
host
source
message
timestamp
status
而后端返回的是:
instance
alertname
summary
starts_at
status
所以我重点考虑的是如何做字段映射,而不是让前端页面大改。
我的映射思路是:
id <- alert_id
timestamp <- starts_at
host <- instance
source <- alertname
message <- summary || description
status <- status
这样前端仍然可以保持原本的表格结构,只需要在数据层做一次转换。
6. 为当前最合理的接入方式
6.1 Dashboard 页面
Dashboard 不必一开始就做成完整主机监控页,而是可以先改成“系统概览页”,展示:
后端是否在线
最近告警数
最近一条告警摘要
它主要依赖两个接口:
GET /api/v1/health
GET /api/v1/alerts/history
6.2 Alerts 页面
Alerts 是最值得优先接通的页面。
因为它和现有后端能力最契合。
一方面,它可以调用:
GET /api/v1/alerts/history
获取最近告警列表。
另一方面,它还可以增加一个“测试分析”按钮,调用:
POST /api/v1/alerts/receive
提交一条测试告警,然后把返回的分析结果显示出来。
这样就能真正展示项目最核心的能力:
告警接收 + 根因分析 + 前端展示
6.3 Logs 和 Chat 页面
这两个页面目前后端并没有对应的真实接口。
所以从整合顺序上,可以先保留 Logs.vue 的 mock 数据和 Chat.vue 的 mock 问答
等核心链路接通之后,再决定要不要继续扩展真实日志接口或智能问答接口。
7. 对于接口端的思考与总结
这次整理下来,我觉得自己做的接口端工作主要有三层。
第一层:确认后端真正已经具备哪些能力
第二层:把前后端的数据结构对齐
第三层:确定分阶段接入策略
这次在 OpsPilot 项目中,我主要做了这些工作:
梳理后端核心接口
分析后端返回的数据结构
识别前端与后端之间的不匹配点
给出字段映射方案
明确页面接入优先级
更多推荐



所有评论(0)