feat: P1 收尾 —— 事件面板区块与 90 天响应时间趋势
事件时间线(面板侧):
- 监控列表下方「最近事件」区块(最近 20 条:时间/类型/目标/详情),
无事件自动隐藏;维护窗口内事件带「维护」标记;窄屏隐藏详情列
响应时间趋势(P1-2):
- 当日分桶在 {total,up} 外累计 latSum/latCount/latMax(仅在线样本)
- 快照输出 lat: { avg, max, days:[{key,avg}] }
- 卡片视图:可用率条下方新增「90 天日均延迟」细柱图(最近 30 天 + 均值)
- 表格视图:可用率列 tooltip 追加「90 天日均延迟 X ms(峰值 Y ms)」
- --demo 同步生成 90 天延迟样例(含起伏),静态部署也能看到效果
验证:verify-views 新增 [J] 段 5 条断言(趋势条/30 格/tooltip/事件空态/无溢出),
六套合计 159 项全通过(17+35+26+20+39+22)。
至此 P0(通知、证书)与 P1(事件时间线、维护窗口、响应时间趋势)全部落地。
This commit is contained in:
+51
-7
@@ -1,4 +1,4 @@
|
||||
const VERSION = '0.10.0';
|
||||
const VERSION = '0.11.0';
|
||||
const HB_LIMIT = 48;
|
||||
const UPTIME_DAYS = 90;
|
||||
const BROWSER_CONCURRENCY = 6;
|
||||
@@ -226,9 +226,47 @@ function uptimeSummary(st) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 90 天日均延迟趋势(卡片视图):取最近 30 天的日均延迟画成细柱,右侧给 90 天均值
|
||||
function latTrendBar(lat) {
|
||||
const days = lat?.days?.slice(-30) ?? [];
|
||||
if (days.length < 2) return '';
|
||||
const max = Math.max(...days.map((d) => d.avg), 1);
|
||||
const bars = days.map((d) => {
|
||||
const h = Math.max(10, Math.round((d.avg / max) * 100));
|
||||
return `<span class="lat-cell" style="height:${h}%" title="${escapeHtml(d.key)}:日均 ${d.avg} ms"></span>`;
|
||||
}).join('');
|
||||
return `<div class="lat-trend" title="90 天日均延迟 ${lat.avg} ms · 峰值 ${lat.max} ms(数据来自当日分桶累计)">
|
||||
<span class="lat-label">延迟</span>
|
||||
<span class="lat-bars">${bars}</span>
|
||||
<span class="lat-avg">${lat.avg} ms</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// 最近事件区块(故障 / 恢复 / 证书阈值 / 维护窗口内的事件)
|
||||
function renderEvents() {
|
||||
const box = $('events');
|
||||
if (!box) return;
|
||||
const list = $('events-list');
|
||||
const empty = $('events-empty');
|
||||
const events = (state.status?.events ?? []).slice(0, 20);
|
||||
box.hidden = events.length === 0;
|
||||
empty.hidden = events.length > 0;
|
||||
const count = $('events-count');
|
||||
if (count) count.textContent = events.length ? `最近 ${events.length} 条` : '';
|
||||
const label = { down: '故障', up: '恢复', tls: '证书', maintenance: '维护' };
|
||||
list.innerHTML = events.map((e) => {
|
||||
const maint = e.maintenance ? '<span class="ev-maint" title="维护窗口内,未计入可用率、未发送告警">维护</span>' : '';
|
||||
return `<li class="ev ev-${escapeHtml(e.type)}">
|
||||
<span class="ev-time" title="${escapeHtml(fmtTime(e.t))}">${escapeHtml(fmtRelative(e.t))}</span>
|
||||
<span class="ev-type">${escapeHtml(label[e.type] ?? e.type)}${maint}</span>
|
||||
<span class="ev-name" title="${escapeHtml(e.name)}">${escapeHtml(e.name)}</span>
|
||||
<span class="ev-detail" title="${escapeHtml(e.detail ?? '')}">${escapeHtml(e.detail ?? '')}</span>
|
||||
</li>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function uptimeBar(st) {
|
||||
const daily = st?.daily;
|
||||
const cells = lastNDays(UPTIME_DAYS).map((key) => {
|
||||
const daily = st?.daily; const cells = lastNDays(UPTIME_DAYS).map((key) => {
|
||||
const b = daily?.[key];
|
||||
let cls = 'lv-none';
|
||||
let tip = `${key} · 无数据`;
|
||||
@@ -309,14 +347,14 @@ function renderCard(m) {
|
||||
hb.innerHTML = heartbeatSvg(st?.history);
|
||||
|
||||
const up = document.createElement('div');
|
||||
up.innerHTML = uptimeBar(st);
|
||||
up.innerHTML = `${uptimeBar(st)}${latTrendBar(st?.lat)}`;
|
||||
|
||||
const detail = document.createElement('div');
|
||||
detail.className = 'card-detail';
|
||||
detail.title = st?.detail ?? '';
|
||||
detail.textContent = st?.detail ?? '等待检测';
|
||||
|
||||
card.append(head, target, metrics, hb, up.firstElementChild ?? document.createElement('div'), detail);
|
||||
card.append(head, target, metrics, hb, up, detail);
|
||||
return card;
|
||||
}
|
||||
|
||||
@@ -493,8 +531,13 @@ function tableRow(m, hide) {
|
||||
`<td class="t-num">${st?.latency != null ? `${st.latency} ms` : '–'}</td>` +
|
||||
`<td class="t-num">${escapeHtml(resultText(st, m))}</td>` +
|
||||
`<td class="t-num" title="${escapeHtml(fmtTime(st?.lastCheck))}">${fmtRelative(st?.lastCheck)}</td>` +
|
||||
`<td class="t-hb">${heartbeatSvg(st?.history)}</td>` +
|
||||
`<td class="t-uptime">${up ? `<span class="u-mini">${miniUptimeBar(st?.daily)}</span><span class="t-pct">${up.pct.toFixed(1)}%</span>` : '<span class="t-pct">–</span>'}</td>`;
|
||||
`<td class="t-hb">${heartbeatSvg(st?.history)}</td>`;
|
||||
const latTip = st?.lat ? ` · 90 天日均延迟 ${st.lat.avg} ms(峰值 ${st.lat.max} ms)` : '';
|
||||
const uptimeCell = document.createElement('td');
|
||||
uptimeCell.className = 't-uptime';
|
||||
uptimeCell.title = `${up ? `可用率 ${up.pct.toFixed(1)}%` : '可用率数据积累中'}${latTip}`;
|
||||
uptimeCell.innerHTML = up ? `<span class="u-mini">${miniUptimeBar(st?.daily)}</span><span class="t-pct">${up.pct.toFixed(1)}%</span>` : '<span class="t-pct">–</span>';
|
||||
tr.appendChild(uptimeCell);
|
||||
return tr;
|
||||
}
|
||||
|
||||
@@ -661,6 +704,7 @@ function renderAll() {
|
||||
renderTabs();
|
||||
renderViewSwitch();
|
||||
renderView();
|
||||
renderEvents();
|
||||
}
|
||||
|
||||
function renderError(err) {
|
||||
|
||||
Reference in New Issue
Block a user