Date: 2026-07-26
Accepted
Operators with geographically distributed WireGuard sites (office in Frankfurt, home-office in Hamburg, cloud VM in Amsterdam) currently only see the existing radial hub-and-spoke TopologyDiagram.js — logically correct, but it carries no sense of where anything physically is. Issue #11 asks for a world-map view as an alternative, not a replacement: site-peers (gateways) as pins at their maintained location, active tunnels as connecting lines, with the existing radial view remaining the default for logical/ACL-oriented browsing.
Two prior decisions already narrowed this scope before this ADR was written:
Site is a logical CIDR grouping with no physical location; the gateway Peer is the physical device. Peer.lat/lng/locationLabel (populated only for type=site) via migration V47.Settings.hubLat/hubLng/hubLocationLabel (V33), already surfaced in the radial diagram's hub label.Both are manually entered (paste "lat, lng" into the peer/hub form) — there has never been an automatic IP→location resolution step anywhere in islandr, and this ADR's scope explicitly keeps it that way (see Decision). A user-supplied design proposal for this feature suggested adding MaxMind GeoLite2 (.mmdb, offline, no external API call at request time) to auto-resolve locations from peer endpoint IPs, particularly for roaming client peers with no fixed site. That is out of scope for this ADR — confirmed via direct question at authoring time — because it introduces a new operational component (a .mmdb file that needs periodic redistribution/updates, a license/redistribution check for GeoLite2's EULA, and a fallback story for CGNAT/private endpoint IPs that resolve to nothing useful) for a benefit (auto-locating peers nobody asked to place on the map) that the current scope — fixed sites with an admin who already knows where they are — doesn't need. If a future issue asks specifically for roaming-peer auto-location, that is this ADR's natural follow-up, not something to build speculatively alongside the map itself.
The rendering approach was also settled going in, precisely because of islandr's own constraints: it runs as a single native binary with no build step for the frontend (per ADR-0002) and is designed to work in air-gapped/zero-network deployments — ruling out Leaflet + OpenStreetMap tiles (a live external tile-server dependency at runtime) before this ADR was even drafted.
A — Local TopoJSON country outlines, rendered as SVG <path> elements via a slim d3-geo-only dependency (projection functions only, not the full d3 bundle), manual geocoding only, no GeoIP.
world-110m.json (or equivalent low-resolution TopoJSON, target < 50 KB) ships with the app under META-INF/resources — no CDN fetch, works fully offline.d3-geo's geoMercator (or geoEquirectangular — see note below) projection converts each site-peer's/hub's stored lat/lng into SVG x/y once per render; country borders render as <path d="..."> filled with a semantic surface-token color (fill: var(--surface-2) or similar), themeable per ADR-colors/dark-mode conventions already in colors_and_type.css.<path> arcs, reusing the same status-color convention already established in TopologyDiagram.js (status-ok green for an online gateway, muted/dashed for offline) — no new color vocabulary.topo-hub-pulse, the one ambient-motion element the design brief permits) anchors the hub pin on the map the same way it anchors the hub circle in the radial view..mmdb. Peers/sites without manually-entered coordinates simply don't appear as map pins (same as today's behavior for anything without lat/lng). Roaming client peers (laptops, phones) are out of scope for map placement entirely in this ADR — they have no fixed location to plot in the first place, manual or automatic.Resolved at implementation time (same session): hand-rolled equirectangular projection, no d3-geo dependency at all — the 3-line lat/lng→x/y formula needs no library, and ADR-0002's no-npm-build stance weighs against adding even a "slim" d3 sub-package for a formula this small. Land data is the land object from world-atlas@2's countries-110m.json (not the countries object with political borders) — TopoJSON decoded once at authoring time into plain [[lon,lat],...] polygon rings (see META-INF/resources/data/README.md for the regeneration recipe), shipped as a 66 KB static JSON asset, no political borders at all. This sidesteps the border-dispute concern in R-169 below more completely than "use a coarse resolution" alone — there is nothing to dispute when only coastlines are drawn. Equirectangular (not Mercator) was chosen for the same reason: zero-cost to compute, and pole distortion is irrelevant once the viewBox crops out the deep polar regions no site will ever be in.
Baseline: A — local TopoJSON + d3-geo projection, manual geocoding only (the decision).
| Criterion (weight) | A: local TopoJSON + d3-geo, manual geo (baseline) | B: Leaflet + OSM tiles | C: A + MaxMind GeoIP auto-resolution | D: status quo (radial diagram only, no map) |
|---|---|---|---|---|
| Works fully offline / air-gapped (5) | 0 | −1 (needs a live tile server at runtime; self-hosting a tile server is a materially bigger operational ask than a bundled JSON file) | 0 | +1 (nothing to break) |
| No new runtime dependency on an external service (4) | 0 | −1 | 0 | +1 |
| Implementation effort (3) | 0 | +1 (Leaflet handles projection, panning, zoom for free) | −1 (mmdb lookup service, update job, CGNAT fallback logic) | +1 (zero code) |
| Matches actual current scope (fixed sites, admin already knows the location) (4) | +1 | 0 | 0 (solves a problem — roaming-peer placement — nobody asked for yet) | −1 |
| New operational component to maintain (mmdb redistribution/licensing) (3) | 0 | 0 | −1 | +1 |
| Bundle footprint (2) | 0 (TopoJSON ~30-50KB + slim d3-geo) | −1 (Leaflet itself + tile-loading code) | 0 | +1 |
| Weighted total | 1 | −9 | −4 | 4* |
* D's positive score is the same pattern flagged in ADR-0016's matrix: it wins by having no cost, not by satisfying the requirement. Issue #11 exists specifically because the radial-only view doesn't answer "where are my sites" — D is included for completeness, not as a live contender.
.mmdb file lifecycle, license terms, CGNAT/private-IP fallback with no good answer). Rejected for this ADR specifically, not rejected forever — flagged as a clean follow-up ADR if a future issue asks for roaming-peer auto-location.TopologyWorldMap.js, alongside TopologyDiagram.js — sharing color/status conventions and CSS classes (.topo hub-pulse/hub-core/node-ring/node-label rules) but not layout code (equirectangular projection vs. radial polar layout). Rendered as an additional "Karte"/"Map" tab in DashboardView.js, alongside the existing "Topologie" and "Verbindungsaktivität" tabs, hidden until worldMapAvailable (≥2 geocoded site-peers).DashboardDto.TopologySite.gatewayLat/gatewayLng and DashboardDto.Topology.hubLat/hubLon (named to match the pre-existing Settings.hubLat/hubLon field naming, not the Peer.lat/lng convention used for gatewayLat/gatewayLng — the hub and site-peer coordinate fields predate this ADR and already used those two different names inconsistently; this ADR did not attempt to unify them). Reuses Peer.lat/lng and Settings.hubLat/hubLon, already populated by the existing manual-geocoding UI — no new database columns.META-INF/resources/data/world-land-110m.json (66 KB raw / ~20 KB gzipped, land-mass outlines only, no political borders — see below), ships with the app for the first time purely for this feature. A short data/README.md documents its provenance (world-atlas@2, ISC-licensed, Natural Earth public-domain source data) and the exact regeneration recipe (decode TopoJSON arcs, round coordinates, emit plain polygon rings) in case a higher-resolution source is ever substituted..mmdb distribution) — nothing here precludes it.land object (coastlines) rather than countries (political borders) removes the disputed-border class of drift entirely — a coastline doesn't move because of a diplomatic dispute. What remains is ordinary "is there a newer/better-resolution source" maintenance, accepted as a manual, infrequent task (see data/README.md's regeneration recipe), not an automated pipeline.TopologyDiagram.js — the existing radial diagram this adds an alternative view alongside; status-color and hub-glow-pulse conventions reused here.