aboutsummaryrefslogtreecommitdiffstats
path: root/src/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/api')
-rw-r--r--src/api/dashboard/dashboard.html95
1 files changed, 68 insertions, 27 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html
index b586834..d872e9b 100644
--- a/src/api/dashboard/dashboard.html
+++ b/src/api/dashboard/dashboard.html
@@ -941,6 +941,10 @@ function renderStats() {
const positions = new Map();
// Nodes put somewhere by hand (dragged): the layout leaves them there.
const pinned = new Set();
+// The lines of the measured links as drawn (`renderGraph`), by pair key: the end whose
+// report is drawn (`a`, the way its bows go) and how far each transport's line bows out
+// at the middle (`sides`; the one in use 0, straight).
+const routes = new Map();
const transportColour = new Map([["mycelium", "#2563eb"], ["libp2p", "#9333ea"],
["tor", "#0891b2"]]);
const SPARE = ["#ca8a04", "#db2777", "#65a30d"];
@@ -1366,6 +1370,7 @@ function renderGraph() {
tooltip(hit(key, p, q, 1), name(a) + " – " + name(b) + ": intended (member list), not measured");
}
const ms = rtt => rtt < 10 ? rtt.toFixed(1) : String(Math.round(rtt));
+ routes.clear();
for (const [key, { a, b, link, secsAgo }] of measured) {
const p = positions.get(a), q = positions.get(b);
if (!p || !q) continue;
@@ -1399,6 +1404,10 @@ function renderGraph() {
// far out); `aside(t)`, how far out at `t` along.
const side = i === 0 ? 0 : (i % 2 ? 1 : -1) * Math.ceil(i / 2) * 6;
const aside = t => 4 * t * (1 - t) * side;
+ // Where it bows, for the messages that go over it (`route`).
+ const route = routes.get(key) || { a, sides: new Map() };
+ if (line.transport && !route.sides.has(line.transport)) route.sides.set(line.transport, side);
+ routes.set(key, route);
const cx = (p.x + q.x) / 2 + nx * side * 2, cy = (p.y + q.y) / 2 + ny * side * 2;
const attrs = { d: "M " + p.x + " " + p.y + " Q " + cx + " " + cy + " " + q.x + " " + q.y,
fill: "none", stroke: colourOf(line.transport),
@@ -2899,6 +2908,34 @@ graph.addEventListener("mouseleave", () => hover(null));
// (`FLASH_GLOW_MS`). Dots are kept above everything else on the map (`flows`, after
// what `show` places), flashes over the lines but below their labels and the nodes. ---
const FLOW_SPREAD_MS = 2000, FLOWS_AT_MOST = 6;
+// The line from `from` to `to` a message went over, as drawn: over `transport`'s
+// line, else (none known, or none drawn for it) the one in use. `at(t)`: the point `t`
+// along (0 at `from`, 1 at `to`) and the direction there (a unit vector); `length`,
+// about how long it is. Null while an end isn't on the map.
+function route(from, to, transport) {
+ const p = positions.get(from), q = positions.get(to);
+ if (!p || !q) return null;
+ const drawnRoute = routes.get(pairKey(from, to));
+ const known = drawnRoute && transport && drawnRoute.sides.get(transport);
+ // Bowing out to the left of the way from `a`: to the right of the other way.
+ const side = (known || 0) * (drawnRoute && drawnRoute.a !== from ? -1 : 1);
+ const chord = Math.max(Math.hypot(q.x - p.x, q.y - p.y), 1);
+ const nx = -(q.y - p.y) / chord, ny = (q.x - p.x) / chord;
+ // The quadratic curve the line is: its control point twice as far out as it bows.
+ const c = { x: (p.x + q.x) / 2 + nx * side * 2, y: (p.y + q.y) / 2 + ny * side * 2 };
+ const length = (chord + Math.hypot(c.x - p.x, c.y - p.y) + Math.hypot(q.x - c.x, q.y - c.y)) / 2;
+ const at = t => {
+ const u = 1 - t;
+ const dx = 2 * u * (c.x - p.x) + 2 * t * (q.x - c.x);
+ const dy = 2 * u * (c.y - p.y) + 2 * t * (q.y - c.y);
+ const d = Math.max(Math.hypot(dx, dy), 1e-6);
+ return { x: u * u * p.x + 2 * u * t * c.x + t * t * q.x,
+ y: u * u * p.y + 2 * u * t * c.y + t * t * q.y, ux: dx / d, uy: dy / d };
+ };
+ return { length, at };
+}
+// The transport an address is of (`mycelium://…` is Mycelium's), as the links name it.
+const schemeOf = address => address ? address.split(":")[0] : null;
// A flash's afterglow, after it arrived; and how long it takes over a link whose round
// trip isn't known.
const FLASH_GLOW_MS = 700, FLASH_UNKNOWN_MS = 50;
@@ -3105,18 +3142,20 @@ function remoteMessagesArrived(id, entry, list) {
const now = performance.now();
const links = new Map();
for (const message of kept) {
- const key = message.from + " " + message.to;
- const link = links.get(key) || { from: message.from, to: message.to, kinds: {}, total: 0 };
+ const transport = schemeOf(message.address);
+ const key = message.from + " " + message.to + " " + (transport || "");
+ const link = links.get(key) ||
+ { from: message.from, to: message.to, transport, kinds: {}, total: 0 };
const kind = link.kinds[message.counted] || (link.kinds[message.counted] = { messages: 0, bytes: 0 });
kind.messages += 1;
kind.bytes += message.bytes;
link.total += 1;
links.set(key, link);
}
- for (const { from, to, kinds, total } of links.values()) {
+ for (const { from, to, transport, kinds, total } of links.values()) {
const shown = dotsFor(kinds, total);
shown.forEach((dot, i) => {
- flows.push({ from, to, start: now + i * FLOW_SPREAD_MS / shown.length,
+ flows.push({ from, to, transport, start: now + i * FLOW_SPREAD_MS / shown.length,
kind: dot.kind, r: dotRadius(dot.bytes) });
});
}
@@ -3368,47 +3407,49 @@ function showConsole() {
function drawFlows() {
flowFrame = null;
const now = performance.now();
- // Where each is: as a dot, how far along its link (0 to 1) as long as the link is now;
- // as a flash, its head and its afterglow's tail (each 0 to 1) and how bright. Arrived
- // (a flash: its afterglow gone), or its link gone, it goes.
+ // Where each is, along the line it went over (`route`): as a dot, how far along (0
+ // to 1) as long as the line is now; as a flash, its head and its afterglow's tail
+ // (each 0 to 1) and how bright. Arrived (a flash: its afterglow gone), or its link
+ // gone, it goes.
const where = flow => {
- const p = positions.get(flow.from), q = positions.get(flow.to);
- if (!p || !q) return null;
- const length = Math.max(Math.hypot(q.x - p.x, q.y - p.y), 1);
+ const line = route(flow.from, flow.to, flow.transport);
+ if (!line) return null;
if (flowsMode === "flash") {
const travel = flow.travel || (flow.travel = Math.max(1, oneWayMs(flow.from, flow.to)));
const age = now - flow.start, glow = Math.max(0, (age - travel) / FLASH_GLOW_MS);
- return { p, q, length, done: glow >= 1, head: Math.min(1, age / travel),
+ return { line, done: glow >= 1, head: Math.min(1, age / travel),
tail: 1 - (1 - Math.min(1, glow)) ** 2, opacity: 1 - Math.min(1, glow) };
}
- const t = (now - flow.start) * flowSpeed / length;
- return { p, q, length, done: t >= 1, t };
+ const t = (now - flow.start) * flowSpeed / line.length;
+ return { line, done: t >= 1, t };
};
for (let i = flows.length - 1; i >= 0; i--) {
const at = where(flows[i]);
if (at === null || at.done) flows.splice(i, 1);
}
const dots = [], streaks = [];
+ const f = n => n.toFixed(1);
for (const flow of flows) {
if (now < flow.start) continue;
const at = where(flow);
- const { p, q } = at;
const r = flow.r * dotScale, colour = kindColour(flow.kind);
if (flowsMode === "flash") {
- // From the tail, a point, widening to the head, rounded: brightest where the
- // message is, drawing back toward it as it fades.
- const ux = (q.x - p.x) / at.length, uy = (q.y - p.y) / at.length;
- const hx = p.x + (q.x - p.x) * at.head, hy = p.y + (q.y - p.y) * at.head;
- const tx = p.x + (q.x - p.x) * Math.min(at.tail, at.head);
- const ty = p.y + (q.y - p.y) * Math.min(at.tail, at.head);
- const w = r * .8, nx = -uy * w, ny = ux * w;
- const f = n => n.toFixed(1);
- streaks.push({ colour, opacity: at.opacity, d: "M" + f(tx) + " " + f(ty) +
- "L" + f(hx + nx) + " " + f(hy + ny) +
- "A" + f(w) + " " + f(w) + " 0 0 0 " + f(hx - nx) + " " + f(hy - ny) + "Z" });
+ // From the tail, a point, widening to the head, rounded, along the line: brightest
+ // where the message is, drawing back toward it as it fades.
+ const head = at.head, tail = Math.min(at.tail, head), w = r * .8, steps = 8;
+ const left = [], right = [];
+ for (let k = 0; k <= steps; k++) {
+ const s = tail + (head - tail) * k / steps, point = at.line.at(s);
+ const width = w * k / steps;
+ left.push(f(point.x - point.uy * width) + " " + f(point.y + point.ux * width));
+ right.push(f(point.x + point.uy * width) + " " + f(point.y - point.ux * width));
+ }
+ streaks.push({ colour, opacity: at.opacity, d: "M" + left.join("L") +
+ "A" + f(w) + " " + f(w) + " 0 0 0 " + right[steps] + "L" +
+ right.slice(0, steps).reverse().join("L") + "Z" });
} else {
- dots.push({ x: p.x + (q.x - p.x) * at.t, y: p.y + (q.y - p.y) * at.t, r, colour,
- opacity: Math.sin(Math.PI * at.t) });
+ const point = at.line.at(at.t);
+ dots.push({ x: point.x, y: point.y, r, colour, opacity: Math.sin(Math.PI * at.t) });
}
}
const layer = (id, count, tag) => {