1. 页面概览
1.1 是什么
「监控可观测」页(对应前端源码 action/web/src/views/apollo/MonitoringPage.vue,页面标题「Apollo 监控可观测」,对齐 TAD-08)是 LightApollo 的自研轻量监控台:内置 HTTP / TCP / exec 三种探针,后端定时对启用的健康检查执行探测并把结果写入 health_check_results,健康状态按「连续失败次数」判定(healthy → degraded → unhealthy),页面负责把这些对象统一收口展示与配置。页面不引外部监控/图表库:健康分布用六张计数卡 + 状态点 chips,结果趋势用纯 CSS 柱状图(柱高=响应耗时、颜色=状态)。
页面自上而下四大板块:健康总览卡片区(项目内全部健康检查的最新状态分布与状态点)、健康检查列表(探针的 CRUD、启停、立即探测与结果趋势入口)、监控配置卡片(metrics/logs/traces 采集开关、采集间隔与日志保留天数,按环境读写)、监控仪表盘区(简版仪表盘清单与创建,内置模板不可删)。弹窗承载新建/编辑健康检查与探测结果趋势。
理解本页的关键是把「一条健康检查(探针)」当成闭环的最小单元:定义(name + check_type + check_config + 归属环境)→ 后端定时探测(或行内「探测」手动触发)→ 按状态机判定 healthy/degraded/unhealthy → 结果写入趋势 → 总览 chips 与计数反映最新状态。页面上每条健康检查都有独立的检查周期、超时与失败阈值参数,探测结果按 checked_at 时间倒序累积,构成「探测 → 落库 → 判定 → 趋势」的闭环。输入是用户填写的探针定义(URL/主机端口/命令等),输出是列表里的状态徽标、总览卡片计数与弹窗里的耗时趋势。
在整个 Apollo 产品流程中,本页处于「部署与漂移 → 健康监控 → 告警自愈」链路的中间位置:部署产生的健康检查(G18 桥接,携带 deployment_id)在本页列表可查、可探测,「告警自愈」页的告警规则绑定这里的健康检查(下拉取 health-checks),规则的触发依据就是 health_check_results 的最新结果。健康检查被禁用或从未探测时,总览分别按 disabled 与 unknown 单独计数,不混入异常统计。
典型使用链路:① 先在「环境管理」侧确认目标环境(本页默认取环境列表第一个);② 点「新建健康检查」按类型填 URL/host:port/command,保存后列表出现新行(默认启用、最近状态 unknown);③ 等后端调度器按 30s 周期自动探测,或直接点行内「探测」手动触发一次,看状态与耗时;④ 连续失败达到失败阈值后状态翻 unhealthy,总览 chips 同步变红;⑤ 点「结果」打开趋势弹窗,看柱状图与明细定位波动;⑥ 到「告警自愈」页建告警规则绑定该健康检查,异常自动生成事件单。
1.2 核心价值/能力表
| 能力 | 说明 | 对应页面操作 |
|---|---|---|
| 三种探针 | http(GET url + 期望状态码)/ tcp(host+port 连通)/ exec(shell 命令退出码)动态表单按类型切换 | 「新建健康检查」弹窗按 check_type 切换字段 |
| 状态机判定 | 本次 OK→healthy;本次失败且最近 (threshold-1) 条全部非 healthy→unhealthy;其余失败→degraded | 后端 ProbeAndRecord,页面列表展示 last_status |
| 健康总览 | 六张计数卡 + 每检查一颗状态点 chip,一屏掌握项目健康分布 | 页首「健康总览(项目 #1)」卡片 |
| 立即探测 | 行内一键手动触发一次,即时看到本次状态/耗时/失败原因 | 行内「探测」按钮 |
| 结果趋势 | 纯 CSS 柱状趋势(柱高=response_time_ms,最大 56px)+ 明细表,不依赖图表库 | 行内「结果」按钮弹窗 |
| 采集配置 | metrics/logs/traces 开关 + 采集间隔 + 日志保留天数,按环境维度存储 | 「监控配置」卡片 + 「保存配置」 |
| 简版仪表盘 | 6 种模板新建自定义仪表盘,后端幂等补齐内置模板(不可删除) | 「新建仪表盘」/ 行内「删除」 |
1.3 一句话总结
监控可观测页用内置 HTTP/TCP/exec 探针把「探测、判定、趋势、配置、仪表盘」串成 Apollo 的应用健康监控闭环,是部署健康度与告警规则触发依据的展示与配置入口。
2. 访问入口
2.1 路由与菜单
| 项 | 值 |
|---|---|
| 路由 path | /apollo/monitoring |
| 路由 name | ApolloMonitoring |
| meta.title | Apollo 监控可观测 |
| 侧边栏入口 | ApolloLayout 侧边栏「监控」,位于「流水线」之后、「告警自愈」之前 |
| 前端源码 | action/web/src/views/apollo/MonitoringPage.vue |
| API 客户端 | action/web/src/api/apolloClient.js |
| 路由注册 | action/web/src/router/index.js,/apollo 子路由 children 下第 458~463 行,component: ApolloMonitoringPage,挂 ApolloLayout |
相邻页(侧边栏顺序):流水线 pipelines.html(前)、告警自愈 alerts.html(后,告警规则绑定本页健康检查)、部署与漂移 deployments.html(部署自动登记的 deployment_id 健康检查)、环境管理 environments.html(健康检查与监控配置的归属环境)。
2.2 认证与权限
- 路由
requiresAuth: true:未登录访问被前端路由守卫拦截并跳/apollo/login(登录态为独立apollo_token,向后兼容回退读取旧aip_token)。 - 全部端点位于 Apollo 后端 protected 组并按读写执行分层:列表/详情/总览/结果读
PermMonitoringRead,新建/编辑/删除与监控配置写PermMonitoringWrite,启停/立即探测PermMonitoringExecute。403 由 apolloClient 统一alert('无权限执行该操作')。 - 401(apollo_token 失效)由响应拦截器清 token 并跳
/apollo/login;本页无公开端点。
2.3 端口与 API 前缀
- Apollo 后端端口 18082。Vite 开发代理把
/apollo-api前缀 rewrite 为/api转发(后端路由注册在/api/v1),客户端 baseURL/apollo-api/v1。 - 统一响应 envelope
{code,message,data,request_id}:拦截器在code===0时把response.data解包为业务数据,页面const { data } = await apiClient.get(...)直接拿业务数组/对象。 - 环境下拉辅助接口走
GET /projects/1/environments(环境管理模块),同时决定监控配置默认读取的环境 id(取第一个环境的 id)。
3. 界面布局
+--------------------------------------------------------------+
| Apollo 监控可观测 [新建健康检查] |
| [alert-info 顶部说明] [操作结果提示条(可关闭)] |
+--------------------------------------------------------------+
| [健康总览(项目 #1)卡片] |
| healthy 正常 | degraded 降级 | unhealthy 异常 | |
| unknown 未探测 | disabled 禁用 | 检查总数 六张计数卡 |
| (chips 区:每检查 状态点+名称+环境·类型+最近状态,或空态提示)|
+--------------------------------------------------------------+
| [健康检查(N)卡片] |
| ID|名称|类型|检查配置|间隔|超时|失败阈值|环境|启用|最近状态|操作 |
| 操作列:探测 | 结果 | 编辑 | 删除 |
+--------------------------------------------------------------+
| [监控配置(环境 #N)卡片] |
| 指标采集(metrics_enabled) 日志采集(logs_enabled) |
| 链路追踪(traces_enabled) 三开关 |
| 采集间隔(scrape_interval_seconds) 日志保留(logs_retention_days) |
| [保存配置] |
+--------------------------------------------------------------+
| [监控仪表盘(N)卡片] |
| 名称输入 + 模板下拉 + [新建仪表盘] |
| ID|名称|模板类型|内置|创建时间|操作 |
+--------------------------------------------------------------+
| [新建/编辑健康检查弹窗][探测结果趋势弹窗] |
+--------------------------------------------------------------+
各板块职责:
| 板块 | 职责 |
|---|---|
| 页头 + 顶部说明 + 提示条 | 标题「Apollo 监控可观测」、「新建健康检查」入口、TAD-08 说明、全局操作结果提示(info/success/error 三型,可关闭) |
| 健康总览卡片 | GET /projects/1/health-status 拉取各健康检查最新状态,按 last_status 计六类数字并渲染状态点 chips |
| 健康检查列表卡片 | 探针定义全量表格(空态/表格两态),行内含启停 switch 与四枚操作按钮 |
| 监控配置卡片 | metrics/logs/traces 三开关 + 采集间隔 + 日志保留天数的读写表单,按 configEnvId 环境 |
| 监控仪表盘卡片 | 简版仪表盘清单、内置徽标与创建/删除入口 |
| 新建/编辑健康检查弹窗 | 按 check_type 切换的动态表单(http/tcp/exec),创建与编辑复用 |
| 探测结果趋势弹窗 | 最近 30 条结果的纯 CSS 柱状图 + 明细表 |
4. 交互元素
4.1 页头、顶部说明与操作结果提示条
| 控件 | 位置 | 含义 | 必填/默认/可用条件 | 操作效果 | 触发后端调用 | 边界与细节 |
|---|---|---|---|---|---|---|
| 页面标题 | 页头 h2 | 页面身份标识 | 常驻 | Apollo 监控可观测 | 无 | 与源码 h2 逐字一致 |
| 「新建健康检查」 | 页头右上(btn-primary) | 打开新建弹窗 | 始终可用 | openCreate() 置弹窗可见并重置表单,默认 check_type=http、environment_id=环境列表第一个(环境列表空时回退 1) | 无(提交才触发) | 弹窗标题「新建健康检查(项目 #1)」 |
| 顶部说明 | 标题下 alert-info | TAD-08 定位说明 | 常驻 | 纯文案 | 无 | 文案见 5.4 附注,与源码逐字一致 |
| 操作结果提示条 | 说明下方 | 展示最近一次操作结果 | 有 alert.message 才显示 | 成功用 alert-success、失败用 alert-error 展示;「关闭」按钮清空 | 无 | 单槽覆盖:新结果替换旧提示,无历史 |
页面用一组 ref 维护状态:PROJECT_ID(常量 1)、checks(健康检查列表)、overview/overviewChecks(总览计数与 chips 数据)、configForm(监控配置表单)、dashboards(仪表盘列表)、environments(环境下拉)、busy(当前进行中的操作键,非空时所有行内操作按钮与开关禁用)、alert(提示条单槽)、checkModal/checkForm(健康检查弹窗)、resultsModal(趋势弹窗)。注意 busy 是字符串键(如 probe-3、saveCheck),同一时刻只允许一个写操作在途。
页面数据在 onMounted 一次性拉取:先 fetchEnvironments()(失败静默为空列表),随后并行 fetchChecks() / fetchHealthStatus() / fetchConfig() / fetchDashboards()。无定时轮询、无手动刷新按钮,各写操作成功后仅刷新受影响的列表(详见 4.8)。
4.2 健康总览卡片区
卡片标题固定 健康总览(项目 #{{ PROJECT_ID }})。挂载即请求 GET /projects/1/health-status,成功后把返回 items 经 parseHealthStatus(兼容扁平数组 / 按环境分组 / {items|checks|results} / {health_checks} 多种形态)摊平为 overviewChecks,并按 last_status(缺省当 unknown)累加六类数字:healthy 正常 / degraded 降级 / unhealthy 异常 / unknown 未探测 / disabled 禁用 / 检查总数。
- 计数卡:每张卡一个大号数字 + 标签,色条区分:healthy 绿、degraded 橙、unhealthy 红、unknown/disabled 灰、总数无强调色。
- chips 区:空态提示
暂无健康检查,点击"新建健康检查"添加。;有数据显示一排胶囊,每颗含 状态点 + 名称(title 悬停全名)+环境名 · 类型+ 最近状态。圆点颜色chipStatus:disabled 按灰(unknownclass)处理,其余按 last_status。 - 加载中显示
加载中...;失败在顶部提示加载健康总览失败:{错误},计数保持 0、chips 空(见 7.1——当前版本因缺environment_id恒失败,属已知缺陷)。
后端该端点返回 {environment_id, items:[{check_id,check_name,check_type,last_status,last_checked_at}]}(见 5.3),要求必带 ?environment_id=;本页当前调用未带该参数,总览在 2026-09-07 版本下恒报「缺少查询参数 environment_id」,详见 7.1 与 8 章缺陷表。
4.3 健康检查列表(行级控件)
卡片标题固定 健康检查({{ checks.length }})。空态 暂无健康检查,点击"新建健康检查"添加(http/tcp/exec 探针)。;有数据渲染表格,列头依次 ID / 名称 / 类型 / 检查配置 / 间隔 / 超时 / 失败阈值 / 环境 / 启用 / 最近状态 / 操作。各列取值:
| 列 | 取值规则(逐字对应源码) |
|---|---|
| ID | hc.id 等宽显示 |
| 名称 | 加粗 hc.name |
| 类型 | type-badge 徽标:type-http(蓝)/ type-tcp(紫)/ type-exec(黄),取 hc.check_type,空显 - |
| 检查配置 | configSummary(hc):http 显 url(期望 N)(无期望不显括注);tcp 显 host:port;exec 显命令全文;其余 JSON 化;超长省略号(cfg-cell) |
| 间隔 | formatInterval(hc.interval_seconds):<60 秒显 N 秒,<1 小时显 N 分钟(取整),否则显 N.N 小时,空显 - |
| 超时 | hc.timeout_seconds + 's',空显 - |
| 失败阈值 | hc.unhealthy_threshold,空显 - |
| 环境 | envLabel(hc.environment_id):环境列表命中显 名称(#id),未命中显 #id |
| 启用 | 行内 switch(title 提示「点击禁用/点击启用」) |
| 最近状态 | status-badge 徽标:healthy 绿(status-online)/ degraded 橙(status-degraded)/ unhealthy 红(status-revoked)/ disabled 与 unknown 灰(status-offline),取 hc.last_status || 'unknown' |
4.3.1 启用/停用开关
| 控件 | 位置 | 含义 | 可用条件 | 操作效果 | 触发后端调用 | 边界与细节 |
|---|---|---|---|---|---|---|
| switch | 启用列 | 切换检查启用态 | busy 为空;当前值取 !!hc.enabled | 勾选变化触发 handleToggleEnabled:改为相反态,成功后提示 健康检查 #{id} 已启用(最近状态:{last_status}) 或 健康检查 #{id} 已禁用(最近状态:{last_status})(success),并刷新列表与总览 | POST /health-checks/:id/enable 或 /disable | 失败提示 禁用/启用健康检查 #{id} 失败:{msg}。禁用后调度器不再探测该检查;最近状态从响应 data.last_status 或旧值读取 |
注意:开关不是即时切换视图,先发请求再按成功结果提示;busy 期间全页面按钮与开关禁用。禁用中的检查行内「探测」仍可点击,但后端 ProbeAndRecord 会拒绝并返回错误 health check {id} 已禁用,跳过探测(页面提示探测失败,见 7.3)。
4.3.2 行内「探测」按钮
| 控件 | 位置 | 含义 | 可用条件 | 操作效果 | 触发后端调用 | 边界与细节 |
|---|---|---|---|---|---|---|
| 「探测」 | 操作列(btn-sm btn-outline) | 立即执行一次探测并落库 | busy 为空 | handleProbe 置 busy='probe-{id}',成功后从响应取结果(兼容 data / data.result / data.health_check_result 多形态),提示 探测完成:状态 {status}{,耗时 {rt} ms}{,原因:{err}}——status 为 healthy 用绿色,否则红色 | POST /health-checks/:id/probe | 探测完成后自动刷新列表与总览。禁用中的检查探测会被后端拒绝(见 4.3.1)。目标不可达时状态为 degraded/unhealthy 且提示带原因 |
该操作直接写一条 health_check_results 并刷新 last_status,等同一次调度周期的手动触发,适合验证配置与排查(见 7.2)。
4.3.3 行内「结果」「编辑」「删除」按钮
| 控件 | 位置 | 含义 | 可用条件 | 操作效果 | 触发后端调用 | 边界与细节 |
|---|---|---|---|---|---|---|
| 「结果」 | 操作列 | 打开该检查的结果趋势弹窗 | busy 为空 | openResults(hc) 弹窗载入最近 30 条结果(见 4.5) | GET /health-checks/:id/results?limit=30 | 弹窗标题 探测结果趋势:{名称} |
| 「编辑」 | 操作列 | 打开编辑弹窗回填表单 | busy 为空 | openEdit(hc) 回填 check_config 各字段到动态表单并置 mode='edit' | 无(提交才触发) | 弹窗标题 编辑健康检查 #{id}(项目 #1) |
| 「删除」 | 操作列(btn-danger) | 删除该健康检查 | busy 为空 | confirm('确定删除健康检查 "{名称}"(#{id})吗?关联的探测结果将一并清理。') 通过后 DELETE 并提示 健康检查已删除(success),刷新列表与总览 | DELETE /health-checks/:id | 确认文案逐字见左;后端级联清理该检查的 health_check_results;失败提示 删除健康检查失败:{msg} |
4.4 新建/编辑健康检查弹窗
| 控件 | 位置 | 含义 | 必填/默认/可用条件 | 操作效果 | 触发后端调用 | 边界与细节 |
|---|---|---|---|---|---|---|
| 弹窗标题 | 弹窗头 | 标识当前模式 | create 显示 新建健康检查,edit 显示 编辑健康检查 #{id},均附 (项目 #1) | - | 无 | 点遮罩 @click.self 或「关闭」可关闭 |
| 名称(name)* | 表单首行 | 探针名称 | 必填;placeholder 如 api-health / db-port / deploy-check | 前端空名拦截提示 名称(name)必填 | - | 保存 payload name trim |
| 检查类型(check_type)* | 类型下拉 | 探针类型 | 必填;http/tcp/exec 三选(HTTP 探测/TCP 端口探测/Exec 命令探测) | 切换即切换下方动态表单 | - | 创建默认 http |
| 所属环境(environment_id)* | 环境选择 | 探针归属环境 | 环境列表非空时下拉(名称(#id));为空时降级为数字输入(placeholder 环境 ID(环境列表加载失败时手输)) | - | - | 创建默认取环境列表第一个(空则 1);后端对 0 值兜底写 1 |
| URL(check_config.url)* | http 动态区 | 探测地址 | 仅 http 显示;必填 | - | - | placeholder 如 http://127.0.0.1:18080/healthz |
| 期望状态码(check_config.expect_status) | http 动态区 | 期望 HTTP 状态码 | 仅 http;非必填,0=仅探测可达 | 填了才进 payload | - | placeholder 如 200;>0 时与实际状态码须相等 |
| 主机(check_config.host)* / 端口(check_config.port)* | tcp 动态区 | TCP 目标 | 仅 tcp;必填 | - | - | 端口 min=1 max=65535;placeholder 127.0.0.1 / 5432 |
| 命令(check_config.command)* | exec 动态区 | shell 命令 | 仅 exec;必填 | - | - | placeholder 如 curl -s http://127.0.0.1:18080/healthz;exit code 0=OK |
| 探测间隔(interval_seconds) | 通用三连输入 | 探测周期 | 非必填 | 非空才入 payload | - | placeholder 默认 30;后端存储但当前调度不按它逐个排程(见 5.4/8) |
| 超时(timeout_seconds) | 通用三连输入 | 单次超时 | 非必填 | 非空才入 payload | - | placeholder 默认 5 |
| 失败阈值(unhealthy_threshold) | 通用三连输入 | 连续失败判 unhealthy | 非必填 | 非空才入 payload | - | placeholder 默认 3;1=单次失败即异常 |
| 「取消」/「创建」或「保存」 | 弹窗底部 | 关闭 / 提交 | 提交时 busy 为空 | create 调用 POST,成功提示 健康检查创建成功(id={id});edit 调用 PUT,成功提示 健康检查 #{id} 更新成功;均关闭弹窗并刷新列表与总览 | POST /projects/1/health-checks / PUT /health-checks/:id | 保存前校验:名称空、配置不完整(见下)会前端拦截;失败提示 保存健康检查失败:{msg} |
前端配置完整性校验(handleSaveCheck,拦截文案逐字):检查配置不完整(http 需 url;tcp 需 host+port;exec 需 command)——http 判 cfg.url 非空、tcp 判 cfg.host 非空且 cfg.port > 0、exec 判 cfg.command 非空。后端另做 CheckConfig 必填校验,双重防护。
保存 payload(buildCheckPayload):固定字段 {name, check_type, check_config:{url 或 host+port 或 command}, environment_id};三个通用数字字段仅在非空时追加(转为 Number);expect_status 非空时作为数字入 check_config。编辑保存为全量 PUT,未填的通用字段保持不变。
4.5 探测结果趋势弹窗(「结果」)
| 控件/元素 | 位置 | 含义 | 取值规则 | 操作效果 | 触发后端调用 | 边界与细节 |
|---|---|---|---|---|---|---|
| 弹窗标题 | 弹窗头 | 标识检查 | 探测结果趋势:{hc.name} | - | 无 | 点遮罩或「关闭」可关 |
| 柱状趋势区 | 弹窗上部(.trend-box) | 最近探测耗时与状态的 CSS 柱状图 | 每柱高=该条 response_time_ms 相对本批最大值的归一化(max(4, round(rt/maxRt*56)),最高 56px),颜色=状态:绿 healthy / 橙 degraded / 红 unhealthy / 灰 unknown;rt=0 也给 4px 底柱 | 纯展示;hover title #序号 状态 {status} @ {时间} · {rt} ms | 无 | 柱高随本批最大值缩放,无固定刻度;caption 文案 柱高 = response_time_ms(最大 {maxRt} ms),颜色 = 状态(绿 healthy / 橙 degraded / 红 unhealthy / 灰 unknown) |
| 明细表 | 弹窗下部 | 最近 30 条结果逐条明细 | 列:# / 状态(status-badge)/ 状态码 / 响应耗时 / 错误信息 / 探测时间;状态码与耗时空显 -,错误信息空显 - | 纯展示 | 无 | 时间 formatTime(RFC3339 去 T/Z 截前 19 位,不转本地时区);空态 暂无探测结果,可先点击"探测"触发一次。 |
openResults 请求 GET /health-checks/:id/results(Query limit=30),响应经 toList 解析,并求本批 response_time_ms 最大值 maxRt 供柱高归一化;加载失败顶部提示 加载探测结果失败:{msg}。后端结果默认按 checked_at DESC 倒序返回(最新的在最左/最上)。
4.6 监控配置卡片
卡片标题固定 监控配置(环境 #{{ configEnvId }})——configEnvId 由环境列表第一个环境决定(初始 1)。表单默认值:metrics_enabled=true、scrape_interval_seconds=30、logs_enabled=true、logs_retention_days=7、traces_enabled=false;读接口返回后按 data.config || data.monitoring_config || data 回填,其中 metrics/logs 用「!== false 即开」宽松判定。
| 控件 | 位置 | 含义 | 可用条件 | 操作效果 | 触发后端调用 | 边界与细节 |
|---|---|---|---|---|---|---|
| 指标采集(metrics_enabled) | 配置卡第一行 switch-lg | 指标采集开关 | 常驻 | v-model 直接改本地表单 | 无(点保存才提交) | - |
| 日志采集(logs_enabled) | 同左 | 日志采集开关 | 常驻 | 同左 | 无 | - |
| 链路追踪(traces_enabled) | 同左 | 链路采集开关 | 常驻 | 同左 | 无 | 默认关 |
| 采集间隔(scrape_interval_seconds,秒) | 第二行数字输入 min=1 | 采集周期 | 常驻 | 仅本地 | 无 | 保存前须 >0 |
| 日志保留(logs_retention_days,天) | 同左 min=1 | 日志保留天数 | 常驻 | 仅本地 | 无 | 保存前须 >0 |
| 「保存配置」 | 卡片右下(btn-primary) | 提交监控配置 | busy 为空;进行中按钮显示 保存中... | handleSaveConfig 先本地校验后 PUT,成功提示 监控配置保存成功(环境 #{configEnvId})(success) | PUT /projects/1/monitoring-config(Query environment_id={configEnvId}) | 校验拦截文案:采集间隔(scrape_interval_seconds)必须大于 0、日志保留(logs_retention_days)必须大于 0;失败提示 保存监控配置失败:{msg} |
读配置走 GET /projects/1/monitoring-config(Query environment_id={configEnvId}),加载失败顶部提示 加载监控配置失败:{msg}。后端 GetOrCreateConfig 在不存在时按默认值幂等建行(project_id+environment_id 唯一),因此该配置只影响项目 1 的目标环境,其它环境配置独立存储(见 8)。
4.7 监控仪表盘卡片
卡片标题固定 监控仪表盘({{ dashboards.length }})。卡片内含创建行 + 表格:
| 控件 | 位置 | 含义 | 可用条件 | 操作效果 | 触发后端调用 | 边界与细节 |
|---|---|---|---|---|---|---|
| 名称输入 | 创建行 | 仪表盘名称 | placeholder 仪表盘名称,如 订单服务概览;回车(@keyup.enter)等同点新建 | - | - | 名称为空时新建按钮禁用 |
| 模板下拉 | 创建行 | dashboard template_type | 六选项:自定义(custom) / Go 应用(go_app) / Java 应用(java_app) / Nginx(nginx) / Redis(redis) / K8s Pod(k8s_pod) | - | - | 默认 custom |
| 「新建仪表盘」 | 创建行(btn-primary) | 创建自定义仪表盘 | busy 为空且名称非空 trim | handleCreateDashboard:POST 成功提示 仪表盘创建成功(id={id})(success)、清空名称并刷新列表;进行中显示 创建中... | POST /projects/1/dashboards | 名称空兜底提示 仪表盘名称必填;失败提示 创建仪表盘失败:{msg} |
| 表格行 | ID/名称/模板类型/内置/创建时间/操作 | 内置列徽标 内置(灰 status-offline)/ 自定义(绿 status-online) | - | 模板类型按模板 label 显示 | - | 时间 formatTime(created_at) |
| 「删除」 | 操作列(btn-danger) | 删除仪表盘 | busy 为空且非内置(!!d.is_builtin 禁用,title 内置仪表盘不允许删除) | confirm('确定删除仪表盘 "{名称}"(#{id})吗?') 通过后 DELETE,成功提示 仪表盘已删除(success)并刷新 | DELETE /dashboards/:id | is_builtin 为 true 时按钮禁用、点击直接 return;失败提示 删除仪表盘失败:{msg} |
列表请求 GET /projects/1/dashboards:后端先幂等补齐内置模板(Go 应用概览 go_app、Nginx 接入概览 nginx,is_builtin=true,见 5.4)再按 id 升序返回。空态 暂无仪表盘。;内置与自定义同名不冲突(无唯一约束),可「新建同名自定义」。
4.8 全局互斥与数据刷新
全页唯一「写互斥位」是字符串 busy:任何写操作开始置为对应操作键(saveCheck/saveConfig/saveDash/probe-{id}/enable-{id}/disable-{id}/delCheck-{id}/delDash-{id} 等),结束时在 finally 清空。busy 非空时:列表四枚操作按钮与行内 switch、弹窗提交按钮、监控配置保存按钮、仪表盘删除按钮全部禁用(:disabled="!!busy")——避免并发写把同一检查状态改乱。注意 打开弹窗/查看结果不受 busy 约束(只读)。
各写操作成功后的刷新面:健康检查相关的保存/探测/启停/删除都会 fetchChecks() + fetchHealthStatus();监控配置保存只提交不重拉配置;仪表盘创建/删除只 fetchDashboards()。页面初始加载顺序与失败策略见 4.1。
5. 后端关联
5.1 API 客户端
action/web/src/api/apolloClient.js:axios 实例 baseURL /apollo-api/v1、timeout 30000ms、请求拦截器对所有请求附加 Authorization: Bearer <token>(token 取 apollo_token,无则回退 aip_token)。响应拦截器:HTTP 2xx 且 code===0 时把 response.data 解包为业务数据;HTTP 2xx 但 code!==0 拒绝;4xx/5xx 提取 body.message || body.error 并把 message 别名写入 error.response.data.error 后 reject——401 清 token 跳 /apollo/login,403 alert('无权限执行该操作')。页面 catch 统一以 err.response?.data?.message || err.response?.data?.error || err.message || '未知错误' 展示后端错误。
5.2 端点表
以下路径均为相对 baseURL /apollo-api/v1(Vite rewrite 后后端收 /api/v1/...)。项目 id 页面恒用 1。权限点映射见 server.go 注册(894~908 行)。
| 方法 | 路径 | 权限 | 请求体/Query | 页面触发点 |
|---|---|---|---|---|
| GET | /projects/1/health-status | PermMonitoringRead | 后端要求 Query environment_id(当前页面未传,见 7.1/8) | 健康总览卡片 |
| GET | /projects/1/health-checks | PermMonitoringRead | 可选 Query deployment_id(部署关联过滤,页面未用) | 健康检查列表 |
| POST | /projects/1/health-checks | PermMonitoringWrite | body HealthCheckRequest(见 4.4) | 新建健康检查 |
| GET | /health-checks/:id | PermMonitoringRead | - | (页面未调用,后端有) |
| PUT | /health-checks/:id | PermMonitoringWrite | body HealthCheckRequest | 编辑健康检查 |
| DELETE | /health-checks/:id | PermMonitoringWrite | - | 删除健康检查 |
| POST | /health-checks/:id/enable | PermMonitoringExecute | - | 启用开关 |
| POST | /health-checks/:id/disable | PermMonitoringExecute | - | 停用开关 |
| POST | /health-checks/:id/probe | PermMonitoringExecute | - | 「探测」 |
| GET | /health-checks/:id/results | PermMonitoringRead | Query limit(默认 20、上限 1000;页面传 30) | 「结果」趋势弹窗 |
| GET | /projects/1/monitoring-config | PermMonitoringRead | Query environment_id(必填) | 监控配置读 |
| PUT | /projects/1/monitoring-config | PermMonitoringWrite | Query environment_id(必填);body MonitoringConfigRequest(指针字段部分更新) | 「保存配置」 |
| GET | /projects/1/dashboards | PermMonitoringRead | - | 仪表盘列表 |
| POST | /projects/1/dashboards | PermMonitoringWrite | body DashboardRequest{name, template_type} | 「新建仪表盘」 |
| DELETE | /dashboards/:id | PermMonitoringWrite | - | 删除仪表盘 |
| GET | /projects/1/environments | - | - | 环境下拉(决定 configEnvId 与弹窗默认环境) |
5.3 响应结构示例
统一 envelope 解包后(code===0 的 data)。健康总览(GET /projects/1/health-status?environment_id=1,真实后端形状——页面当前未传参恒 400,示例供接口正确调用时参考):
{
"environment_id": 1,
"items": [
{ "check_id": 3, "check_name": "api-health", "check_type": "http", "last_status": "healthy", "last_checked_at": "2026-09-07T03:21:00+08:00" },
{ "check_id": 5, "check_name": "db-port", "check_type": "tcp", "last_status": "degraded", "last_checked_at": "2026-09-07T03:21:30+08:00" }
]
}
字段含义(monitoring.EnvironmentStatusItem):check_id 健康检查主键;check_name 名称;check_type 探针类型 http/tcp/exec;last_status 最近判定状态(healthy/degraded/unhealthy/disabled/unknown);last_checked_at 最近一次探测时间(无结果则缺省)。本页 parseHealthStatus 对扁平数组 / {environments:[{items:[]}]} / {items} / {health_checks} 均能摊平,取 last_status 计数。
健康检查列表(GET /projects/1/health-checks,envelope data 为数组,单项即 HealthCheck 行):
{
"id": 3,
"project_id": 1,
"environment_id": 1,
"name": "api-health",
"check_type": "http",
"check_config": { "url": "http://127.0.0.1:18080/healthz", "expect_status": 200 },
"interval_seconds": 30,
"timeout_seconds": 5,
"unhealthy_threshold": 3,
"enabled": true,
"last_status": "healthy",
"created_at": "2026-09-07T02:00:00+08:00",
"updated_at": "2026-09-07T03:21:00+08:00"
}
立即探测 / 单条结果(POST /health-checks/:id/probe 与 GET .../results 元素同构,envelope data 为 HealthCheckResult 或数组):
{
"id": 91,
"health_check_id": 3,
"status": "healthy",
"status_code": 200,
"response_time_ms": 8,
"error_message": "",
"checked_at": "2026-09-07T03:21:30+08:00"
}
字段含义:status_code 非 HTTP 探测恒 0;error_message 失败原因(HTTP 报 HTTP 探测失败: .../状态码不符: 期望 200, 实际 500、TCP 报 TCP 探测失败: ...、exec 报 exec 探测失败: ...);checked_at 探测时间。
监控配置(GET/PUT /projects/1/monitoring-config?environment_id=1):
{
"id": 1,
"project_id": 1,
"environment_id": 1,
"metrics_enabled": true,
"scrape_interval_seconds": 30,
"logs_enabled": true,
"logs_retention_days": 7,
"traces_enabled": false,
"created_at": "2026-09-07T01:00:00+08:00",
"updated_at": "2026-09-07T03:20:00+08:00"
}
仪表盘列表(GET /projects/1/dashboards,后端先幂等补齐内置模板再返回,envelope data 为数组):
[
{ "id": 1, "project_id": 1, "name": "Go 应用概览", "template_type": "go_app", "dashboard_json": { "title": "Go 应用概览", "charts": [] }, "is_builtin": true, "created_at": "2026-09-07T01:00:00+08:00", "updated_at": "2026-09-07T01:00:00+08:00" },
{ "id": 2, "project_id": 1, "name": "Nginx 接入概览", "template_type": "nginx", "dashboard_json": { "title": "Nginx 接入概览", "charts": [] }, "is_builtin": true, "created_at": "2026-09-07T01:00:00+08:00", "updated_at": "2026-09-07T01:00:00+08:00" }
]
is_builtin=true 的行前端禁用删除;新建自定义返回 data 为创建的 Dashboard(含新 id),页面取 data.id || data.dashboard.id。
错误示例(envelope fail):健康总览缺 environment_id 时后端返回 HTTP 400、code=40001、message=缺少查询参数 environment_id(测试 M-23 佐证,见 7.1)。
5.4 关键机制
三种探针的实现语义(monitoring/probe.go,RunProbe 按 check_type 分发):
- http:
http.Get该 URL,比较实际状态码与expect_status——expect_status<=0(0=仅探测可达)不做码比较,任意响应码即 OK;expect_status>0时必须相等,否则 NOT OK,错误文案状态码不符: 期望 %d, 实际 %d。请求超时受 timeout 控制,错误文案HTTP 探测失败: ...。 - tcp:
net.DialTimeout("tcp", host:port, timeout),连接成功即 OK(不读数据);失败文案TCP 探测失败: ...。 - exec:在 Apollo 后端进程内执行命令(Windows 走
cmd.exe /c,Unix 走sh -c),exit code 0 为 OK,非零/超时失败,文案exec 探测失败: ...。命令可用性依赖后端部署环境(本机有 curl/程序才有意义)。 - file:后端另支持
{path}文件存在性探针(G11 探针分层),页面未暴露(见 8)。
健康状态机(ProbeAndRecord):单次探测结果经三次判定后写 health_check_results 并刷新 health_checks.last_status:本次 OK→healthy;本次失败且最近 (threshold-1) 条结果全部非 healthy→unhealthy(连续失败链达成);其余失败→degraded。特例 threshold=1:单次失败即 unhealthy(不查历史)。探测前若检查已禁用,直接返回错误 health check {id} 已禁用,跳过探测 不落库。默认常量:DefaultScrapeIntervalSeconds=30、DefaultTimeoutSeconds=5、DefaultUnhealthyThreshold=3、DefaultLogsRetentionDays=7(monitoring/models.go)。后端兜底逻辑与页面默认值一致。
调度:monitoring.Scheduler 在 server 启动处以 30s 间隔 Start(ctx, 30*time.Second),每 tick 调 RunOnce——把全部 enabled=true 的健康检查按 id 升序各探测一轮(单条失败不中断整轮,仅 logger.Warn)。因此「每检查的 interval_seconds」在列表可见,但当前不参与按检查独立排程,实际节奏由全局 30s tick 驱动(见 8 边界)。
监控配置按环境存储:monitoring_configs 以 project_id+environment_id 唯一索引;GetOrCreateConfig 不存在时按默认值幂等建行,UpdateConfig 用指针字段做部分更新(请求体缺失字段不改动)。页面 configEnvId 取环境列表第一个 id,GET/PUT 均带该 environment_id。健康检查创建时后端对 environment_id=0 兜底写 1(与配置默认环境口径一致),故本页总览即便补上参数也只反映默认环境 #1 的检查。
内置仪表盘:EnsureBuiltinDashboards 在 GET /projects/1/dashboards 时对缺失的内置模板幂等建行——内置定义两个:Go 应用概览(template_type=go_app,结构 JSON title 为 Go 应用概览)与 Nginx 接入概览(nginx,title 为 Nginx 接入概览),均 is_builtin=true。DeleteDashboard 对内置行返回拒绝(前端已禁用按钮双保险),新建自定义仅存 name+template_type(未提供 dashboard_json 用空模板)。
总览宽松解析是兼容性设计:parseHealthStatus 同时兼容「扁平数组」「按环境分组(对象数组内嵌 items)」「{items|checks|results}」「{health_checks}」四种后端形态,把环境分组里每条子项合并 environment 字段后摊平。这与 deployments/monitoring 跨模块复用 health-status 的字段演进有关;当前 Apollo 后端实际只回 {environment_id, items} 一种形态。
跨页关联:部署与健康(G18)——部署产生/关联的健康检查带 deployment_id,GET /health-checks?deployment_id=x 可按部署过滤查询(页面未用,供「部署与漂移」侧追溯);告警绑定——「告警自愈」页新建规则的健康检查下拉即本页资源,alert 包按表名直查 health_checks/health_check_results,评估引擎消费最新一条结果判定触发(见 alerts.html 5.4)。
6. 权限与安全
- 认证:全部端点位于 Apollo protected 组(
/api/v1),JWT 无效返回 401,前端拦截器清 token 并跳/apollo/login;本页无公开端点、无匿名可调接口。 - 权限点分层:读(PermMonitoringRead:列表/详情/总览/结果/配置读/仪表盘列)、写(PermMonitoringWrite:新建/编辑/删除、配置写、仪表盘建删)、执行(PermMonitoringExecute:enable/disable/probe)三权分离,403 统一
alert('无权限执行该操作')。 - 写操作防护:删除健康检查与删除仪表盘均经
window.confirm二次确认;删除健康检查会级联清理其探测结果(确认文案已明示);监控配置保存前本地校验采集间隔与日志保留必须大于 0。 - 并发防护:全局
busy互斥串行化写操作;exec 探针在服务端进程内执行命令,属演示功能,生产应限定命令白名单(见 8)。
7. 常见问题与排错
以下现象均由 MonitoringPage.vue 的 catch 分支、后端 handler/测试与状态机代码归纳,可溯源复现;确属前端缺陷的两条见 8 章,并已记入临时缺陷报告。
顶部提示「加载健康总览失败:缺少查询参数 environment_id」,健康总览计数恒为 0:原因是
fetchHealthStatus()调用GET /projects/1/health-status时未带?environment_id=,而后端handleHealthStatus经parseEnvIDQuery强制要求该参数(缺失返回 40001「缺少查询参数 environment_id」,测试 M-23 佐证)。处理:这是 2026-09-07 版本的 web/src 前端缺陷(未按后端契约传参),用户侧暂无法绕过——总览恒失败、chips 恒空,健康检查状态请以列表「最近状态」列为准;修复需在fetchHealthStatus补{ params: { environment_id: configEnvId.value } }。探测返回 degraded/unhealthy,或最近状态一直 unknown:原因是目标不可达(http 连接失败/状态码不符、tcp 端口不通、exec 命令非零退出/超时),或尚未到首个调度 tick(unknown=从未产生结果)。处理:先点行内「探测」看提示原因(如
HTTP 探测失败: ...、状态码不符: 期望 200, 实际 500、TCP 探测失败: ...);核对 URL/端口/命令与目标服务存活;确认检查处于启用态。exec 探测失败还需确认命令在 Apollo 后端所在机器可用(本页探测在服务端进程内执行,不是浏览器)。点「探测」提示「探测健康检查 #{id} 失败:health check {id} 已禁用,跳过探测」:原因是该行开关处于关闭态,后端
ProbeAndRecord拒绝对禁用检查探测。处理:先打开启用开关(切换后提示最近状态)再探测;或编辑后由调度器下轮自动探测。新建健康检查提示「检查配置不完整(http 需 url;tcp 需 host+port;exec 需 command)」:原因是当前 check_type 对应的动态字段缺填(如 tcp 缺 port、exec 缺 command),前端
handleSaveCheck拦截。处理:切回对应类型补齐必填字段(http→url、tcp→host+port、exec→command)再保存;expect_status/host/port 类型不符时 Number 转换也可能被后端拒绝。「保存配置」被拦截或提示失败:原因是
scrape_interval_seconds或logs_retention_days为空/≤0(前端先拦采集间隔(scrape_interval_seconds)必须大于 0/日志保留(logs_retention_days)必须大于 0),或后端 PUT 校验失败。处理:确认两个数字字段 >0;若已 >0 仍失败,看错误 message 与 401/403(权限不足或 token 失效)。内置仪表盘无法删除、删除按钮禁用:原因是该行
is_builtin=true(内置模板),按钮 title内置仪表盘不允许删除且:disabled="!!busy || !!d.is_builtin"。处理:仅可删除自定义仪表盘;内置模板无需删,可直接新建同名自定义。「结果」趋势弹窗柱子形状看不懂或没有数据:原因是柱高按本批最大
response_time_ms归一化(最高 56px),长尾耗时会让多数柱子趋同;空态暂无探测结果,可先点击"探测"触发一次。。处理:把鼠标移到柱子上看 hover title(序号/状态/时间/耗时);确认检查最近探测过、且列表刷新成功;仅最近 30 条可见,更早历史需后端配合分页。删除健康检查后「告警自愈」页规则里的下拉/绑定怎么办:健康检查被删后,绑定它的告警规则
health_check_id悬空,规则评估时查询无结果即跳过(EvaluateHealthCheck 找不到最新结果直接 return,不触发)。处理:到「告警自愈」页把失效规则改为绑定现存检查、停用或删除,避免规则形同虚设。
8. 已知缺陷与边界
| 缺陷/边界 | 说明 |
|---|---|
| 健康总览缺参恒失败(web/src 可修) | fetchHealthStatus() 未传后端必填的 environment_id,总览恒 400「缺少查询参数 environment_id」,计数与 chips 恒空;修复:GET 补 { params: { environment_id: configEnvId.value } }。已记临时缺陷报告 batch-p5 |
| 项目 id 硬编码 | PROJECT_ID = 1 常量,注释标明「后续可改为从 URL 参数 /project_id 读取」 |
| 监控配置单环境 | 配置读写固定针对 configEnvId(环境列表第一个),多环境需切环境列表顺序或改代码;其它环境无 UI 入口 |
| 探测节奏与 interval_seconds 不符 | 后端调度按全局 30s tick 对所有 enabled 检查各探测一轮,单检查 interval_seconds 仅存储展示、不参与独立排程;页面「间隔」列勿理解为精确周期 |
| 结果趋势无分页 | openResults 固定 limit=30,弹窗不提供翻页,更早历史不可见(后端 ListResults 默认 20、上限 1000,可手动改 limit 但页面写死 30) |
| 纯 CSS 柱状局限 | 柱高最大 56px、按本批最大值归一化,无固定刻度/坐标轴,长尾耗时柱形趋同 |
| exec/file 探针边界 | exec 在 Apollo 后端进程内执行命令、file 探针仅后端支持;本页未暴露 file 形态 UI,exec 命令可用性依赖部署环境 |
| 总览宽松解析 | parseHealthStatus 兼容多种后端形态属兼容性设计,但也掩盖契约漂移(见第 1 条缺参缺陷直到接口测试才暴露) |
| 仪表盘为简版 | 仅清单/创建/删除与内置补齐,无图表渲染、无编辑能力;内置 dashboard_json 为空 charts 模板 |
| 列表/总览无轮询 | onMounted 拉取一次后仅写操作成功刷新;长时间停留页面的状态不自动更新,需重新进入或操作触发 |
| 时间展示为 UTC | formatTime 对 RFC3339 去 T/Z 截前 19 位展示,不转本地时区,跨时区观察有偏差 |
附录 A:速查——页面文案与关键常量清单
| 场景 | 源码取值/文案(逐字) |
|---|---|
| 页面标题 | Apollo 监控可观测 |
| 顶部说明 | TAD-08 自研轻量监控:内置 HTTP/TCP/exec 探针定时探测并落库,健康状态按连续失败次数判定(healthy → degraded → unhealthy);本项目 id 暂用常量 1。 |
| 总览卡片标题 | 健康总览(项目 #{{ PROJECT_ID }}) |
| 计数卡标签 | healthy 正常 / degraded 降级 / unhealthy 异常 / unknown 未探测 / disabled 禁用 / 检查总数 |
| 列表卡片标题 | 健康检查({{ checks.length }}) |
| 总览/列表空态 | 暂无健康检查,点击"新建健康检查"添加。 / 暂无健康检查,点击"新建健康检查"添加(http/tcp/exec 探针)。 |
| 类型选项 | HTTP 探测 / TCP 端口探测 / Exec 命令探测 |
| 配置不完整提示 | 检查配置不完整(http 需 url;tcp 需 host+port;exec 需 command) |
| 探测成功提示 | 探测完成:状态 {status}{,耗时 {rt} ms}{,原因:{err}} |
| 启停提示 | 健康检查 #{id} 已启用(最近状态:{status}) / 健康检查 #{id} 已禁用(最近状态:{status}) |
| 删除确认 | 确定删除健康检查 "{name}"(#{id})吗?关联的探测结果将一并清理。 |
| 配置保存成功 | 监控配置保存成功(环境 #{configEnvId}) |
| 健康检查保存/更新成功 | 健康检查创建成功(id={newId})(无 id 时不带括号部分)/ 健康检查 #{id} 更新成功 |
| 加载/保存失败提示 | 加载健康总览失败:{msg} / 加载健康检查列表失败:{msg} / 保存监控配置失败:{msg} / 保存健康检查失败:{msg} / 加载仪表盘列表失败:{msg} |
| 配置校验 | 采集间隔(scrape_interval_seconds)必须大于 0 / 日志保留(logs_retention_days)必须大于 0 |
| 仪表盘空态 | 暂无仪表盘。 |
| 删除确认(仪表盘) | 确定删除仪表盘 "{name}"(#{id})吗? |
| 内置禁用提示 | title 内置仪表盘不允许删除 |
| 趋势 caption | 柱高 = response_time_ms(最大 {maxRt} ms),颜色 = 状态(绿 healthy / 橙 degraded / 红 unhealthy / 灰 unknown) |
| 结果空态 | 暂无探测结果,可先点击"探测"触发一次。 |
| 默认常量 | interval_seconds=30、timeout_seconds=5、unhealthy_threshold=3、logs_retention_days=7 |
| 模板枚举 | custom / go_app / java_app / nginx / redis / k8s_pod |