[CI] Keep line cards visible and time windows current; add 24h to 96h ranges
This commit is contained in:
1 parent
a7ebf61551
commit
cc64df3e70
3 files changed
+15
-27
No files matched your search
@@ -9,6 +9,9 @@ export const OVERVIEW_RANGES = [
|
||||
{ value: '3h', label: '近 3 小时', hours: 3, step: '5m', stepMin: 5 },
|
||||
{ value: '6h', label: '近 6 小时', hours: 6, step: '10m', stepMin: 10 },
|
||||
{ value: '12h', label: '近 12 小时', hours: 12, step: '15m', stepMin: 15 },
|
||||
{ value: '24h', label: '近 24 小时', hours: 24, step: '30m', stepMin: 30 },
|
||||
{ value: '48h', label: '近 48 小时', hours: 48, step: '1h', stepMin: 60 },
|
||||
{ value: '96h', label: '近 96 小时', hours: 96, step: '2h', stepMin: 120 },
|
||||
] as const
|
||||
|
||||
export type OverviewRange = (typeof OVERVIEW_RANGES)[number]['value']
|
||||
@@ -17,12 +20,12 @@ type Level = 'ok' | 'warn' | 'bad' | 'none'
|
||||
|
||||
const levelOf = (loss: number | null | undefined): Level => (loss === null || loss === undefined ? 'none' : loss <= 0.5 ? 'ok' : loss < 3 ? 'warn' : 'bad')
|
||||
|
||||
const clock = (ms: number) => new Date(ms).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false })
|
||||
const clock = (ms: number, showDate: boolean) => new Date(ms).toLocaleString('zh-CN', { ...(showDate ? { month: '2-digit', day: '2-digit' } : {}), hour: '2-digit', minute: '2-digit', hour12: false })
|
||||
|
||||
interface Props {
|
||||
lineId: string
|
||||
range: OverviewRange
|
||||
/** 近 3 小时的汇总(左上、右上的数字)。 */
|
||||
/** 所选时间范围的汇总(左上、右上的数字)。 */
|
||||
m: Record<string, number | null | undefined>
|
||||
}
|
||||
|
||||
@@ -30,7 +33,7 @@ interface Props {
|
||||
export function LineStrips({ lineId, range, m }: Props) {
|
||||
const info = OVERVIEW_RANGES.find((r) => r.value === range) ?? OVERVIEW_RANGES[0]
|
||||
const series = useQuery(
|
||||
() => api.get<SeriesDto>(`/api/public/lines/${encodeURIComponent(lineId)}/series`, { range: info.value, anchor: 'latest', step: info.step, preset: 'quic,tcp', metrics: 'loss.up,loss.down,loss.total,rtt.p50,rtt.p95,rtt.p99,tcp.retrans,tcp.rtt.p50' }),
|
||||
() => api.get<SeriesDto>(`/api/public/lines/${encodeURIComponent(lineId)}/series`, { range: info.value, anchor: 'now', step: info.step, preset: 'quic,tcp', metrics: 'loss.up,loss.down,loss.total,rtt.p50,rtt.p95,rtt.p99,tcp.retrans,tcp.rtt.p50' }),
|
||||
[lineId, info.value],
|
||||
30_000,
|
||||
)
|
||||
@@ -50,7 +53,7 @@ export function LineStrips({ lineId, range, m }: Props) {
|
||||
const r = bars.getBoundingClientRect()
|
||||
setHover(Math.min(t.length - 1, Math.max(0, Math.floor(((e.clientX - r.left) / r.width) * t.length))))
|
||||
}
|
||||
const common = { t, ready: Boolean(s), cells: Math.round((info.hours * 60) / info.stepMin), stepText: `${info.stepMin} 分钟`, hoursText: `${info.hours} 小时`, hover }
|
||||
const common = { t, ready: Boolean(s), cells: Math.round((info.hours * 60) / info.stepMin), stepText: info.stepMin >= 60 ? `${info.stepMin / 60} 小时` : `${info.stepMin} 分钟`, hoursText: `${info.hours} 小时`, hover }
|
||||
const at = shown !== null && t[shown] !== undefined ? shown : null
|
||||
const lossUp = col('loss.up')
|
||||
const lossDown = col('loss.down')
|
||||
@@ -62,12 +65,9 @@ export function LineStrips({ lineId, range, m }: Props) {
|
||||
const retransValue = m['tcp.retrans']
|
||||
const retransTone = levelOf(retransValue)
|
||||
const retransText = retransValue === null || retransValue === undefined ? '重传 —' : `${fmtPct(retransValue)} 重传`
|
||||
const hasMeasurements = Object.values(s?.values ?? {}).some((values) => values.some((value) => value !== null && value !== undefined))
|
||||
return (
|
||||
<div className="strips" ref={box} onMouseMove={onMove} onMouseLeave={() => setHover(null)}>
|
||||
{s && !hasMeasurements ? (
|
||||
<div className="line-no-data">近 {info.hours} 小时暂无测量数据</div>
|
||||
) : <div className="line-mini-stack">
|
||||
<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</span><strong className="num">{fmtMs(m['rtt.p50'])}</strong><small>悬停看 p95 / p99</small>
|
||||
{series.error ? <span className="line-mini-badge bad" title={`上一次取数失败:${series.error.message}`}>数据未更新</span> : null}
|
||||
@@ -105,12 +105,12 @@ export function LineStrips({ lineId, range, m }: Props) {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
<div className={`pop num${hover !== null && at !== null ? ' open' : ''}`} role="tooltip" aria-hidden={hover === null}>
|
||||
{at !== null ? (
|
||||
<>
|
||||
<b className="pop-time">
|
||||
{clock(t[at])}–{clock(t[at] + info.stepMin * 60_000)}
|
||||
{clock(t[at], info.hours >= 24)}–{clock(t[at] + info.stepMin * 60_000, info.hours >= 24)}
|
||||
</b>
|
||||
<div className="pop-group">
|
||||
<span className="pop-name">QUIC Datagram</span>
|
||||
|
||||
@@ -1839,14 +1839,8 @@
|
||||
background: color-mix(in srgb, var(--app-ink) 7%, transparent);
|
||||
}
|
||||
|
||||
.agent-card .line-link.disconnected,
|
||||
.agent-card .line-link.disconnected:hover {
|
||||
filter: grayscale(1);
|
||||
opacity: 0.62;
|
||||
}
|
||||
|
||||
.line-range.disconnected-label {
|
||||
color: var(--app-ink-faint);
|
||||
.line-range.connection-issue {
|
||||
color: var(--app-warn);
|
||||
}
|
||||
|
||||
|
||||
@@ -2019,12 +2013,6 @@ input {
|
||||
color: var(--app-ink-faint);
|
||||
}
|
||||
|
||||
.line-no-data {
|
||||
padding: 8px 2px 2px;
|
||||
color: var(--app-ink-faint);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 线路面板:按三网分组 */
|
||||
.carrier-group + .carrier-group {
|
||||
margin-top: 18px;
|
||||
|
||||
@@ -152,7 +152,7 @@ function LineCard({ l, range, infos }: { l: OverviewLineDto; range: OverviewRang
|
||||
? 'QUIC 正在连接'
|
||||
: null
|
||||
return (
|
||||
<Link to={`/line/${encodeURIComponent(l.id)}`} className={`line-link line-card${connectionIssue ? ' disconnected' : ''}`}>
|
||||
<Link to={`/line/${encodeURIComponent(l.id)}`} className="line-link line-card">
|
||||
<div className="line-top">
|
||||
<span className="line-name" title={[l.probe_name, probeInfo?.tag || probeInfo?.version].filter(Boolean).join(' · ')}>
|
||||
{probeInfo?.country ? <Flag code={probeInfo.country} /> : null}
|
||||
@@ -163,7 +163,7 @@ function LineCard({ l, range, infos }: { l: OverviewLineDto; range: OverviewRang
|
||||
<span className="name">{l.name}</span>
|
||||
</span>
|
||||
<span
|
||||
className={connectionIssue ? 'line-range disconnected-label' : 'faint line-range'}
|
||||
className={connectionIssue ? 'line-range connection-issue' : 'faint line-range'}
|
||||
title={l.quic_health?.state === 'retry_wait' ? `Probe 正在等待退避重试${l.quic_health.retry_in_ms ? `(约 ${Math.ceil(l.quic_health.retry_in_ms / 1000)} 秒)` : ''}。` : !l.probe_connected && l.online ? 'Agent 在线,但 Probe 尚未建立测量会话。' : undefined}
|
||||
>
|
||||
{connectionIssue ? `${connectionIssue} · ${rangeLabel}` : rangeLabel}
|
||||
@@ -179,7 +179,7 @@ export function Overview() {
|
||||
const [sort, setSort] = useState<SortKey>('name')
|
||||
const { hosts, live } = useHosts()
|
||||
const infos = useNodeInfos()
|
||||
// 近多久的汇总和走势:3 / 6 / 12 小时,记在链接里(?range=6h)
|
||||
// 汇总和走势使用同一个时间范围,记在链接里(例如 ?range=48h)。
|
||||
const [params, setParams] = useSearchParams()
|
||||
const ipVersion = ipVersionOf(params.get('ip_version'))
|
||||
const ipv6 = useQuery(() => ipVersion === 'ipv6' ? api.get<Page<AgentIpv6Dto>>('/api/public/ipv6') : Promise.resolve(undefined), [ipVersion], 15_000)
|
||||
|
||||
Reference in new issue
Block a user