diff --git a/lib/forcegraph.ts b/lib/forcegraph.ts
index f3b98842..93820ed7 100644
--- a/lib/forcegraph.ts
+++ b/lib/forcegraph.ts
@@ -7,6 +7,7 @@ import * as d3Timer from "d3-timer";
import * as d3Zoom from "d3-zoom";
import math from "./utils/math.js";
+import * as helper from "./utils/helper.js";
import draw, { MapLink } from "./forcegraph/draw.js";
import { Sidebar } from "./sidebar.js";
import { ClientPointEvent } from "d3-selection";
@@ -158,7 +159,8 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT
return 0.02;
}
// @ts-ignore
- return Math.max(0.5, node.o.source_tq);
+ let metric = helper.linkMetric(node.o.source_tq, node.o.source_tp) ?? 0;
+ return Math.max(0.5, metric);
});
let zoom = d3Zoom
@@ -244,8 +246,8 @@ export const ForceGraph = function (linkScale: (t: any) => any, sidebar: ReturnT
o: link,
source: dictNodes[link.source.node_id],
target: dictNodes[link.target.node_id],
- color: linkScale(link.source_tq),
- color_to: linkScale(link.target_tq),
+ color: linkScale(helper.linkMetric(link.source_tq, link.source_tp) ?? 0),
+ color_to: linkScale(helper.linkMetric(link.target_tq, link.target_tp) ?? 0),
};
});
diff --git a/lib/forcegraph/draw.ts b/lib/forcegraph/draw.ts
index 4ff66c2d..8818605d 100644
--- a/lib/forcegraph/draw.ts
+++ b/lib/forcegraph/draw.ts
@@ -133,7 +133,10 @@ self.drawLink = function drawLink(link: MapLink) {
} else if (link.o.type.indexOf("other") === 0) {
ctx.globalAlpha = 1;
ctx.lineWidth = 3.5;
- if (link.o.source_tq >= 0.99 && link.o.target_tq >= 0.99) {
+ if (
+ (helper.linkMetric(link.o.source_tq, link.o.source_tp) ?? 0) >= 0.99 &&
+ (helper.linkMetric(link.o.target_tq, link.o.target_tp) ?? 0) >= 0.99
+ ) {
link.color = config.forceGraph.otherLinkColor;
link.color_to = config.forceGraph.otherLinkColor;
}
diff --git a/lib/infobox/link.ts b/lib/infobox/link.ts
index 8e07da48..c0641103 100644
--- a/lib/infobox/link.ts
+++ b/lib/infobox/link.ts
@@ -79,11 +79,19 @@ export const Link = function (el: HTMLElement, linkData: LinkData[], linkScale:
);
helper.attributeEntry(
children,
- "node.tq",
+ (link.source_tp ?? 0) > 0 || (link.target_tp ?? 0) > 0 ? "node.throughput" : "node.tq",
h(
"span",
- { style: { color: linkScale((link.source_tq + link.target_tq) / 2) } },
- helper.showTq(link.source_tq) + " - " + helper.showTq(link.target_tq),
+ {
+ style: {
+ color: linkScale(
+ ((helper.linkMetric(link.source_tq, link.source_tp) ?? 0) +
+ (helper.linkMetric(link.target_tq, link.target_tp) ?? 0)) /
+ 2,
+ ),
+ },
+ },
+ helper.showBiDiLinkMetric(link.source_tq, link.source_tp, link.target_tq, link.target_tp),
),
);
diff --git a/lib/infobox/node.ts b/lib/infobox/node.ts
index 3e090f14..ca44e438 100644
--- a/lib/infobox/node.ts
+++ b/lib/infobox/node.ts
@@ -108,7 +108,11 @@ export function Node(el: HTMLElement, node: NodeData, linkScale: (t: any) => any
"a",
{
style: {
- color: linkScale((connecting.link.source_tq + connecting.link.target_tq) / 2),
+ color: linkScale(
+ ((helper.linkMetric(connecting.link.source_tq, connecting.link.source_tp) ?? 0) +
+ (helper.linkMetric(connecting.link.target_tq, connecting.link.target_tp) ?? 0)) /
+ 2,
+ ),
},
props: {
title: connecting.link.source.hostname + " - " + connecting.link.target.hostname,
@@ -120,7 +124,12 @@ export function Node(el: HTMLElement, node: NodeData, linkScale: (t: any) => any
},
},
},
- helper.showTq(connecting.link.source_tq) + " - " + helper.showTq(connecting.link.target_tq),
+ helper.showBiDiLinkMetric(
+ connecting.link.source_tq,
+ connecting.link.source_tp,
+ connecting.link.target_tq,
+ connecting.link.target_tp,
+ ),
),
]),
h("td", helper.showDistance(connecting.link)),
@@ -158,7 +167,13 @@ export function Node(el: HTMLElement, node: NodeData, linkScale: (t: any) => any
name: "node.tq",
class: "ion-connection-bars",
sort: function (a: Neighbour, b: Neighbour) {
- return a.link.source_tq - b.link.source_tq;
+ let am = helper.linkMetric(a.link.source_tq, a.link.source_tp);
+ let an = helper.linkMetric(a.link.target_tq, a.link.target_tp);
+ let bm = helper.linkMetric(b.link.source_tq, b.link.source_tp);
+ let bn = helper.linkMetric(b.link.target_tq, b.link.target_tp);
+ let aAvg = am === undefined && an === undefined ? -Infinity : ((am ?? 0) + (an ?? 0)) / 2;
+ let bAvg = bm === undefined && bn === undefined ? -Infinity : ((bm ?? 0) + (bn ?? 0)) / 2;
+ return aAvg - bAvg;
},
reverse: true,
},
diff --git a/lib/linklist.ts b/lib/linklist.ts
index fab0c2fc..852490c5 100644
--- a/lib/linklist.ts
+++ b/lib/linklist.ts
@@ -28,7 +28,13 @@ let headings: Heading[] = [
name: "node.tq",
class: "ion-connection-bars",
sort: function (a, b) {
- return (a.source_tq + a.target_tq) / 2 - (b.source_tq + b.target_tq) / 2;
+ let am = helper.linkMetric(a.source_tq, a.source_tp);
+ let bm = helper.linkMetric(b.source_tq, b.source_tp);
+ let an = helper.linkMetric(a.target_tq, a.target_tp);
+ let bn = helper.linkMetric(b.target_tq, b.target_tp);
+ let aAvg = am === undefined && an === undefined ? -Infinity : ((am ?? 0) + (an ?? 0)) / 2;
+ let bAvg = bm === undefined && bn === undefined ? -Infinity : ((bm ?? 0) + (bn ?? 0)) / 2;
+ return aAvg - bAvg;
},
reverse: true,
},
@@ -81,8 +87,16 @@ export const Linklist = function (linkScale: (t: any) => any): CanRender & CanSe
h("td", td1Content),
h(
"td",
- { style: { color: linkScale((link.source_tq + link.target_tq) / 2) } },
- helper.showTq(link.source_tq) + " - " + helper.showTq(link.target_tq),
+ {
+ style: {
+ color: linkScale(
+ ((helper.linkMetric(link.source_tq, link.source_tp) ?? 0) +
+ (helper.linkMetric(link.target_tq, link.target_tp) ?? 0)) /
+ 2,
+ ),
+ },
+ },
+ helper.showBiDiLinkMetric(link.source_tq, link.source_tp, link.target_tq, link.target_tp),
),
h("td", helper.showDistance(link)),
]);
diff --git a/lib/map/labellayer.js b/lib/map/labellayer.js
index 6cf950cc..a5d3575c 100644
--- a/lib/map/labellayer.js
+++ b/lib/map/labellayer.js
@@ -112,10 +112,12 @@ function addLinksToMap(dict, linkScale, graph) {
return graph.map(function (link) {
let linkColor;
- if (link.type.indexOf("other") == 0 && link.source_tq >= 0.99 && link.target_tq >= 0.99) {
+ let sm = helper.linkMetric(link.source_tq, link.source_tp);
+ let tm = helper.linkMetric(link.target_tq, link.target_tp);
+ if (link.type.indexOf("other") == 0 && (sm ?? 0) >= 0.99 && (tm ?? 0) >= 0.99) {
linkColor = config.map.otherLinkColor;
} else {
- linkColor = linkScale((link.source_tq + link.target_tq) / 2);
+ linkColor = linkScale(((sm ?? 0) + (tm ?? 0)) / 2);
}
let opts = {
@@ -136,9 +138,7 @@ function addLinksToMap(dict, linkScale, graph) {
"
" +
helper.showDistance(link) +
" / " +
- helper.showTq(link.source_tq) +
- " - " +
- helper.showTq(link.target_tq) +
+ helper.showBiDiLinkMetric(link.source_tq, link.source_tp, link.target_tq, link.target_tp) +
"
" +
link.type +
"",
diff --git a/lib/utils/helper.ts b/lib/utils/helper.ts
index 02025be5..b05b68b7 100644
--- a/lib/utils/helper.ts
+++ b/lib/utils/helper.ts
@@ -123,6 +123,63 @@ export const showTq = function showTq(tq: number) {
return (tq * 100).toFixed(0) + "%";
};
+// Format throughput in kbps as an SI-prefixed bit/s string (e.g. "24 Mbit/s").
+export const showThroughput = function showThroughput(kbps: number) {
+ if (kbps === undefined || kbps === null || isNaN(kbps)) {
+ return "";
+ }
+ let units = ["kbit/s", "Mbit/s", "Gbit/s", "Tbit/s"];
+ let value = kbps;
+ let unit = 0;
+ while (value >= 1000 && unit < units.length - 1) {
+ value /= 1000;
+ unit++;
+ }
+ return (value < 10 ? value.toFixed(1) : value.toFixed(0)) + " " + units[unit];
+};
+
+// Returns a Link's link-quality metric for a given direction, normalised to a
+// 0..1 scale where higher is better. Batman IV uses tq directly (TQ=0 is
+// treated as no metric; call sites coalesce to 0 for colouring). Batman V
+// maps throughput logarithmically over [1 Mbps, 1.2 Gbps]. Returns undefined
+// when neither metric is set or non-zero.
+export const linkMetric = function linkMetric(tq: number | undefined, throughput: number | undefined) {
+ if (tq !== undefined && tq > 0) {
+ return tq;
+ }
+ if (throughput !== undefined && throughput > 0) {
+ let lo = Math.log10(1e3);
+ let hi = Math.log10(1.2e6);
+ let v = (Math.log10(throughput) - lo) / (hi - lo);
+ return Math.max(0, Math.min(1, v));
+ }
+ return undefined;
+};
+
+// Human-readable display string for a Link direction.
+export const showLinkMetric = function showLinkMetric(tq: number | undefined, throughput: number | undefined) {
+ if (tq !== undefined && tq > 0) {
+ return showTq(tq);
+ }
+ if (throughput !== undefined && throughput > 0) {
+ return showThroughput(throughput);
+ }
+ return "";
+};
+
+// Human-readable display string for both directions of a Link, omitting the
+// separator when one side has no metric (e.g. VPN gateways in Batman V).
+export const showBiDiLinkMetric = function showBiDiLinkMetric(
+ src_tq: number | undefined,
+ src_throughput: number | undefined,
+ tgt_tq: number | undefined,
+ tgt_throughput: number | undefined,
+) {
+ const src = showLinkMetric(src_tq, src_throughput);
+ const tgt = showLinkMetric(tgt_tq, tgt_throughput);
+ return [src, tgt].filter(Boolean).join(" - ");
+};
+
export function attributeEntry(children: VNode[], label: string, value: string | VNode) {
if (value !== undefined) {
if (typeof value !== "object") {
diff --git a/lib/utils/node.ts b/lib/utils/node.ts
index 9f354eee..ee53576f 100644
--- a/lib/utils/node.ts
+++ b/lib/utils/node.ts
@@ -15,8 +15,10 @@ export interface Link {
target_addr: string;
source_mac?: string; // Same as _addr
target_mac?: string; // Same as _addr
- source_tq: number;
- target_tq: number;
+ source_tq?: number; // Batman IV link quality (0..1)
+ target_tq?: number;
+ source_tp?: number; // Batman V link quality (kbps)
+ target_tp?: number;
}
export interface Neighbour {
diff --git a/public/locale/cz.json b/public/locale/cz.json
index 7a470c29..528570c7 100644
--- a/public/locale/cz.json
+++ b/public/locale/cz.json
@@ -9,6 +9,7 @@
"distance": "Vzdálenost",
"connectionType": "typ připojení",
"tq": "tq",
+ "throughput": "Propustnost",
"lastOnline": "poslední on-line %{time} (%{date})",
"lastOffline": "lastOffline %{time} (%{date})",
"activated": "aktivováno (%{branch})",
diff --git a/public/locale/de.json b/public/locale/de.json
index c0d18f54..2240039c 100644
--- a/public/locale/de.json
+++ b/public/locale/de.json
@@ -9,6 +9,7 @@
"distance": "Entfernung",
"connectionType": "Verbindungsart",
"tq": "Übertragungsqualität",
+ "throughput": "Durchsatz",
"lastOnline": "online, letzte Nachricht %{time} (%{date})",
"lastOffline": "offline, letzte Nachricht %{time} (%{date})",
"activated": "aktiviert (%{branch})",
diff --git a/public/locale/en.json b/public/locale/en.json
index 93403aa6..f6142677 100644
--- a/public/locale/en.json
+++ b/public/locale/en.json
@@ -9,6 +9,7 @@
"distance": "Distance",
"connectionType": "Connection type",
"tq": "Transmit quality",
+ "throughput": "Throughput",
"lastOnline": "online, last message %{time} (%{date})",
"lastOffline": "offline, last message %{time} (%{date})",
"activated": "activated (%{branch})",
diff --git a/public/locale/fr.json b/public/locale/fr.json
index 71945beb..09aae71c 100644
--- a/public/locale/fr.json
+++ b/public/locale/fr.json
@@ -9,6 +9,7 @@
"distance": "Distance",
"connectionType": "Type de connexion",
"tq": "Qualité de transmission",
+ "throughput": "Débit",
"lastOnline": "en ligne, dernier message %{time} (%{date})",
"lastOffline": "hors ligne, dernier message %{time} (%{date})",
"activated": "activé (%{branch})",
diff --git a/public/locale/ru.json b/public/locale/ru.json
index 247a0975..d685f87c 100644
--- a/public/locale/ru.json
+++ b/public/locale/ru.json
@@ -9,6 +9,7 @@
"distance": "Расстояние",
"connectionType": "Тип подключения",
"tq": "Качество связи",
+ "throughput": "Пропускная способность",
"lastOnline": "в сети, последнее сообщение %{time} (%{date})",
"lastOffline": "не в сети, последнее сообщение %{time} (%{date})",
"activated": "активировано (%{branch})",
diff --git a/public/locale/tr.json b/public/locale/tr.json
index 43a0ca01..59abf0a9 100644
--- a/public/locale/tr.json
+++ b/public/locale/tr.json
@@ -9,6 +9,7 @@
"distance": "Mesafe",
"connectionType": "Bağlantı türü",
"tq": "İletim kalitesi",
+ "throughput": "Aktarım hızı",
"lastOnline": "çevrimiçi, son mesaj %{time} (%{date})",
"lastOffline": "çevrimdışı, son mesaj %{time} (%{date})",
"activated": "aktif (%{branch})",
diff --git a/scss/modules/_infobox.scss b/scss/modules/_infobox.scss
index 37d3abd8..b8d9b34b 100644
--- a/scss/modules/_infobox.scss
+++ b/scss/modules/_infobox.scss
@@ -22,6 +22,10 @@
th,
td {
+ &:nth-child(2) {
+ width: 42%;
+ }
+
&:nth-child(3),
&:nth-child(5) {
width: 12%;