web: draw isolated points as a connected series and mark unrecorded time in the adaptive audit [CI]
ci/woodpecker/push/web-build Pipeline was successful
ci/woodpecker/push/verify Pipeline was successful
ci/woodpecker/push/dev-backend-image Pipeline was canceled
ci/woodpecker/push/dev-image Pipeline was canceled

This commit is contained in:
justme committed 2026-10-22 10:57:53 +08:00
1 parent 93abf59afb
commit 4e7a8c2ba3
3 files changed
+23 -5

No files matched your search

+5 -2
View File
@@ -17,12 +17,12 @@ const STATES = [
{ key: 'quiet', label: '平稳', level: 'ok' },
{ key: 'watch', label: '观察', level: 'warn' },
{ key: 'active', label: '活跃', level: 'bad' },
{ key: 'dead', label: '不通', level: 'none' },
{ key: 'dead', label: '不通', level: 'dead' },
] as const
type StateKey = (typeof STATES)[number]['key']
const KIND_NAMES: Record<string, string> = { quic_ping: 'QUIC 往返', tcp_ping: 'TCP 往返', tcp_connect: 'TCP 建连', http_request: 'HTTP 请求' }
const STATE_LABEL: Record<string, string> = { quiet: '平稳', watch: '观察', active: '活跃', dead: '不通' }
const STATE_LABEL: Record<string, string> = { start: '启动', quiet: '平稳', watch: '观察', active: '活跃', dead: '不通' }
const secondsOf = (b: AdaptiveBucketDto, state: StateKey) => b[`${state}_seconds` as const]
@@ -141,6 +141,9 @@ export function AdaptiveAudit({ lineId }: { lineId: string }) {
<i className={`bar ${s.level}`} /> {s.label}
</span>
))}
<span>
<i className="bar none" /> 无记录
</span>
</div>
{data.kinds.map((k) => (
<Row key={k.kind} kind={k.kind} buckets={k.buckets} step={info.step} />
+6
View File
@@ -2752,3 +2752,9 @@ input {
color: var(--app-ink-muted);
font-size: 13px;
}
/* 自适应审计里「不通」:比「没有记录」(灰、半透明)深 */
.bar.dead {
background: var(--app-ink-muted);
opacity: 1;
}
+12 -3
View File
@@ -40,10 +40,19 @@ export function fmtOffset(ms: number): string {
return h > 0 ? `${h}:${two(m)}${sec ? ':' + two(sec) : ''}` : `${m}:${two(sec)}`
}
/** 有值的格子不到三分之一(而且至少有一个)算稀疏。 */
/**
* 稀疏:有值的点里,前后都是空的(孤立的)占三成以上。每 10 秒才一次的建连放在 5 秒的格子里就是隔一格有一格没有,
* 每个点都是孤立的,连不成线,图上什么都看不见;这种序列画出点,并把点连起来。
*/
function isSparse(values: (number | null)[]): boolean {
const filled = values.filter((v) => v !== null).length
return filled > 0 && filled < values.length / 3
let filled = 0
let isolated = 0
for (let i = 0; i < values.length; i++) {
if (values[i] === null) continue
filled++
if ((i === 0 || values[i - 1] === null) && (i === values.length - 1 || values[i + 1] === null)) isolated++
}
return filled > 0 && isolated / filled >= 0.3
}
/**