Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build-meshviewer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ jobs:
- run: npm install
- run: npm audit
- run: npm run lint
- run: npm run test:unit
- run: npm run build
12 changes: 6 additions & 6 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@ Map human readable names to the domain from the individual node as stated in the

Add Links to external Sites like wikis or monitoring. You might use one of the following variables as placeholders:

| variable | description |
| --------------- | --------------------------- |
| `{NODE_CUSTOM}` | node_name with custom regex |
| `{NODE_ID}` | mac address of the node |
| `{NODE_NAME}` | hostname |
| `{TIME}` | |
| variable | description |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `{NODE_CUSTOM}` | node_name with custom regex |
| `{NODE_ID}` | `node_id` from `meshviewer.json` (legacy 12-digit hex IDs, numeric IDs, or dotted IDs like `node.a03`; no `-` inside a node ID!) |
| `{NODE_NAME}` | hostname |
| `{TIME}` | |

Here are some examples:

Expand Down
6 changes: 6 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ This mode serves `dev-fixtures/config.json` as `/config.json` and `dev-fixtures/

For timestamps in `dev-fixtures/meshviewer.json` you can use relative markers like `@now`, `@now-6d`, `@now-28h` or `@now-1w+2d`. They are expanded to ISO timestamps when the fixture endpoint is requested.

### Unit tests

For unit tests run:

`npm run test:unit`

### Build and run using Docker

You have to copy `config.example.json` to `public/config.json`.
Expand Down
60 changes: 30 additions & 30 deletions dev-fixtures/meshviewer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@
"clients_other": 1,
"is_gateway": false,
"addresses": ["fd00::11", "10.0.0.11"],
"gateway_nexthop": "021122334402",
"gateway": "021122334402",
"gateway6": "021122334402",
"gateway_nexthop": "1004",
"gateway": "1004",
"gateway6": "1004",
"autoupdater": {
"enabled": true,
"branch": "stable"
}
},
{
"node_id": "021122334402",
"node_id": "1004",
"hostname": "Vega",
"domain": "lab",
"firstseen": "@now-7d",
Expand All @@ -61,16 +61,16 @@
"clients_other": 0,
"is_gateway": true,
"addresses": ["fd00::12", "10.0.0.12"],
"gateway_nexthop": "021122334402",
"gateway": "021122334402",
"gateway6": "021122334402",
"gateway_nexthop": "1004",
"gateway": "1004",
"gateway6": "1004",
"autoupdater": {
"enabled": true,
"branch": "stable"
}
},
{
"node_id": "021122334403",
"node_id": "node.a03",
"hostname": "Arktur",
"domain": "lab",
"firstseen": "@now-38d",
Expand All @@ -95,9 +95,9 @@
"clients_other": 1,
"is_gateway": true,
"addresses": ["fd00::13", "10.0.0.13"],
"gateway_nexthop": "021122334403",
"gateway": "021122334403",
"gateway6": "021122334403",
"gateway_nexthop": "node.a03",
"gateway": "node.a03",
"gateway6": "node.a03",
"autoupdater": {
"enabled": true,
"branch": "stable"
Expand Down Expand Up @@ -129,16 +129,16 @@
"clients_other": 0,
"is_gateway": false,
"addresses": ["fd00::14", "10.0.0.14"],
"gateway_nexthop": "021122334403",
"gateway": "021122334403",
"gateway6": "021122334403",
"gateway_nexthop": "node.a03",
"gateway": "node.a03",
"gateway6": "node.a03",
"autoupdater": {
"enabled": true,
"branch": "stable"
}
},
{
"node_id": "021122334405",
"node_id": "rgl5x",
"hostname": "Rigel",
"domain": "lab",
"firstseen": "@now-10d",
Expand All @@ -163,9 +163,9 @@
"clients_other": 1,
"is_gateway": true,
"addresses": ["fd00::15", "10.0.0.15"],
"gateway_nexthop": "021122334405",
"gateway": "021122334405",
"gateway6": "021122334405",
"gateway_nexthop": "rgl5x",
"gateway": "rgl5x",
"gateway6": "rgl5x",
"autoupdater": {
"enabled": true,
"branch": "stable"
Expand Down Expand Up @@ -197,9 +197,9 @@
"clients_other": 0,
"is_gateway": false,
"addresses": ["fd00::16", "10.0.0.16"],
"gateway_nexthop": "021122334405",
"gateway": "021122334405",
"gateway6": "021122334405",
"gateway_nexthop": "rgl5x",
"gateway": "rgl5x",
"gateway6": "rgl5x",
"autoupdater": {
"enabled": false,
"branch": "stable"
Expand Down Expand Up @@ -231,9 +231,9 @@
"clients_other": 0,
"is_gateway": false,
"addresses": ["fd00::17", "10.0.0.17"],
"gateway_nexthop": "021122334405",
"gateway": "021122334405",
"gateway6": "021122334405",
"gateway_nexthop": "rgl5x",
"gateway": "rgl5x",
"gateway6": "rgl5x",
"autoupdater": {
"enabled": false,
"branch": "stable"
Expand Down Expand Up @@ -265,9 +265,9 @@
"clients_other": 0,
"is_gateway": false,
"addresses": ["fd00::18", "10.0.0.18"],
"gateway_nexthop": "021122334405",
"gateway": "021122334405",
"gateway6": "021122334405",
"gateway_nexthop": "rgl5x",
"gateway": "rgl5x",
"gateway6": "rgl5x",
"autoupdater": {
"enabled": true,
"branch": "stable"
Expand Down Expand Up @@ -312,15 +312,15 @@
{
"type": "wifi24",
"source": "021122334401",
"target": "021122334402",
"target": "1004",
"source_addr": "02:11:22:33:44:01",
"target_addr": "02:11:22:33:44:02",
"source_tq": 0.93,
"target_tq": 0.91
},
{
"type": "wifi24",
"source": "021122334403",
"source": "node.a03",
"target": "021122334404",
"source_addr": "02:11:22:33:44:03",
"target_addr": "02:11:22:33:44:04",
Expand All @@ -329,7 +329,7 @@
},
{
"type": "wifi5",
"source": "021122334405",
"source": "rgl5x",
"target": "021122334408",
"source_addr": "02:11:22:33:44:05",
"target_addr": "02:11:22:33:44:08",
Expand Down
3 changes: 2 additions & 1 deletion lib/map/clientlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as helper from "../utils/helper.js";
import { Node } from "../utils/node.js";
import { ObjectsLinksAndNodes } from "../datadistributor.js";
import { Coords } from "leaflet";
import { nodeIdToStartAngle } from "./clientlayerUtils.js";

export const ClientLayer = L.GridLayer.extend({
mapRTree: function mapRTree(node: Node) {
Expand All @@ -22,7 +23,7 @@ export const ClientLayer = L.GridLayer.extend({

// pre-calculate start angles
this.data.all().forEach(function (positionedNode: { startAngle: number; node: Node }) {
positionedNode.startAngle = (parseInt(positionedNode.node.node_id.substr(10, 2), 16) / 255) * 2 * Math.PI;
positionedNode.startAngle = nodeIdToStartAngle(positionedNode.node.node_id);
});
this.redraw();
},
Expand Down
30 changes: 30 additions & 0 deletions lib/map/clientlayerUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, it } from "vitest";
import { nodeIdToStartAngle } from "./clientlayerUtils.js";

describe("nodeIdToStartAngle", () => {
it("returns stable but different angles for numeric and dotted node ids", () => {
const numericFirst = nodeIdToStartAngle("1004");
const numericSecond = nodeIdToStartAngle("1004");
const dotted = nodeIdToStartAngle("node.a03");

expect(numericFirst).toBe(numericSecond);
expect(numericFirst).not.toBe(dotted);
expect(numericFirst).toBeGreaterThanOrEqual(0);
expect(numericFirst).toBeLessThan(2 * Math.PI);
expect(dotted).toBeGreaterThanOrEqual(0);
expect(dotted).toBeLessThan(2 * Math.PI);
});

it("keeps legacy hex node ids stable and distinct", () => {
const legacyAFirst = nodeIdToStartAngle("021122334401");
const legacyASecond = nodeIdToStartAngle("021122334401");
const legacyB = nodeIdToStartAngle("021122334402");

expect(legacyAFirst).toBe(legacyASecond);
expect(legacyAFirst).not.toBe(legacyB);
expect(legacyAFirst).toBeGreaterThanOrEqual(0);
expect(legacyAFirst).toBeLessThan(2 * Math.PI);
expect(legacyB).toBeGreaterThanOrEqual(0);
expect(legacyB).toBeLessThan(2 * Math.PI);
});
});
5 changes: 5 additions & 0 deletions lib/map/clientlayerUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { cyrb53, CYRB53_MAX_HASH } from "../utils/cyrb53.js";

export function nodeIdToStartAngle(nodeId: string) {
return (cyrb53(nodeId) / CYRB53_MAX_HASH) * 2 * Math.PI;
}
17 changes: 17 additions & 0 deletions lib/utils/cyrb53.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const CYRB53_MAX_HASH = Number.MAX_SAFE_INTEGER;

export function cyrb53(str: string, seed = 0) {
let h1 = 0xdeadbeef ^ seed;
let h2 = 0x41c6ce57 ^ seed;

for (let index = 0; index < str.length; index++) {
const character = str.charCodeAt(index);
h1 = Math.imul(h1 ^ character, 2654435761);
h2 = Math.imul(h2 ^ character, 1597334677);
}

h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);

return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}
3 changes: 2 additions & 1 deletion lib/utils/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Navigo, { Match } from "navigo";
import { Language } from "./language.js";
import { Link, NodeId } from "./node.js";
import { Moment } from "moment";
import { MESHVIEWER_ROUTE_PATTERN } from "./routerUtils.js";

export interface Objects {
nodeDict: NodeId[];
Expand Down Expand Up @@ -147,7 +148,7 @@ export class Router extends Navigo {
)
.on(
// lang, viewValue, node, link, zoom, lat, lon
/^\/?(\w{2})?\/?(map|graph)?\/?([a-f\d]{12})?([a-f\d\-]{25})?\/?(?:(\d+)\/(-?[\d.]+)\/(-?[\d.]+))?(?:\?.*)?$/,
MESHVIEWER_ROUTE_PATTERN,
(match?: Match) => {
this.customRoute(match);
},
Expand Down
62 changes: 62 additions & 0 deletions lib/utils/routerUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { describe, expect, it } from "vitest";
import { matchMeshviewerRoute } from "./routerUtils.js";

describe("matchMeshviewerRoute", () => {
it("matches numeric and dotted node routes", () => {
expect(matchMeshviewerRoute("/de/map/1004")).toEqual({
lang: "de",
view: "map",
node: "1004",
link: undefined,
zoom: undefined,
lat: undefined,
lng: undefined,
});

expect(matchMeshviewerRoute("/de/map/node.a03")).toEqual({
lang: "de",
view: "map",
node: "node.a03",
link: undefined,
zoom: undefined,
lat: undefined,
lng: undefined,
});
});

it("matches link and location routes", () => {
expect(matchMeshviewerRoute("/de/map/1004-1005")).toEqual({
lang: "de",
view: "map",
node: undefined,
link: "1004-1005",
zoom: undefined,
lat: undefined,
lng: undefined,
});

expect(matchMeshviewerRoute("/de/map/13/51.3397/12.3731")).toEqual({
lang: "de",
view: "map",
node: undefined,
link: undefined,
zoom: "13",
lat: "51.3397",
lng: "12.3731",
});
});

it("keeps legacy hex routes working and rejects dashed node ids", () => {
expect(matchMeshviewerRoute("/de/map/021122334401?foo=bar")).toEqual({
lang: "de",
view: "map",
node: "021122334401",
link: undefined,
zoom: undefined,
lat: undefined,
lng: undefined,
});

expect(matchMeshviewerRoute("/de/map/gw-west-02")).toBeNull();
});
});
34 changes: 34 additions & 0 deletions lib/utils/routerUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export interface MeshviewerRouteData {
lang?: string;
view?: string;
node?: string;
link?: string;
zoom?: string;
lat?: string;
lng?: string;
}

const NODE_ID_ROUTE_PART = "[A-Za-z\\d.]+";
const LINK_ID_ROUTE_PART = `${NODE_ID_ROUTE_PART}-${NODE_ID_ROUTE_PART}`;

export const MESHVIEWER_ROUTE_PATTERN = new RegExp(
`^\\/?(\\w{2})?\\/?(map|graph)?(?:\\/(?:(${NODE_ID_ROUTE_PART})|(${LINK_ID_ROUTE_PART})|(?:(\\d+)\\/(-?[\\d.]+)\\/(-?[\\d.]+))))?\\/?(?:\\?.*)?$`,
);

export function matchMeshviewerRoute(path: string): MeshviewerRouteData | null {
const match = path.match(MESHVIEWER_ROUTE_PATTERN);

if (!match) {
return null;
}

return {
lang: match[1],
view: match[2],
node: match[3],
link: match[4],
zoom: match[5],
lat: match[6],
lng: match[7],
};
}
Loading
Loading