web: group the line card rows by direction so each agent pair is named once

This commit is contained in:
justme committed 2026-10-21 16:31:22 +08:00
1 parent cfe6571db5
commit 35207a70ea
2 files changed
+51 -71

No files matched your search

+27 -53
View File
@@ -62,68 +62,42 @@ export function LineStrips({ lineId, dialer, listener, range, m }: Props) {
const lossDown = col('loss.down')
const retransUp = col('tcp.retrans.up')
const retransDown = col('tcp.retrans.down')
const up = m['loss.up']
const down = m['loss.down']
const upText = up === null || up === undefined ? '丢包 —' : `${fmtPct(up)} 丢包`
const downText = down === null || down === undefined ? '丢包 —' : `${fmtPct(down)} 丢包`
const retransUpValue = m['tcp.retrans.up']
const retransDownValue = m['tcp.retrans.down']
const lossText = (v: number | null | undefined) => (v === null || v === undefined ? '丢包 —' : `${fmtPct(v)} 丢包`)
const retransText = (v: number | null | undefined) => (v === null || v === undefined ? '重传 —' : `${fmtPct(v)} 重传`)
const cellsOf = (values: (number | null)[]) => (s ? values : Array.from({ length: common.cells }, () => null))
// 一个方向下的一行:协议名、数值徽标和质量点阵。
const row = (id: string, proto: string, label: string, value: number | null | undefined, text: string, values: (number | null)[]) => (
<div className="line-mini-row" key={id}>
<div className="line-mini-direction-head" title={`${proto} ${label}`}>
<span>{proto}</span>
<span className={`line-mini-badge ${levelOf(value)}`}>{text}</span>
</div>
<div className="bars" role="img" aria-label={`近 ${common.hoursText}每 ${common.stepText}的 ${proto} ${label}`}>
{cellsOf(values).map((v, i) => (
<span key={`${id}-${t[i] ?? i}`} className={`bar ${s ? levelOf(v) : 'none'}${hover === i ? ' on' : ''}`} />
))}
</div>
</div>
)
return (
<div className="strips" ref={box} onMouseMove={onMove} onMouseLeave={() => setHover(null)}>
<div className="line-mini-stack">
<div className="line-mini-quic-head" title={`QUIC RTT p50 ${fmtMs(m['rtt.p50'])} · p95 ${fmtMs(m['rtt.p95'])} · p99 ${fmtMs(m['rtt.p99'])}`}>
<span>QUIC RTT ⇄</span><strong className="num">{fmtMs(m['rtt.p50'])}</strong><small>悬停看 p95 / p99</small>
<div className="line-mini-quic-head" title={`RTT 是往返。QUIC p50 ${fmtMs(m['rtt.p50'])} · p95 ${fmtMs(m['rtt.p95'])} · p99 ${fmtMs(m['rtt.p99'])};TCP p50 ${fmtMs(m['tcp.rtt.p50'])}`}>
<span>RTT ⇄</span>
<span className="line-mini-rtt">QUIC <strong className="num">{fmtMs(m['rtt.p50'])}</strong></span>
<span className="line-mini-rtt">TCP <strong className="num">{fmtMs(m['tcp.rtt.p50'])}</strong></span>
<small>悬停看 p95 / p99</small>
{series.error ? <span className="line-mini-badge bad" title={`上一次取数失败:${series.error.message}`}>数据未更新</span> : null}
</div>
<div className="line-mini-direction">
<div className="line-mini-direction-head" title={`去程 拨号方 → 监听方 丢包 ${fmtPct(up)}`}>
<span className="line-mini-direction-name" title={`去程:${dialer} → ${listener}`}>{dialer} → {listener}</span>
<span className={`line-mini-badge ${levelOf(up)}`}>{upText}</span>
</div>
<div className="bars" role="img" aria-label={`近 ${common.hoursText}每 ${common.stepText}的去程丢包`}>
{(s ? lossUp : Array.from({ length: common.cells }, () => null)).map((value, i) => (
<span key={`up-${t[i] ?? i}`} className={`bar ${s ? levelOf(value) : 'none'}${hover === i ? ' on' : ''}`} />
))}
</div>
<div className="line-mini-route" title={`去程:${dialer} → ${listener}`}>{dialer} → {listener}</div>
{row('up-quic', 'QUIC', '去程丢包', m['loss.up'], lossText(m['loss.up']), lossUp)}
{row('up-tcp', 'TCP', '去程重传', m['tcp.retrans.up'], retransText(m['tcp.retrans.up']), retransUp)}
</div>
<div className="line-mini-direction">
<div className="line-mini-direction-head" title={`回程 监听方 → 拨号方 丢包 ${fmtPct(down)}`}>
<span className="line-mini-direction-name" title={`回程:${listener} → ${dialer}`}>{listener} → {dialer}</span>
<span className={`line-mini-badge ${levelOf(down)}`}>{downText}</span>
</div>
<div className="bars" role="img" aria-label={`近 ${common.hoursText}每 ${common.stepText}的回程丢包`}>
{(s ? lossDown : Array.from({ length: common.cells }, () => null)).map((value, i) => (
<span key={`down-${t[i] ?? i}`} className={`bar ${s ? levelOf(value) : 'none'}${hover === i ? ' on' : ''}`} />
))}
</div>
</div>
<div className="line-mini-tcp">
<div className="line-mini-tcp-head" title={`TCP RTT p50 ${fmtMs(m['tcp.rtt.p50'])}`}>
<span>TCP RTT ⇄</span><strong className="num">{fmtMs(m['tcp.rtt.p50'])}</strong>
</div>
</div>
<div className="line-mini-direction">
<div className="line-mini-direction-head" title={`TCP 去程 拨号方 → 监听方 重传 ${fmtPct(retransUpValue)}`}>
<span className="line-mini-direction-name" title={`去程:${dialer} → ${listener}`}>{dialer} → {listener}</span>
<span className={`line-mini-badge ${levelOf(retransUpValue)}`}>{retransText(retransUpValue)}</span>
</div>
<div className="bars" role="img" aria-label={`近 ${common.hoursText}每 ${common.stepText}的 TCP 去程重传`}>
{(s ? retransUp : Array.from({ length: common.cells }, () => null)).map((value, i) => (
<span key={`retrans-up-${t[i] ?? i}`} className={`bar ${s ? levelOf(value) : 'none'}${hover === i ? ' on' : ''}`} />
))}
</div>
</div>
<div className="line-mini-direction">
<div className="line-mini-direction-head" title={`TCP 回程 监听方 → 拨号方 重传 ${fmtPct(retransDownValue)}`}>
<span className="line-mini-direction-name" title={`回程:${listener} → ${dialer}`}>{listener} → {dialer}</span>
<span className={`line-mini-badge ${levelOf(retransDownValue)}`}>{retransText(retransDownValue)}</span>
</div>
<div className="bars" role="img" aria-label={`近 ${common.hoursText}每 ${common.stepText}的 TCP 回程重传`}>
{(s ? retransDown : Array.from({ length: common.cells }, () => null)).map((value, i) => (
<span key={`retrans-down-${t[i] ?? i}`} className={`bar ${s ? levelOf(value) : 'none'}${hover === i ? ' on' : ''}`} />
))}
</div>
<div className="line-mini-route" title={`回程:${listener} → ${dialer}`}>{listener} → {dialer}</div>
{row('down-quic', 'QUIC', '回程丢包', m['loss.down'], lossText(m['loss.down']), lossDown)}
{row('down-tcp', 'TCP', '回程重传', m['tcp.retrans.down'], retransText(m['tcp.retrans.down']), retransDown)}
</div>
</div>
<div className={`pop num${hover !== null && at !== null ? ' open' : ''}`} role="tooltip" aria-hidden={hover === null}>
+24 -18
View File
@@ -1388,15 +1388,13 @@
}
.line-mini-quic-head > span,
.line-mini-direction-head > span:first-child,
.line-mini-tcp-head > span:first-child {
.line-mini-direction-head > span:first-child {
color: var(--app-ink-muted);
font-size: 10px;
font-weight: 600;
}
.line-mini-quic-head > strong,
.line-mini-tcp-head > strong {
.line-mini-quic-head strong {
color: var(--app-ink);
font-size: 14px;
font-weight: 700;
@@ -1410,15 +1408,13 @@
white-space: nowrap;
}
.line-mini-direction,
.line-mini-tcp {
.line-mini-direction {
display: grid;
gap: 3px;
min-width: 0;
}
.line-mini-direction-head,
.line-mini-tcp-head {
.line-mini-direction-head {
display: flex;
align-items: baseline;
gap: 6px;
@@ -1426,33 +1422,43 @@
line-height: 1.25;
}
.line-mini-direction-head > span:first-child,
.line-mini-tcp-head > span:first-child {
.line-mini-direction-head > span:first-child {
font-weight: 500;
white-space: nowrap;
}
.line-mini-direction-head .line-mini-badge,
.line-mini-tcp-head .line-mini-badge {
.line-mini-direction-head .line-mini-badge {
margin-left: auto;
}
.line-mini-direction-head > .line-mini-direction-name {
.line-mini-route {
min-width: 0;
overflow: hidden;
color: var(--app-ink);
font-size: 11px;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.line-mini-tcp {
.line-mini-row {
display: grid;
gap: 3px;
min-width: 0;
}
.line-mini-rtt {
display: inline-flex;
align-items: baseline;
gap: 4px;
}
.line-mini-direction + .line-mini-direction {
margin-top: 2px;
padding-top: 5px;
border-top: 1px solid var(--app-outline-soft);
}
.line-mini-tcp-head > strong {
font-size: 12px;
}
.line-mini-badge {
display: inline-flex;
align-items: center;