节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
20.70 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">
  <!-- 左侧图标导航(对应 StarWeb StardustLayout 的 56px 窄导航) -->
  <aside class="sd-rail">
    <a class="sd-rail__brand" href="index.html" title="看板设计稿总览">✦</a>
    <a class="sd-rail__item is-active" 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" 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">

        <!-- 受限提示(limited / none 时由 CSS 显示) -->
        <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="project.html">去申请项目权限</a>
        </section>

        <!-- 作用域栏 -->
        <section class="scope-bar">
          <div>
            <div class="crumbs">
              <a href="index.html">看板总览</a><span class="sep">/</span><span>系统级</span>
            </div>
            <h1 class="page-title">
              系统级看板
              <span class="pill pill--online pill--live"><span class="dot"></span>整体运行正常</span>
            </h1>
            <p class="page-sub">
              覆盖 <strong data-scope-projects>12</strong> 个项目 ·
              <strong data-scope-value data-full="41" data-limited="13">41</strong> 个应用 ·
              <strong data-scope-value data-full="32" data-limited="21">32</strong> 个节点 ·
              数据每 30 秒自动刷新
            </p>
          </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">
              <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="wallboard.html">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="12" rx="2"/><path d="M8 20h8"/></svg>
              投屏
            </a>
            <a class="btn btn--primary" href="project.html">
              进入项目
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
            </a>
          </div>
        </section>

        <!-- KPI -->
        <section class="kpi-row">
          <div class="sd-kpi">
            <div class="sd-kpi__label">项目(可见范围)</div>
            <div class="sd-kpi__value"><span data-scope-projects>12</span><span class="sd-kpi__unit">个</span></div>
            <div class="sd-kpi__foot"><span class="pill pill--brand">可见 <span data-scope-projects>12</span></span><span><span data-scope-value data-full="12" data-limited="3">12</span> 位项目负责人</span></div>
          </div>
          <div class="sd-kpi sd-kpi--online">
            <div class="sd-kpi__label">在线应用</div>
            <div class="sd-kpi__value">
              <span data-scope-value data-full="34" data-limited="13">34</span>
              <span class="sd-kpi__unit">/ <span data-scope-value data-full="41" data-limited="13">41</span></span>
            </div>
            <div class="sd-kpi__foot"><span class="delta delta--good">▲ 2</span><span>较昨日 · 在线率 <span data-scope-value data-full="82.9" data-limited="100">82.9</span>%</span></div>
            <div class="sd-kpi__spark" data-spark="appSpark" data-spark-color="var(--sd-color-online)"></div>
          </div>
          <div class="sd-kpi sd-kpi--alert">
            <div class="sd-kpi__label">节点在线</div>
            <div class="sd-kpi__value">
              <span data-scope-value data-full="28" data-limited="20">28</span>
              <span class="sd-kpi__unit">/ <span data-scope-value data-full="32" data-limited="21">32</span></span>
            </div>
            <div class="sd-kpi__foot"><span class="pill pill--warn">告警 <span data-scope-value data-full="2" data-limited="0">2</span></span><span class="pill pill--offline">离线 <span data-scope-value data-full="2" data-limited="1">2</span></span></div>
            <div class="sd-kpi__spark" data-spark="nodeSpark" data-spark-color="var(--sd-color-alert)"></div>
          </div>
          <div class="sd-kpi">
            <div class="sd-kpi__label">今日请求量</div>
            <div class="sd-kpi__value"><span data-scope-value data-full="1286" data-limited="826">1286</span><span class="sd-kpi__unit">万</span></div>
            <div class="sd-kpi__foot"><span class="delta delta--good">▲ 8.4%</span><span>较昨日</span></div>
            <div class="sd-kpi__spark" data-spark="systemSpark"></div>
          </div>
          <div class="sd-kpi sd-kpi--accent">
            <div class="sd-kpi__label">P95 耗时</div>
            <div class="sd-kpi__value"><span data-scope-value data-full="214" data-limited="198">214</span><span class="sd-kpi__unit">ms</span></div>
            <div class="sd-kpi__foot"><span class="delta delta--good">▼ 6 ms</span><span>较昨日</span></div>
          </div>
          <div class="sd-kpi sd-kpi--offline">
            <div class="sd-kpi__label">错误率</div>
            <div class="sd-kpi__value"><span data-scope-value data-full="0.38" data-limited="0.29">0.38</span><span class="sd-kpi__unit">%</span></div>
            <div class="sd-kpi__foot"><span class="delta delta--bad">▲ 0.04%</span><span>活跃告警 <span data-scope-value data-full="7" data-limited="5">7</span> 条</span></div>
            <div class="sd-kpi__spark" data-spark="errorSpark" data-spark-color="var(--sd-color-offline)"></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="sd-card__actions">
                <div class="legend">
                  <span><i class="l-brand"></i>请求量 QPS</span>
                  <span><i class="l-warn"></i>错误率 %</span>
                </div>
              </div>
            </div>
            <div id="systemTrend" 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" id="healthRate">节点在线率 87.5%</span>
            </div>
            <div id="healthDonut" class="chart" style="height: 160px;"></div>
            <div id="healthList" class="event-list" style="max-height: 172px;"></div>
          </div>

          <!-- 项目健康矩阵 -->
          <div class="sd-card col-8">
            <div class="sd-card__head">
              <h2 class="sd-card__title">项目健康矩阵</h2>
              <a class="link" href="project.html">查看全部</a>
            </div>
            <div class="table-wrap table-wrap--scroll">
              <table class="sd-table sd-table--compact">
                <thead>
                  <tr>
                    <th>项目</th>
                    <th class="num">应用</th>
                    <th class="num">节点</th>
                    <th class="num">错误率</th>
                    <th class="num">告警</th>
                    <th>健康度</th>
                  </tr>
                </thead>
                <tbody id="projectMatrix"></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="topMetric">
                <button type="button" class="is-active" data-metric="qps">QPS</button>
                <button type="button" data-metric="errorRate">错误率</button>
                <button type="button" data-metric="p95">耗时</button>
              </div>
            </div>
            <div id="topAppsChart" 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">节点大小 = QPS · 点击下钻</span>
            </div>
            <div id="systemTopology" class="chart chart-md"></div>
          </div>

          <!-- 资源分布 -->
          <div class="sd-card col-16">
            <div class="sd-card__head">
              <h2 class="sd-card__title">机房资源水位</h2>
              <div class="legend">
                <span><i class="l-brand"></i>CPU</span>
                <span><i class="l-accent"></i>内存</span>
                <span><i class="l-warn"></i>磁盘</span>
              </div>
            </div>
            <div id="resourceChart" 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">告警 / 部署 / 上下线</span>
            </div>
            <div id="eventFeed" class="event-list" style="max-height: 260px;"></div>
          </div>

        </section>

        <footer class="page-foot">
          <span>数据来源:Nodes/Node · Registry/App · Registry/AppOnline · Monitors/AppMinuteStat · Monitors/AlarmRecord</span>
          <span>看板设计稿 v1.1 · 最后更新 <b data-clock>--:--:--</b></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;

  /* 项目健康矩阵:全量 12 个项目,受限视角自动隐藏未授权项 */
  const rows = SD.projects.map(function (p) {
    const offline = p.nodes - p.nodesOnline;
    const healthClass = p.health >= 95 ? 'meter--ok' : p.health >= 90 ? 'meter--warn' : 'meter--bad';
    return '<tr class="row-link" data-project="' + p.key + '" data-visible="' + p.visible + '" onclick="location.href=\'project.html\'">' +
      '<td class="strong">' + p.name + '</td>' +
      '<td class="num">' + p.appsOnline + '/' + p.apps + '</td>' +
      '<td class="num">' + p.nodesOnline + '/' + p.nodes + (offline ? ' <span class="pill pill--offline" style="padding:1px 6px">-' + offline + '</span>' : '') + '</td>' +
      '<td class="num ' + (p.errorRate > 0.4 ? 'strong' : '') + '">' + p.errorRate.toFixed(2) + '%</td>' +
      '<td class="num">' + (p.alerts ? '<span class="pill pill--warn">' + p.alerts + '</span>' : '<span class="muted">0</span>') + '</td>' +
      '<td><div class="meter ' + healthClass + '" style="width:74px"><i style="width:' + p.health + '%"></i></div></td>' +
      '</tr>';
  }).join('');
  document.getElementById('projectMatrix').innerHTML = rows;

  /* 集群健康环:随数据范围切换 */
  const limitedScope = SD.util.currentScope() === 'limited';
  const healthCount = limitedScope
    ? { online: 20, warn: 0, offline: 1 }
    : { online: 28, warn: 2, offline: 2 };
  const healthTotal = healthCount.online + healthCount.warn + healthCount.offline;
  const healthRate = (healthCount.online / healthTotal * 100).toFixed(1) + '%';
  document.getElementById('healthRate').textContent = '节点在线率 ' + healthRate;

  SDCharts.donut('healthDonut', {
    centerText: healthRate,
    centerSub: '节点在线率',
    data: [
      { name: '在线', value: healthCount.online, itemStyle: { color: SDCharts.palette.online } },
      { name: '告警', value: healthCount.warn, itemStyle: { color: SDCharts.palette.alert } },
      { name: '离线', value: healthCount.offline, itemStyle: { color: SDCharts.palette.offline } }
    ]
  });

  const healthItems = [
    { level: 'bad', title: 'GZ-App-01 节点离线', meta: '电商中台 · 10.0.4.31 · 42 分钟', time: '10:42' },
    { level: 'warn', title: 'SH-App-01 CPU 71%', meta: '电商中台 · 持续 10 分钟', time: '12:06' },
    { level: 'warn', title: 'NJ-DB-01 磁盘 72%', meta: '电商中台 · /data 分区', time: '08:51' }
  ];
  document.getElementById('healthList').innerHTML = healthItems.map(function (e) {
    return '<div class="event event--' + e.level + '">' +
      '<div class="event__bar"></div>' +
      '<div><div class="event__title">' + e.title + '</div><div class="event__meta">' + e.meta + '</div></div>' +
      '<div class="event__time">' + e.time + '</div></div>';
  }).join('');

  /* 全局吞吐趋势 */
  SDCharts.areaTrend('systemTrend', {
    categories: SD.hours,
    interval: 3,
    primary: { name: '请求量', data: SD.series.systemThroughput, color: SDCharts.palette.brand, unit: 'QPS' },
    secondary: {
      name: '错误率', data: SD.series.systemError, color: SDCharts.palette.alert,
      unit: '%', formatter: '{value}%'
    }
  });

  /* Top 应用:QPS / 错误率 / 耗时切换 */
  const metricConf = {
    qps: { label: 'QPS', unit: '', format: function (v) { return fmt(v); } },
    errorRate: { label: '错误率', unit: '%', format: function (v) { return v.toFixed(2) + '%'; } },
    p95: { label: 'P95', unit: 'ms', format: function (v) { return v + 'ms'; } }
  };
  function renderTop(metric) {
    const conf = metricConf[metric];
    const items = SD.apps.slice().sort(function (a, b) { return b[metric] - a[metric]; }).slice(0, 6).map(function (a) {
      return {
        name: a.name,
        value: metric === 'errorRate' ? Number(a.errorRate.toFixed(2)) : a[metric],
        color: metric === 'errorRate' ? undefined : undefined
      };
    });
    SDCharts.barH('topAppsChart', { items: items, unit: conf.unit });
  }
  renderTop('qps');
  document.getElementById('topMetric').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');
    renderTop(btn.dataset.metric);
  });

  /* 服务拓扑 */
  SDCharts.graph('systemTopology', {
    nodes: SD.systemTopology.nodes,
    links: SD.systemTopology.links,
    unit: ' QPS',
    zoom: 0.86
  });

  /* 机房资源水位 */
  SDCharts.stackedBar('resourceChart', {
    stacked: false,
    barWidth: 12,
    categories: SD.locations.map(function (l) { return l.name + ' · ' + l.nodes + ' 节点'; }),
    series: [
      { name: 'CPU', color: SDCharts.palette.brand, data: SD.locations.map(function (l) { return l.cpu; }) },
      { name: '内存', color: SDCharts.palette.accent, data: SD.locations.map(function (l) { return l.memory; }) },
      { name: '磁盘', color: SDCharts.palette.alert, data: SD.locations.map(function (l) { return l.disk; }) }
    ]
  });

  /* 实时事件 */
  document.getElementById('eventFeed').innerHTML = SD.events.map(function (e) {
    return '<div class="event event--' + e.level + '">' +
      '<div class="event__bar"></div>' +
      '<div><div class="event__title"><b>' + e.title + '</b></div><div class="event__meta">' + e.meta + '</div></div>' +
      '<div class="event__time">' + e.time + '</div></div>';
  }).join('');
})();
</script>
</body>
</html>