Skip to content

Commit 6636f80

Browse files
roll product back to v2.2; add hover time marker on the x axis (only change)
1 parent 719efe8 commit 6636f80

2 files changed

Lines changed: 65 additions & 95 deletions

File tree

.github/FUNDING.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
github: githubflyideas
2+
# 爱发电开通后取消注释:
3+
# custom: ["https://afdian.com/a/githubflyideas"]

static/index.html

Lines changed: 62 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -7,78 +7,64 @@
77
<title>pingping — link quality oscilloscope</title>
88
<style>
99
:root {
10-
--bg:#0a0c0e; --panel:#12171a; --screen:#07090a; --line:#1f262b;
11-
--phos:#7ce38b; --amber:#e8b44a; --alert:#ff5c57; --ink:#cdd6cf; --dim:#5f6f64;
12-
--grid-axis:#2a332c; --grid-split:#141d16;
13-
--smoke:rgba(124,227,139,.16); --lossbar:rgba(255,92,87,.35); --glow:6;
14-
--smoke-hot:#eafff0; --smoke-mid:#57d573; --smoke-cool:#7d6a2e;
15-
--shadow:0 1px 2px rgba(0,0,0,.5); --shadow-lg:0 10px 30px rgba(0,0,0,.45);
16-
--sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans", sans-serif;
17-
--mono: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, "Noto Sans Mono", monospace;
10+
--bg:#0a0c0e; --panel:#101613; --screen:#060808; --line:#1e242a;
11+
--phos:#7ce38b; --amber:#e8b44a; --alert:#ff5c57; --ink:#c7d0c9; --dim:#5c6b60;
12+
--grid-axis:#2a332c; --grid-split:#10281a;
13+
--smoke:rgba(124,227,139,.16); --lossbar:rgba(255,92,87,.55); --glow:6;
14+
--smoke-hot:#d8ffe2; --smoke-mid:#7ce38b; --smoke-cool:#8a7a3a;
15+
--mono: ui-monospace,"SF Mono","Cascadia Mono",Consolas,"Noto Sans Mono",monospace;
1816
}
17+
/* Daylight: warm paper — the trace printed on lab graph paper */
1918
html[data-theme="daylight"] {
20-
--bg:#f6f3ea; --panel:#fffdf6; --screen:#fffef9; --line:#e6e0cd;
21-
--phos:#136c40; --amber:#946400; --alert:#c23a32; --ink:#292d27; --dim:#8b8a78;
22-
--grid-axis:#d3cdb6; --grid-split:#efe9d7;
23-
--smoke:rgba(23,114,69,.20); --lossbar:rgba(194,58,50,.30); --glow:0;
24-
--smoke-hot:#0a4f2d; --smoke-mid:#2f9e63; --smoke-cool:#b08a2e;
25-
--shadow:0 1px 3px rgba(80,70,40,.10), 0 1px 2px rgba(80,70,40,.06);
26-
--shadow-lg:0 12px 32px rgba(80,70,40,.14);
19+
--bg:#f4f1e8; --panel:#faf8f1; --screen:#fffef9; --line:#e0dbc9;
20+
--phos:#177245; --amber:#986a00; --alert:#c23a32; --ink:#2b2f28; --dim:#8b8a7a;
21+
--grid-axis:#cfc9b4; --grid-split:#ebe6d4;
22+
--smoke:rgba(23,114,69,.20); --lossbar:rgba(194,58,50,.5); --glow:0;
23+
--smoke-hot:#0e5e37; --smoke-mid:#2f9e63; --smoke-cool:#b08a2e;
2724
}
25+
/* Arctic: cool morning light — teal trace on frosted glass */
2826
html[data-theme="arctic"] {
29-
--bg:#eef2f6; --panel:#ffffff; --screen:#fbfdfe; --line:#dde5ec;
30-
--phos:#0b7285; --amber:#b45309; --alert:#d1433f; --ink:#22303c; --dim:#7f8f9d;
31-
--grid-axis:#c9d5e0; --grid-split:#e7edf3;
32-
--smoke:rgba(11,114,133,.18); --lossbar:rgba(209,67,63,.30); --glow:0;
33-
--smoke-hot:#043b46; --smoke-mid:#1596ad; --smoke-cool:#b3722f;
34-
--shadow:0 1px 3px rgba(30,50,70,.10), 0 1px 2px rgba(30,50,70,.06);
35-
--shadow-lg:0 12px 32px rgba(30,50,70,.14);
27+
--bg:#edf1f5; --panel:#f6f9fb; --screen:#fbfdfe; --line:#d7e0e8;
28+
--phos:#0b7285; --amber:#b45309; --alert:#d1433f; --ink:#24313d; --dim:#7f8f9d;
29+
--grid-axis:#c3d0db; --grid-split:#e3ebf2;
30+
--smoke:rgba(11,114,133,.18); --lossbar:rgba(209,67,63,.5); --glow:0;
31+
--smoke-hot:#064a57; --smoke-mid:#1596ad; --smoke-cool:#b3722f;
3632
}
3733
* { box-sizing:border-box; margin:0; }
38-
body { background:var(--bg); color:var(--ink); font-family:var(--sans);
39-
padding:24px clamp(16px, 3vw, 40px); font-size:14px; line-height:1.5;
40-
-webkit-font-smoothing:antialiased; }
41-
header { display:flex; align-items:center; gap:16px; margin-bottom:20px; flex-wrap:wrap; }
42-
.mark { color:var(--phos); font-weight:700; font-size:21px; letter-spacing:.04em; font-family:var(--mono); }
43-
.tag { color:var(--dim); font-size:13px; font-weight:500; letter-spacing:.01em; }
44-
.row { display:flex; gap:12px; align-items:center; margin-bottom:16px; flex-wrap:wrap; }
34+
body { background:var(--bg); color:var(--ink); font-family:var(--mono); padding:18px 22px; }
35+
header { display:flex; align-items:baseline; gap:14px; margin-bottom:14px; flex-wrap:wrap; }
36+
.mark { color:var(--phos); font-weight:700; font-size:20px; letter-spacing:.05em; }
37+
.tag { color:var(--dim); font-size:12px; }
38+
.row { display:flex; gap:12px; align-items:flex-start; margin-bottom:14px; flex-wrap:wrap; }
4539
.pills { display:flex; gap:8px; flex-wrap:wrap; }
46-
.pill { font-family:var(--sans); font-weight:600; font-size:13px; background:var(--panel);
47-
color:var(--ink); border:1px solid var(--line); border-radius:999px; padding:8px 16px;
48-
cursor:pointer; display:flex; align-items:center; gap:8px; box-shadow:var(--shadow);
49-
transition:transform .12s ease, border-color .12s ease, color .12s ease; }
50-
.pill:hover { transform:translateY(-1px); border-color:var(--dim); }
40+
.pill { font-family:var(--mono); font-size:13px; background:var(--panel); color:var(--ink);
41+
border:1px solid var(--line); border-radius:4px; padding:7px 14px; cursor:pointer;
42+
display:flex; align-items:center; gap:8px; }
5143
.pill.on { border-color:var(--phos); color:var(--phos); }
52-
.pill:focus-visible { outline:2px solid var(--phos); outline-offset:2px; }
53-
.led { width:8px; height:8px; border-radius:50%; background:var(--phos); box-shadow:0 0 8px var(--phos); }
54-
.led.bad { background:var(--alert); box-shadow:0 0 8px var(--alert); }
55-
.rng { display:flex; align-items:center; background:var(--panel); border:1px solid var(--line);
56-
border-radius:10px; overflow:hidden; box-shadow:var(--shadow); }
57-
.rng span { color:var(--dim); font-size:10px; padding:0 6px 0 14px; letter-spacing:.14em; font-family:var(--mono); }
44+
.led { width:8px; height:8px; border-radius:50%; background:var(--phos); box-shadow:0 0 6px var(--phos); }
45+
.led.bad { background:var(--alert); box-shadow:0 0 6px var(--alert); }
46+
.rng { display:flex; background:var(--panel); border:1px solid var(--line); border-radius:4px; overflow:hidden; }
47+
.rng span { color:var(--dim); font-size:11px; padding:8px 10px 0 12px; letter-spacing:.1em; }
5848
.rng button { font-family:var(--mono); font-size:12px; background:none; border:0; color:var(--dim);
59-
padding:9px 13px; cursor:pointer; transition:color .12s ease, background .12s ease; }
60-
.rng button:hover { color:var(--ink); }
61-
.rng button.on { background:color-mix(in srgb, var(--amber) 14%, transparent); color:var(--amber); font-weight:700; }
62-
.scope { background:var(--panel); border:1px solid var(--line); border-radius:16px;
63-
padding:18px; box-shadow:var(--shadow-lg); }
49+
padding:8px 14px; cursor:pointer; }
50+
.rng button.on { background:#1a2018; color:var(--amber); }
51+
.scope { background:var(--panel); border:1px solid var(--line); border-radius:6px; padding:14px; }
6452
.crt { position:relative; }
65-
#chart { height:440px; background:var(--screen); border-radius:10px; }
53+
#chart { height:420px; background:var(--screen); border-radius:4px; }
6654
html:not([data-theme]) .crt::after {
67-
content:""; position:absolute; inset:0; border-radius:10px; pointer-events:none;
55+
content:""; position:absolute; inset:0; border-radius:4px; pointer-events:none;
6856
background:
69-
repeating-linear-gradient(0deg, transparent 0 3px, rgba(0,0,0,.04) 3px 4px),
70-
radial-gradient(120% 90% at 50% 42%, transparent 58%, rgba(0,0,0,.34) 100%);
57+
repeating-linear-gradient(0deg, transparent 0 3px, rgba(0,0,0,.045) 3px 4px),
58+
radial-gradient(120% 90% at 50% 42%, transparent 55%, rgba(0,0,0,.38) 100%);
7159
}
72-
.readouts { display:flex; gap:0; margin-top:16px; flex-wrap:wrap; align-items:stretch; }
73-
.ro { padding:2px 26px 2px 0; margin-right:26px; border-right:1px solid var(--line); }
74-
.ro:last-of-type { border-right:0; }
75-
.ro .v { font-size:26px; font-weight:600; color:var(--phos); font-family:var(--mono); line-height:1.15; }
60+
.readouts { display:flex; gap:34px; margin-top:12px; flex-wrap:wrap; align-items:flex-end; }
61+
.ro .v { font-size:24px; color:var(--phos); }
7662
.ro .v.bad { color:var(--alert); }
77-
.ro .l { font-size:10px; color:var(--dim); letter-spacing:.14em; margin-top:3px; font-family:var(--mono); }
63+
.ro .l { font-size:10px; color:var(--dim); letter-spacing:.12em; margin-top:2px; }
7864
.sp { flex:1; }
79-
footer { margin-top:14px; color:var(--dim); font-size:12.5px; display:flex; gap:12px; flex-wrap:wrap; }
80-
#conclusion { color:var(--ink); font-weight:600; font-family:var(--mono); font-size:12.5px; }
81-
.legend { color:var(--dim); font-family:var(--mono); font-size:11.5px; align-self:center; }
65+
footer { margin-top:12px; color:var(--dim); font-size:12px; display:flex; gap:10px; flex-wrap:wrap; }
66+
#conclusion { color:var(--ink); }
67+
.legend { color:var(--dim); }
8268
</style>
8369
</head>
8470
<body>
@@ -165,21 +151,16 @@
165151
if ((!current || !visible.some(t => t.name === current)) && visible.length) current = visible[0].name;
166152
}
167153

168-
let renderSeq = 0;
169154
async function render() {
170-
const seq = ++renderSeq;
171155
await loadTargets();
172156
const t = targets.find(x => x.name === current);
173157
if (!t) return;
174158
if (days > 0) { await renderBand(t); return; }
175159
const rounds = await (await fetch(`/api/series?target=${encodeURIComponent(current)}&minutes=${minutes}`)).json() || [];
176-
if (seq !== renderSeq) return; // 过期响应:用户已切走
177160

178161
const smoke = [], median = [], lossBars = [], burstPts = [];
179162
let sent = 0, recv = 0, bursts = 0;
180163
const pool = [];
181-
rounds.sort((a, b) => a.t - b.t);
182-
const roundInfo = {};
183164
rounds.forEach(r => {
184165
const ts = r.t * 1000;
185166
let rmed = 0;
@@ -196,7 +177,6 @@
196177
const lp = r.s ? (r.s - r.r) / r.s * 100 : 0;
197178
if (lp > 0) lossBars.push([ts, lp]);
198179
if (r.b) { bursts++; burstPts.push([ts, lp, r.z || 0, r.s - r.r, r.s]); }
199-
roundInfo[ts] = { med: rmed, lp, lost: r.s - r.r, s: r.s, b: r.b, z: r.z || 0 };
200180
});
201181
pool.sort((a,b)=>a-b);
202182
const q = p => pool.length ? pool[Math.min(pool.length-1, Math.floor(pool.length*p))] : 0;
@@ -218,47 +198,30 @@
218198
animation: false,
219199
grid: { left: 52, right: 46, top: 18, bottom: 42 },
220200
xAxis: { type: 'time', axisLine: { lineStyle: { color: cssv('--grid-axis') } },
221-
axisLabel: { color: cssv('--dim'), fontSize: 10 }, splitLine: { lineStyle: { color: cssv('--grid-split') } } },
201+
axisLabel: { color: cssv('--dim'), fontSize: 10 }, splitLine: { lineStyle: { color: cssv('--grid-split') } },
202+
axisPointer: { show: true, snap: false,
203+
lineStyle: { color: cssv('--dim'), opacity: .45 },
204+
label: { show: true, backgroundColor: cssv('--panel'), color: cssv('--ink'),
205+
borderColor: cssv('--line'), borderWidth: 1, fontFamily: 'monospace', fontSize: 10,
206+
formatter: p => new Date(p.value).toLocaleTimeString() } } },
222207
yAxis: [
223208
{ type: 'value', name: 'RTT ms', nameTextStyle: { color: cssv('--dim'), fontSize: 10 },
224209
axisLabel: { color: cssv('--dim'), fontSize: 10 }, splitLine: { lineStyle: { color: cssv('--grid-split') } } },
225210
{ type: 'value', max: 100, show: false }
226211
],
227-
tooltip: {
228-
trigger: 'axis',
229-
axisPointer: { type: 'cross',
230-
lineStyle: { color: cssv('--dim'), opacity: .45 },
231-
crossStyle: { color: cssv('--dim'), opacity: .45 },
232-
label: { backgroundColor: cssv('--panel'), color: cssv('--ink'), borderColor: cssv('--line'),
233-
borderWidth: 1, fontFamily: 'monospace', fontSize: 10,
234-
formatter: p => p.axisDimension === 'x'
235-
? new Date(p.value).toLocaleTimeString()
236-
: (+p.value).toFixed(1) } },
237-
backgroundColor: cssv('--panel'), borderColor: cssv('--line'), borderWidth: 1,
238-
padding: [8, 12], extraCssText: 'border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.18)',
239-
textStyle: { color: cssv('--ink'), fontSize: 12, fontFamily: 'monospace' },
240-
formatter: params => {
241-
const ts = params[0] && params[0].axisValue;
242-
const info = roundInfo[ts];
243-
if (!info) return '';
244-
const t = new Date(+ts).toLocaleTimeString();
245-
let html = `<b>${t}</b><br>median <b>${info.med.toFixed(1)} ms</b>`;
246-
html += ` · loss <b>${info.lp.toFixed(1)}%</b> (${info.lost}/${info.s})`;
247-
if (info.b) html += `<br><span style="color:${cssv('--alert')}">◆ burst · z-score ${info.z ? info.z.toFixed(2) : '—'}</span>`;
248-
return html;
249-
}
250-
},
212+
tooltip: { backgroundColor: cssv('--panel'), borderColor: cssv('--line'),
213+
textStyle: { color: cssv('--ink'), fontSize: 11, fontFamily: 'monospace' } },
251214
// 荧光束渐变:贴线的样本 = 炽亮核心,散开的样本渐冷渐暗 —— 渐变即信息
252-
visualMap: { show: false, type: 'continuous', dimension: 2, min: 0, max: 1.0,
215+
visualMap: { show: false, type: 'continuous', dimension: 2, min: 0, max: 0.5,
253216
seriesIndex: 0,
254217
inRange: { color: [cssv('--smoke-hot'), cssv('--smoke-mid'), cssv('--smoke-cool')],
255-
opacity: [0.55, 0.25] } },
218+
opacity: [0.5, 0.10] } },
256219
series: [
257-
{ type: 'scatter', data: smoke, symbolSize: 2.4, silent: true, progressive: 4000 },
220+
{ type: 'scatter', data: smoke, symbolSize: 2.2, silent: true, large: true },
258221
{ type: 'line', data: median, showSymbol: false, silent: true, z: 4,
259-
lineStyle: { color: phos, width: 3, opacity: 0.08 } },
222+
lineStyle: { color: phos, width: 7, opacity: 0.10 } },
260223
{ type: 'line', data: median, showSymbol: false,
261-
lineStyle: { color: phos, width: 1.3, shadowColor: phos, shadowBlur: +cssv('--glow') * 2 }, z: 5 },
224+
lineStyle: { color: phos, width: 1.6, shadowColor: phos, shadowBlur: +cssv('--glow') * 2 }, z: 5 },
262225
{ type: 'bar', yAxisIndex: 1, data: lossBars, barMaxWidth: 3,
263226
itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1,
264227
[{ offset: 0, color: cssv('--alert') }, { offset: 1, color: cssv('--lossbar') }]) } },
@@ -289,7 +252,6 @@
289252
// P50 line, a P50–P99 band, loss bars and burst counts from server-side buckets.
290253
async function renderBand(t) {
291254
const rows = await (await fetch(`/api/band?target=${encodeURIComponent(current)}&days=${days}`)).json() || [];
292-
if (seq !== renderSeq) return; // 过期响应:用户已切走
293255
const p50 = [], band = [], loss = [], bursts = [];
294256
let lossW = 0, nSum = 0, burstSum = 0, p99max = 0, p50W = 0, p90W = 0;
295257
rows.forEach(r => {
@@ -316,7 +278,12 @@
316278
tooltip: { backgroundColor: cssv('--panel'), borderColor: cssv('--line'),
317279
textStyle: { color: cssv('--ink'), fontSize: 11, fontFamily: 'monospace' } },
318280
xAxis: { type: 'time', axisLine: { lineStyle: { color: cssv('--grid-axis') } },
319-
axisLabel: { color: cssv('--dim'), fontSize: 10 }, splitLine: { lineStyle: { color: cssv('--grid-split') } } },
281+
axisLabel: { color: cssv('--dim'), fontSize: 10 }, splitLine: { lineStyle: { color: cssv('--grid-split') } },
282+
axisPointer: { show: true, snap: false,
283+
lineStyle: { color: cssv('--dim'), opacity: .45 },
284+
label: { show: true, backgroundColor: cssv('--panel'), color: cssv('--ink'),
285+
borderColor: cssv('--line'), borderWidth: 1, fontFamily: 'monospace', fontSize: 10,
286+
formatter: p => new Date(p.value).toLocaleTimeString() } } },
320287
yAxis: [
321288
{ type: 'value', name: 'RTT ms', nameTextStyle: { color: cssv('--dim'), fontSize: 10 },
322289
axisLabel: { color: cssv('--dim'), fontSize: 10 }, splitLine: { lineStyle: { color: cssv('--grid-split') } } },

0 commit comments

Comments
 (0)