静态站点告警看板:基于Mermaid + 原生JS的轻量可视化实现

举报
yd_282816122 发表于 2026/11/14 18:37:36 2026/11/14
【摘要】 背景前面我们已经实现了埋点采集、日志聚合、阈值环比告警、Webhook推送。但告警消息只推送到IM里有一个短板:没办法直观看到指标趋势、历史告警记录、异常分布,每次排查都要翻原始日志。很多成熟方案会引入Prometheus+Grafana,但对于纯静态站点、无常驻后端的场景太重,维护成本高。我们希望:不引入数据库、不部署额外服务,基于已产出的日志JSON,做一个纯前端、可打包进项目仓库的告...

背景

前面我们已经实现了埋点采集、日志聚合、阈值环比告警、Webhook推送。但告警消息只推送到IM里有一个短板:没办法直观看到指标趋势、历史告警记录、异常分布,每次排查都要翻原始日志。

很多成熟方案会引入Prometheus+Grafana,但对于纯静态站点、无常驻后端的场景太重,维护成本高。我们希望:不引入数据库、不部署额外服务,基于已产出的日志JSON,做一个纯前端、可打包进项目仓库的告警看板。

核心约束:

  1. 全部静态运行,可直接在浏览器打开;
  2. 复用之前Node脚本输出的日志与告警结果;
  3. 使用Mermaid渲染时序/告警拓扑图,降低绘图代码量;
  4. 保留基础筛选:指标类型、时间区间、告警等级;
  5. 可以和CI联动,每次构建刷新看板数据。

整体架构

日志分析脚本 → 输出 metrics.json + alerts.json
        ↓
静态看板页面(HTML + JS + Mermaid)
        ↓
渲染趋势图表、告警列表、异常链路图
  • metrics.json:时序指标,LCP、PV、错误数、环比变化率;
  • alerts.json:历史告警,包含触发时间、阈值、告警等级、关联日志ID;
  • Mermaid负责可视化,原生JS做数据加载、过滤、排序。

核心代码实现

1. 看板页面 index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>站点指标告警看板</title>
<script src="[https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js](https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js)"></script>
<style>
*{box-sizing:border-box;margin:0;padding:0;font-family:system-ui}
.container{max-width:1200px;margin:24px auto;padding:0 16px}
.card{border:1px solid #e5e7eb;border-radius:8px;padding:16px;margin-bottom:16px}
.alert-critical{border-left:4px solid #ef4444}
.alert-warning{border-left:4px solid #f59e0b}
.filter-bar{display:flex;gap:12px;margin-bottom:20px;align-items:center}
pre{white-space:pre-wrap;font-size:12px;color:#666}
</style>
</head>
<body>
<div class="container">
  <h2>静态站点指标 & 告警看板</h2>
  <div class="filter-bar">
    <select id="levelFilter">
      <option value="all">全部告警等级</option>
      <option value="critical">严重</option>
      <option value="warning">警告</option>
    </select>
  </div>
  <div class="card">
    <h3>指标时序图</h3>
    <div class="mermaid" id="metricChart"></div>
  </div>
  <div class="card">
    <h3>告警链路拓扑</h3>
    <div class="mermaid" id="alertGraph"></div>
  </div>
  <div class="card">
    <h3>告警记录列表</h3>
    <div id="alertList"></div>
  </div>
</div>

<script>
mermaid.initialize({startOnLoad:false});

let metrics = [];
let alerts = [];

// 加载数据
async function loadData(){
  const mRes = await fetch('./metrics.json');
  metrics = await mRes.json();
  const aRes = await fetch('./alerts.json');
  alerts = await aRes.json();
  renderChart();
  renderGraph();
  renderAlerts();
}

// 渲染时序图
function renderChart(){
  const times = metrics.map(i => i.time);
  const lcp = metrics.map(i => i.lcp);
  const pv = metrics.map(i => i.pv);
  const mmdText = `
xychart-beta
    title "LCP 与访问量时序"
    x-axis [${times.join(',')}]
    bar [${pv.join(',')}]
    line [${lcp.join(',')}]
  `;
  document.getElementById('metricChart').innerText = mmdText;
  mermaid.run({nodes:[document.getElementById('metricChart')]});
}

// 告警关联拓扑
function renderGraph(){
  const items = alerts.slice(0,6);
  let lines = ['graph LR'];
  items.forEach((item,idx)=>{
    lines.push(`A${idx}[${item.time}:${item.title}]:::${item.level}`);
  })
  lines.push(`classDef critical fill:#fee2e2,stroke:#ef4444`);
  lines.push(`classDef warning fill:#fffbeb,stroke:#f59e0b`);
  document.getElementById('alertGraph').innerText = lines.join('\n');
  mermaid.run({nodes:[document.getElementById('alertGraph')]});
}

// 告警列表渲染
function renderAlerts(){
  const filterVal = document.getElementById('levelFilter').value;
  const container = document.getElementById('alertList');
  container.innerHTML = '';
  const filtered = filterVal === 'all' ? alerts : alerts.filter(x=>x.level === filterVal);
  filtered.forEach(alert=>{
    const div = document.createElement('div');
    div.className = `card alert-${alert.level}`;
    div.innerHTML = `
      <div><strong>${alert.time}【${alert.level}】</strong> ${alert.title}</div>
      <pre>${alert.detail}</pre>
    `
    container.appendChild(div);
  })
}

document.getElementById('levelFilter').onchange = renderAlerts;
loadData();
</script>
</body>
</html>

2. Node 脚本:生成 metrics.json / alerts.json(集成到原有分析脚本)

// 追加写入看板所需文件
const fs = require('fs');

function exportDashboardData(metricList, alertList){
  fs.writeFileSync('./metrics.json', JSON.stringify(metricList, null, 2));
  fs.writeFileSync('./alerts.json', JSON.stringify(alertList, null, 2));
  console.log('[看板数据导出完成] metrics.json, alerts.json');
}
module.exports = { exportDashboardData };

工程权衡

  1. 优点
    • 零服务、零数据库,直接静态托管;
    • Mermaid 声明式绘图,不用维护大量Canvas绘图逻辑;
    • 和原有日志告警链路打通,CI每次执行自动刷新看板数据;
    • 可以直接放进仓库,团队成员打开页面就能看历史异常。
  2. 局限
    • 不适合超大日志量,数据量大之后JSON加载卡顿;
    • 没有自动下钻,复杂的多维度分析能力弱于专业监控;
    • 图表交互能力有限,Mermaid图表只适合概览,不适合精细拖拽缩放。

优化与演进方向

  • 增加数据分页、懒加载,避免一次性加载全量历史日志;
  • 增加告警静默、标记已处理状态,给告警加 resolved 字段;
  • 增加导出CSV按钮,方便把指标导入表格做深度复盘;
  • 增加深色模式适配,适合长时间看告警看板;
  • 后续可以接入简单本地IndexedDB,在浏览器端缓存历史指标,不用每次拉取全量JSON。

CI 集成要点

在之前的CI流水线,日志分析脚本执行完成后,自动调用 exportDashboardData,然后把看板HTML + 两个JSON文件一起打包发布,访问路径和站点本身在同一个静态域名下,CSP配置放开Mermaid CDN即可。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。