节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
22.78 KiB
Stardust
<!DOCTYPE html>
<html lang="zh-CN" data-scope="full">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>应用看板 · 订单服务 · 星尘看板设计稿</title>
  <script>
    /* 首屏前应用主题,避免闪白/闪黑 */
    (function () {
      try {
        var m = localStorage.getItem('sd-theme') || 'auto';
        var d = m === 'auto' ? (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : m;
        document.documentElement.dataset.theme = d;
        document.documentElement.dataset.themeMode = m;
      } catch (e) { document.documentElement.dataset.theme = 'dark'; }
    })();
  </script>
  <link rel="stylesheet" href="assets/tokens.css">
  <link rel="stylesheet" href="assets/dashboard.css">
</head>
<body>
<div class="sd-topband"></div>

<div class="sd-app">
  <aside class="sd-rail">
    <a class="sd-rail__brand" href="index.html" title="看板设计稿总览">✦</a>
    <a class="sd-rail__item" href="system.html" title="系统级看板">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
        <rect x="3" y="3" width="7" height="7" rx="1.6"/><rect x="14" y="3" width="7" height="7" rx="1.6"/>
        <rect x="3" y="14" width="7" height="7" rx="1.6"/><rect x="14" y="14" width="7" height="7" rx="1.6"/>
      </svg>
      <span>系统</span>
    </a>
    <a class="sd-rail__item" href="project.html" title="项目看板">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
        <path d="M12 3l9 4.5-9 4.5-9-4.5z"/><path d="M3 12l9 4.5 9-4.5"/><path d="M3 16.5L12 21l9-4.5"/>
      </svg>
      <span>项目</span>
    </a>
    <a class="sd-rail__item is-active" href="app.html" title="应用看板">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
        <path d="M12 3l8 4.5v9L12 21l-8-4.5v-9z"/><path d="M12 12l8-4.5M12 12v9M12 12L4 7.5"/>
      </svg>
      <span>应用</span>
    </a>
    <a class="sd-rail__item" href="node.html" title="节点看板">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
        <rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="14" width="18" height="6" rx="2"/>
        <circle cx="7" cy="7.5" r="1"/><circle cx="7" cy="17" r="1"/>
      </svg>
      <span>节点</span>
    </a>
    <a class="sd-rail__item" href="wallboard.html" title="大屏模式">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
        <rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8M12 16v4"/>
      </svg>
      <span>大屏</span>
    </a>
    <a class="sd-rail__item" href="index.html" title="设计说明">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
        <path d="M6 4h9l4 4v12H6z"/><path d="M15 4v4h4M9 12h6M9 16h6"/>
      </svg>
      <span>说明</span>
    </a>
  </aside>

  <div class="sd-main">
    <header class="sd-topbar">
      <a class="brand" href="index.html">
        <span class="brand__mark">✦</span>
        <span class="brand__text">星尘 Stardust</span>
      </a>
      <span class="topbar__sep"></span>
      <button class="project-trigger" type="button">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7l8 6 8-6M4 7h16v10H4z"/></svg>
        电商中台
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"><path d="M6 9l6 6 6-6"/></svg>
      </button>
      <button class="scope-badge" type="button" data-action="toggle-scope" title="点击切换演示:全量 → 受限 → 无授权">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
          <rect x="4" y="10" width="16" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/>
        </svg>
        <span data-scope-label>全部 12 个项目</span>
      </button>
      <div class="topbar__right">
        <div data-theme-switch></div>
        <span class="topbar__clock" data-clock>00:00:00</span>
        <button class="icon-btn" type="button" data-action="refresh" title="刷新">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></svg>
        </button>
        <div class="avatar" title="当前账号:李海(项目负责人)">李</div>
      </div>
    </header>

    <div class="sd-scroll">
      <div class="sd-content">

        <div class="scope-notice">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
            <path d="M12 3l9 4v6c0 4.6-3.4 7.4-9 8-5.6-.6-9-3.4-9-8V7z"/><path d="M12 9v4M12 16h.01"/>
          </svg>
          <span class="notice--limited">当前为受限数据视角:仅统计你被授权的项目,未授权项目的应用、节点与监控数据不会出现在任何卡片中。</span>
          <span class="notice--none">当前账号未授权任何项目:所有统计卡片已隐藏,请联系管理员分配项目权限。</span>
        </div>

        <section class="scope-empty">
          <div class="scope-empty__icon">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
              <rect x="4" y="10" width="16" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/><path d="M12 14v3"/>
            </svg>
          </div>
          <h2>暂无授权项目</h2>
          <p>你的账号还没有被加入任何项目,无法查看应用看板。请联系项目管理员在「项目成员」中授权。</p>
          <a class="btn btn--primary" href="system.html">返回系统看板</a>
        </section>

        <section class="scope-bar">
          <div>
            <div class="crumbs">
              <a href="index.html">看板总览</a><span class="sep">/</span>
              <a href="project.html">电商中台</a><span class="sep">/</span>
              <span>订单服务</span>
            </div>
          </div>
          <div class="toolbar">
            <div class="segment" role="group" aria-label="时间范围">
              <button type="button">1 小时</button>
              <button type="button" class="is-active">24 小时</button>
              <button type="button">7 天</button>
            </div>
            <button class="btn" type="button" data-action="refresh">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M21 12a9 9 0 1 1-2.64-6.36"/><path d="M21 3v6h-6"/></svg>
              自动刷新
            </button>
            <a class="btn" href="node.html">查看实例节点</a>
            <a class="btn btn--primary" href="#">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 3v12M7 10l5 5 5-5"/><path d="M4 19h16"/></svg>
              部署新版本
            </a>
          </div>
        </section>

        <!-- 应用实体头 -->
        <section class="entity-head">
          <div class="entity-head__icon">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6">
              <path d="M12 3l8 4.5v9L12 21l-8-4.5v-9z"/><path d="M12 12l8-4.5M12 12v9M12 12L4 7.5"/>
            </svg>
          </div>
          <div class="entity-head__main">
            <div class="entity-head__title">
              订单服务
              <span class="entity-head__code">order-api</span>
              <span class="pill pill--online pill--live"><span class="dot"></span>全部实例在线</span>
              <span class="badge">业务服务</span>
            </div>
            <div class="entity-head__meta">
              <span class="kv">所属项目 <b><a href="project.html" style="color:var(--sd-link);text-decoration:none">电商中台</a></b></span><span class="split"></span>
              <span class="kv">版本 <b>v2.8.1</b></span><span class="split"></span>
              <span class="kv">编译于 <b>2026-10-24 09:12</b></span><span class="split"></span>
              <span class="kv">实例 <b>6</b></span><span class="split"></span>
              <span class="kv">最近登录 <b>2 分钟前</b></span>
            </div>
          </div>
          <div class="entity-head__actions">
            <button class="btn" type="button">配置中心</button>
            <button class="btn" type="button">远程命令</button>
            <button class="btn" type="button">调用链</button>
            <button class="btn btn--primary" type="button">部署</button>
          </div>
        </section>

        <!-- KPI -->
        <section class="kpi-row">
          <div class="sd-kpi sd-kpi--brand">
            <div class="sd-kpi__label">当前 QPS</div>
            <div class="sd-kpi__value">310</div>
            <div class="sd-kpi__foot"><span class="delta delta--good">▲ 4.2%</span><span>较昨日</span></div>
            <div class="sd-kpi__spark" data-spark="orderQps"></div>
          </div>
          <div class="sd-kpi sd-kpi--accent">
            <div class="sd-kpi__label">平均耗时</div>
            <div class="sd-kpi__value">42<span class="sd-kpi__unit">ms</span></div>
            <div class="sd-kpi__foot"><span class="delta delta--good">▼ 3 ms</span><span>较昨日</span></div>
          </div>
          <div class="sd-kpi">
            <div class="sd-kpi__label">P95 耗时</div>
            <div class="sd-kpi__value">186<span class="sd-kpi__unit">ms</span></div>
            <div class="sd-kpi__foot"><span class="delta delta--bad">▲ 12 ms</span><span>接近阈值 200ms</span></div>
          </div>
          <div class="sd-kpi sd-kpi--offline">
            <div class="sd-kpi__label">错误率</div>
            <div class="sd-kpi__value">0.42<span class="sd-kpi__unit">%</span></div>
            <div class="sd-kpi__foot"><span class="delta delta--bad">▲ 0.08%</span><span>较昨日</span></div>
            <div class="sd-kpi__spark" data-spark="errorSpark" data-spark-color="var(--sd-color-offline)"></div>
          </div>
          <div class="sd-kpi sd-kpi--online">
            <div class="sd-kpi__label">在线实例</div>
            <div class="sd-kpi__value">6<span class="sd-kpi__unit">/ 6</span></div>
            <div class="sd-kpi__foot"><span class="delta delta--flat">全部在线</span><span>版本一致</span></div>
          </div>
          <div class="sd-kpi">
            <div class="sd-kpi__label">注册服务</div>
            <div class="sd-kpi__value">23<span class="sd-kpi__unit">个</span></div>
            <div class="sd-kpi__foot"><span class="delta delta--good">▲ 1</span><span>今日新增 1 个</span></div>
          </div>
        </section>

        <section class="sd-grid">

          <!-- 请求与错误趋势 -->
          <div class="sd-card col-16 sd-card--fill">
            <div class="sd-card__head">
              <h2 class="sd-card__title">请求量与错误率趋势</h2>
              <div class="legend">
                <span><i class="l-brand"></i>QPS</span>
                <span><i class="l-warn"></i>错误率 %</span>
              </div>
            </div>
            <div id="appTrend" class="chart chart--fill"></div>
          </div>

          <!-- 实例健康 -->
          <div class="sd-card col-8">
            <div class="sd-card__head">
              <h2 class="sd-card__title">实例健康</h2>
              <span class="sd-card__hint">6 / 6 在线 · 版本一致</span>
            </div>
            <div style="display:flex;align-items:center;gap:10px">
              <div id="instanceDonut" class="chart" style="height:132px;width:132px;flex:0 0 132px"></div>
              <div style="display:flex;flex-direction:column;gap:8px;font-size:11px;color:var(--sd-text-secondary)">
                <span>平均在线时长 <b style="color:var(--sd-text-primary)">128 天</b></span>
                <span>最近重启 <b style="color:var(--sd-text-primary)">2 天前</b></span>
                <span>版本分布 <b style="color:var(--sd-text-primary)">v2.8.1 × 6</b></span>
              </div>
            </div>
            <div class="table-wrap" style="margin-top:6px">
              <table class="sd-table sd-table--compact">
                <thead>
                  <tr><th>实例</th><th>版本</th><th class="num">在线</th><th>CPU</th><th>内存</th><th>状态</th></tr>
                </thead>
                <tbody id="instanceRows"></tbody>
              </table>
            </div>
          </div>

          <!-- 端点 Top -->
          <div class="sd-card col-8">
            <div class="sd-card__head">
              <h2 class="sd-card__title">端点 Top</h2>
              <div class="segment" id="endpointMetric">
                <button type="button" class="is-active" data-metric="cost">耗时</button>
                <button type="button" data-metric="errorRate">错误率</button>
                <button type="button" data-metric="qps">调用量</button>
              </div>
            </div>
            <div id="endpointChart" class="chart chart-md"></div>
          </div>

          <!-- 错误分布 -->
          <div class="sd-card col-8">
            <div class="sd-card__head">
              <h2 class="sd-card__title">错误分布</h2>
              <span class="sd-card__hint">近 24 小时 · 383 次</span>
            </div>
            <div id="errorDonut" class="chart chart-sm"></div>
            <div class="code-list" id="errorLegend"></div>
            <div class="code-list" id="errorSamples"></div>
          </div>

          <!-- 运行时性能 -->
          <div class="sd-card col-8">
            <div class="sd-card__head">
              <h2 class="sd-card__title">运行时性能</h2>
              <span class="sd-card__hint">采样 14:18:20</span>
            </div>
            <div class="perf-grid">
              <div class="perf-tile"><span>CPU 使用率</span><b>38%</b><div class="meter meter--ok"><i style="width:38%"></i></div></div>
              <div class="perf-tile"><span>内存占用</span><b>1.24 GB</b><div class="meter meter--ok"><i style="width:52%"></i></div></div>
              <div class="perf-tile"><span>托管堆</span><b>612 MB</b><div class="meter meter--ok"><i style="width:44%"></i></div></div>
              <div class="perf-tile"><span>线程数</span><b>86</b><div class="meter meter--warn"><i style="width:64%"></i></div></div>
              <div class="perf-tile"><span>TCP 连接</span><b>148</b><div class="meter meter--ok"><i style="width:36%"></i></div></div>
              <div class="perf-tile"><span>GC 次数 / 分钟</span><b>12</b><div class="meter meter--ok"><i style="width:28%"></i></div></div>
            </div>
            <div class="legend" style="margin-top:10px">
              <span><i class="l-online"></i>正常</span>
              <span><i class="l-warn"></i>接近阈值</span>
              <span><i class="l-offline"></i>超限</span>
            </div>
          </div>

          <!-- 依赖服务 -->
          <div class="sd-card col-12">
            <div class="sd-card__head">
              <h2 class="sd-card__title">依赖服务</h2>
              <span class="sd-card__hint">出站调用与存储/缓存</span>
            </div>
            <div class="table-wrap">
              <table class="sd-table">
                <thead>
                  <tr><th>依赖</th><th>类型</th><th class="num">QPS</th><th class="num">平均耗时</th><th class="num">错误率</th><th>健康</th></tr>
                </thead>
                <tbody id="dependRows"></tbody>
              </table>
            </div>
          </div>

          <!-- 最近变更 -->
          <div class="sd-card col-6">
            <div class="sd-card__head">
              <h2 class="sd-card__title">最近变更</h2>
              <a class="link" href="#">变更记录</a>
            </div>
            <ul class="timeline" id="changeTimeline"></ul>
          </div>

          <!-- 最新告警 -->
          <div class="sd-card col-6">
            <div class="sd-card__head">
              <h2 class="sd-card__title">最新告警</h2>
              <a class="link" href="#">全部告警</a>
            </div>
            <div id="appAlerts" class="event-list event-list--wrap" style="max-height:214px"></div>
          </div>

        </section>

        <footer class="page-foot">
          <span>数据来源:Registry/App · Registry/AppOnline · Monitors/AppMinuteStat · Monitors/TraceHourStat · Monitors/AppMeter · Monitors/AlarmRecord</span>
          <span>应用看板设计稿 · 下钻:实例 → 节点看板 · 端点 → 跟踪统计</span>
        </footer>
      </div>
    </div>
  </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/echarts@5.6.0/dist/echarts.min.js"></script>
<script src="assets/theme.js"></script>
<script src="assets/mock.js"></script>
<script src="assets/charts.js"></script>
<script>
(function () {
  const fmt = SD.util.fmt;

  /* 实例列表 */
  const instances = [
    { node: 'NJ-App-02', ip: '10.0.2.12', version: 'v2.8.1', uptime: '128 天', cpu: 42, memory: 68, status: 'online' },
    { node: 'NJ-App-01', ip: '10.0.2.11', version: 'v2.8.1', uptime: '96 天', cpu: 38, memory: 62, status: 'online' },
    { node: 'SH-App-01', ip: '10.0.3.21', version: 'v2.8.1', uptime: '64 天', cpu: 71, memory: 74, status: 'warn' },
    { node: 'SH-App-02', ip: '10.0.3.22', version: 'v2.8.1', uptime: '64 天', cpu: 33, memory: 51, status: 'online' },
    { node: 'BJ-App-01', ip: '10.0.5.41', version: 'v2.8.1', uptime: '210 天', cpu: 58, memory: 66, status: 'online' },
    { node: 'NJ-Cache-01', ip: '10.0.2.61', version: 'v2.8.1', uptime: '302 天', cpu: 18, memory: 36, status: 'online' }
  ];
  document.getElementById('instanceRows').innerHTML = instances.map(function (i) {
    const cls = i.status === 'warn' ? 'meter--warn' : 'meter--ok';
    const status = i.status === 'warn'
      ? '<span class="pill pill--warn"><span class="dot"></span>告警</span>'
      : '<span class="pill pill--online"><span class="dot"></span>在线</span>';
    return '<tr class="row-link" onclick="location.href=\'node.html\'">' +
      '<td><div class="strong">' + i.node + '</div><div class="muted" style="font-size:10.5px">' + i.ip + '</div></td>' +
      '<td class="mono">' + i.version + '</td>' +
      '<td class="num">' + i.uptime + '</td>' +
      '<td><div class="meter ' + cls + '" title="' + i.cpu + '%"><i style="width:' + i.cpu + '%"></i></div></td>' +
      '<td><div class="meter ' + cls + '" title="' + i.memory + '%"><i style="width:' + i.memory + '%"></i></div></td>' +
      '<td>' + status + '</td>' +
      '</tr>';
  }).join('');

  SDCharts.donut('instanceDonut', {
    centerText: '6/6',
    centerSub: '实例在线',
    centerSize: 20,
    radius: ['68%', '88%'],
    data: [
      { name: '在线', value: 6, itemStyle: { color: SDCharts.palette.online } },
      { name: '离线', value: 0, itemStyle: { color: SDCharts.palette.offline } }
    ]
  });

  /* 趋势 */
  SDCharts.areaTrend('appTrend', {
    categories: SD.hours,
    interval: 3,
    primary: { name: 'QPS', data: SD.series.appRequest, color: SDCharts.palette.brand, unit: 'QPS' },
    secondary: { name: '错误率', data: SD.series.appError, color: SDCharts.palette.alert, unit: '%', formatter: '{value}%' }
  });

  /* 端点 Top */
  const endpointConf = { cost: 'ms', errorRate: '%', qps: '' };
  function renderEndpoints(metric) {
    const items = SD.endpoints.slice().sort(function (a, b) { return b[metric] - a[metric]; }).map(function (e) {
      return {
        name: e.path,
        value: metric === 'errorRate' ? Number(e.errorRate.toFixed(2)) : e[metric]
      };
    });
    SDCharts.barH('endpointChart', { items: items, unit: endpointConf[metric] });
  }
  renderEndpoints('cost');
  document.getElementById('endpointMetric').addEventListener('click', function (e) {
    const btn = e.target.closest('button');
    if (!btn) return;
    this.querySelectorAll('button').forEach(function (b) { b.classList.remove('is-active'); });
    btn.classList.add('is-active');
    renderEndpoints(btn.dataset.metric);
  });

  /* 错误分布 */
  SDCharts.donut('errorDonut', {
    centerText: '383',
    centerSub: '错误次数',
    centerSize: 20,
    radius: ['62%', '82%'],
    data: SD.errorCodes.map(function (c) {
      return { name: c.name, value: c.value, itemStyle: { color: c.color } };
    })
  });

  const totalErrors = SD.errorCodes.reduce(function (sum, c) { return sum + c.value; }, 0);
  document.getElementById('errorLegend').innerHTML = SD.errorCodes.map(function (c) {
    return '<div class="code-row">' +
      '<i style="width:8px;height:8px;border-radius:2px;background:' + c.color + '"></i>' +
      '<span class="grow">' + c.name + '</span>' +
      '<b style="color:var(--sd-text-regular);font-weight:500">' + c.value + '</b>' +
      '<span style="width:38px;text-align:right">' + (c.value / totalErrors * 100).toFixed(1) + '%</span>' +
      '</div>';
  }).join('');

  const samples = [
    { trace: 'a1f4c9e2b7d84c60', endpoint: 'POST /api/order/create', msg: '支付回调超时', time: '14:18:22' },
    { trace: '7c2e10ab5f934d18', endpoint: 'GET /api/order/query', msg: 'SQL 执行超时 5s', time: '14:12:07' }
  ];
  document.getElementById('errorSamples').innerHTML = samples.map(function (s) {
    return '<div class="code-row"><span class="pill pill--offline" style="padding:1px 6px">错误</span>' +
      '<span class="grow">' + s.endpoint + ' · ' + s.msg + '</span>' +
      '<span class="mono">' + s.trace.slice(0, 8) + '…</span>' +
      '<span>' + s.time + '</span></div>';
  }).join('');

  /* 依赖服务 */
  document.getElementById('dependRows').innerHTML = SD.dependencies.map(function (d) {
    const warn = d.errorRate >= 0.3;
    const pill = warn
      ? '<span class="pill pill--warn"><span class="dot"></span>关注</span>'
      : '<span class="pill pill--online"><span class="dot"></span>正常</span>';
    return '<tr>' +
      '<td class="strong">' + d.name + '</td>' +
      '<td><span class="badge">' + d.type + '</span></td>' +
      '<td class="num">' + fmt(d.qps) + '</td>' +
      '<td class="num">' + d.cost.toFixed(1) + ' ms</td>' +
      '<td class="num">' + d.errorRate.toFixed(2) + '%</td>' +
      '<td>' + pill + '</td>' +
      '</tr>';
  }).join('');

  /* 最近变更 */
  document.getElementById('changeTimeline').innerHTML = SD.appChanges.map(function (c) {
    return '<li class="is-' + c.level + '"><div class="tl-title">' + c.title + '</div><div class="tl-meta">' + c.meta + ' · ' + c.time + '</div></li>';
  }).join('');

  /* 告警 */
  const appAlerts = SD.alerts.filter(function (a) { return a.app === '订单服务' || a.app === '支付网关'; });
  document.getElementById('appAlerts').innerHTML = appAlerts.map(function (a) {
    return '<div class="event event--' + a.level + '">' +
      '<div class="event__bar"></div>' +
      '<div><div class="event__title"><b>' + a.app + '</b> ' + a.message + '</div>' +
      '<div class="event__meta">' + a.node + ' · ' + a.status + '</div></div>' +
      '<div class="event__time">' + a.time + '</div></div>';
  }).join('');
})();
</script>
</body>
</html>