123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777 |
- document.addEventListener('DOMContentLoaded', main)
- function main() {
- getJSON("config.json").then( function (config) {
- moment.locale("de")
- var options = { worldCopyJump: true,
- zoomControl: false
- }
- var mapDiv = document.createElement("div")
- mapDiv.classList.add("map")
- document.body.insertBefore(mapDiv, document.body.firstChild)
- var map = L.map(mapDiv, options)
- var sidebar = mkSidebar(document.body)
- var infobox = new Infobox(sidebar)
- var gotoAnything = new gotoBuilder(config, infobox, showNodeinfo, showLinkinfo)
- var urls = [ config.dataPath + 'nodes.json',
- config.dataPath + 'graph.json'
- ]
- var p = Promise.all(urls.map(getJSON))
- p.then(handle_data(config, sidebar, infobox, map, gotoAnything))
- })
- }
- function handle_data(config, sidebar, infobox, map, gotoAnything) {
- return function (data) {
- var nodedict = data[0]
- var nodes = Object.keys(nodedict.nodes).map(function (key) { return nodedict.nodes[key] })
- nodes = nodes.filter( function (d) {
- return "firstseen" in d && "lastseen" in d
- })
- nodes.forEach( function(node) {
- node.firstseen = moment.utc(node.firstseen)
- node.lastseen = moment.utc(node.lastseen)
- })
- var now = moment()
- var age = moment(now).subtract(14, 'days')
- var newnodes = limit("firstseen", age, sortByKey("firstseen", nodes).filter(online))
- var lostnodes = limit("lastseen", age, sortByKey("lastseen", nodes).filter(offline))
- var onlinenodes = nodes.filter(online)
- var graph = data[1].batadv
- var graphnodes = data[0].nodes
- graph.nodes.forEach( function (d) {
- if (d.node_id in graphnodes)
- d.node = graphnodes[d.node_id]
- })
- graph.links.forEach( function (d) {
- if (graph.nodes[d.source].node)
- d.source = graph.nodes[d.source]
- else
- d.source = undefined
- if (graph.nodes[d.target].node)
- d.target = graph.nodes[d.target]
- else
- d.target = undefined
- })
- var links = graph.links.filter( function (d) {
- return d.source !== undefined && d.target !== undefined
- })
- links.forEach( function (d) {
- if (!("location" in d.source.node.nodeinfo && "location" in d.target.node.nodeinfo))
- return
- d.latlngs = []
- d.latlngs.push(L.latLng(d.source.node.nodeinfo.location.latitude, d.source.node.nodeinfo.location.longitude))
- d.latlngs.push(L.latLng(d.target.node.nodeinfo.location.latitude, d.target.node.nodeinfo.location.longitude))
- d.distance = d.latlngs[0].distanceTo(d.latlngs[1])
- })
- nodes.forEach( function (d) {
- d.neighbours = []
- })
- links.forEach( function (d) {
- d.source.node.neighbours.push({ node: d.target.node, link: d })
- d.target.node.neighbours.push({ node: d.source.node, link: d })
- })
- var markers = mkmap(map, sidebar, now, newnodes, lostnodes, onlinenodes, links, gotoAnything)
- gotoAnything.addMarkers(markers)
- showMeshstats(sidebar, nodes)
- mkNodesList(sidebar, config.showContact, "firstseen", gotoAnything.node, "Neue Knoten", newnodes)
- mkNodesList(sidebar, config.showContact, "lastseen", gotoAnything.node, "Verschwundene Knoten", lostnodes)
- mkLinkList(sidebar, gotoAnything.link, links)
- var historyDict = { nodes: {}, links: {} }
- nodes.forEach( function (d) {
- historyDict.nodes[d.nodeinfo.node_id] = d
- })
- links.forEach( function (d) {
- historyDict.links[linkId(d)] = d
- })
- gotoHistory(gotoAnything, historyDict, window.location.hash)
- window.onpopstate = function (d) {
- gotoHistory(gotoAnything, historyDict, d.state)
- }
- }
- }
- function mkSidebar(el) {
- var sidebar = document.createElement("div")
- sidebar.classList.add("sidebar")
- el.appendChild(sidebar)
- var button = document.createElement("button")
- sidebar.appendChild(button)
- button.classList.add("sidebarhandle")
- button.onclick = function () {
- sidebar.classList.toggle("hidden")
- }
- var container = document.createElement("div")
- container.classList.add("container")
- sidebar.appendChild(container)
- container.getWidth = function () {
- var small = window.matchMedia("(max-width: 60em)");
- return small.matches ? 0 : sidebar.offsetWidth
- }
- return container
- }
- function mkmap(map, sidebar, now, newnodes, lostnodes, onlinenodes, graph, gotoAnything) {
- function mkMarker(dict, iconFunc) {
- return function (d) {
- var opt = { icon: iconFunc(d),
- title: d.nodeinfo.hostname
- }
- var m = L.marker([d.nodeinfo.location.latitude, d.nodeinfo.location.longitude], opt)
- m.on('click', gotoAnything.node(d, false))
- m.bindPopup(d.nodeinfo.hostname)
- dict[d.nodeinfo.node_id] = m
- return m
- }
- }
- var iconBase = { iconUrl: 'img/circlemarker.png',
- iconRetinaUrl: 'img/circlemarker@2x.png',
- iconSize: [17, 17],
- iconAnchor: [8, 8],
- popupAnchor: [0, -3]
- }
- var iconOnline = Object.assign({}, iconBase)
- iconOnline.className = "node-online"
- iconOnline = L.icon(iconOnline)
- var iconOffline = Object.assign({}, iconBase)
- iconOffline.className = "node-offline"
- iconOffline = L.icon(iconOffline)
- var iconNew = Object.assign({}, iconBase)
- iconNew.className = "node-new"
- iconNew = L.icon(iconNew)
- var iconOfflineAlert = Object.assign({}, iconBase)
- iconOfflineAlert.className = "node-offline node-alert"
- iconOfflineAlert = L.icon(iconOfflineAlert)
- L.control.zoom({ position: "topright" }).addTo(map)
- L.tileLayer("https://otile{s}-s.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg", {
- subdomains: "1234",
- type: "osm",
- attribution: "Map data Tiles © <a href=\"https://www.mapquest.com/\" target=\"_blank\">MapQuest</a> <img src=\"https://developer.mapquest.com/content/osm/mq_logo.png\" />, Map data © OpenStreetMap contributors, CC-BY-SA",
- maxZoom: 18
- }).addTo(map)
- var markersDict = addLinksToMap(map, graph, gotoAnything)
- var nodes = newnodes.concat(lostnodes).filter(has_location)
- var markers = nodes.map(mkMarker(markersDict, function (d) {
- if (d.flags.online)
- return iconNew
- if (d.lastseen.isAfter(moment(now).subtract(1, 'days')))
- return iconOfflineAlert
- else
- return iconOffline
- }))
- var onlinemarkers = subtract(onlinenodes.filter(has_location), newnodes)
- .map(mkMarker(markersDict, function (d) { return iconOnline } ))
- var groupOnline = L.featureGroup(onlinemarkers).addTo(map)
- var group = L.featureGroup(markers).addTo(map)
- var bounds = group.getBounds()
- if (!bounds.isValid())
- bounds = groupOnline.getBounds()
- if (bounds.isValid())
- map.fitBounds(bounds, {paddingTopLeft: [sidebar.getWidth(), 0]})
- var funcDict = {}
- Object.keys(markersDict).map( function(k) {
- funcDict[k] = function (d) {
- var m = markersDict[k]
- var bounds
- if ("getBounds" in m)
- bounds = m.getBounds()
- else
- bounds = L.latLngBounds([m.getLatLng()])
- map.fitBounds(bounds, {paddingTopLeft: [sidebar.getWidth(), 0]})
- m.openPopup(bounds.getCenter())
- }
- })
- return funcDict
- }
- function addLinksToMap(map, graph, gotoAnything) {
- var markersDict = {}
- var scale = chroma.scale(chroma.interpolate.bezier(['green', 'yellow', 'red'])).domain([1, 5])
- graph = graph.filter( function (d) {
- return "distance" in d
- })
- var lines = graph.map( function (d) {
- var opts = { color: scale(d.tq).hex(),
- weight: 4
- }
- var line = L.polyline(d.latlngs, opts)
- line.bindPopup(d.source.node.nodeinfo.hostname + " – " + d.target.node.nodeinfo.hostname + "<br><strong>" + showDistance(d) + " / " + showTq(d) + "</strong>")
- line.on('click', gotoAnything.link(d, false))
- markersDict[linkId(d)] = line
- return line
- })
- var group = L.featureGroup(lines).addTo(map)
- return markersDict
- }
- function mkLinkList(el, gotoProxy, links) {
- if (links.length == 0)
- return
- var h2 = document.createElement("h2")
- h2.textContent = "Verbindungen"
- el.appendChild(h2)
- var table = document.createElement("table")
- var thead = document.createElement("thead")
- var tr = document.createElement("tr")
- var th1 = document.createElement("th")
- th1.textContent = "Knoten"
- tr.appendChild(th1)
- var th2 = document.createElement("th")
- th2.textContent = "TQ"
- tr.appendChild(th2)
- var th3 = document.createElement("th")
- th3.textContent = "Entfernung"
- th3.classList.add("sort-default")
- tr.appendChild(th3)
- thead.appendChild(tr)
- table.appendChild(thead)
- var tbody = document.createElement("tbody")
- links.forEach( function (d) {
- var row = document.createElement("tr")
- var td1 = document.createElement("td")
- var a = document.createElement("a")
- a.textContent = d.source.node.nodeinfo.hostname + " – " + d.target.node.nodeinfo.hostname
- a.href = "#"
- a.onclick = gotoProxy(d)
- td1.appendChild(a)
- row.appendChild(td1)
- if (d.vpn)
- td1.appendChild(document.createTextNode(" (VPN)"))
- var td2 = document.createElement("td")
- td2.textContent = showTq(d)
- row.appendChild(td2)
- var td3 = document.createElement("td")
- td3.textContent = showDistance(d)
- td3.setAttribute("data-sort", d.distance !== undefined ? -d.distance : 1)
- row.appendChild(td3)
- tbody.appendChild(row)
- })
- table.appendChild(tbody)
- new Tablesort(table)
- el.appendChild(table)
- }
- function mkNodesList(el, showContact, tf, gotoProxy, title, list) {
- if (list.length == 0)
- return
- var h2 = document.createElement("h2")
- h2.textContent = title
- el.appendChild(h2)
- var table = document.createElement("table")
- el.appendChild(table)
- var tbody = document.createElement("tbody")
- list.forEach( function (d) {
- var time = moment(d[tf]).fromNow()
- var row = document.createElement("tr")
- var td1 = document.createElement("td")
- var a = document.createElement("a")
- a.classList.add("hostname")
- a.classList.add(d.flags.online ? "online" : "offline")
- a.textContent = d.nodeinfo.hostname
- a.href = "#"
- a.onclick = gotoProxy(d)
- td1.appendChild(a)
- if (has_location(d)) {
- var span = document.createElement("span")
- span.classList.add("icon")
- span.classList.add("ion-location")
- td1.appendChild(span)
- }
- if ("owner" in d.nodeinfo && showContact) {
- var contact = d.nodeinfo.owner.contact
- td1.appendChild(document.createTextNode(" – " + contact + ""))
- }
- var td2 = document.createElement("td")
- td2.textContent = time
- row.appendChild(td1)
- row.appendChild(td2)
- tbody.appendChild(row)
- })
- table.appendChild(tbody)
- el.appendChild(table)
- }
- function showMeshstats(el, nodes) {
- var h2 = document.createElement("h2")
- h2.textContent = "Übersicht"
- el.appendChild(h2)
- var p = document.createElement("p")
- var totalNodes = sum(nodes.filter(online).map(one))
- var totalClients = sum(nodes.filter(online).map( function (d) {
- return d.statistics.clients
- }))
- var totalGateways = sum(nodes.filter(online).filter( function (d) {
- return d.flags.gateway
- }).map(one))
- p.textContent = totalNodes + " Knoten (online), " +
- totalClients + " Clients, " +
- totalGateways + " Gateways"
- p.appendChild(document.createElement("br"))
- p.appendChild(document.createTextNode("Diese Daten sind " + moment.utc(nodes.timestamp).fromNow(true) + " alt."))
- el.appendChild(p)
- }
- function Infobox(sidebar) {
- var self = this
- el = undefined
- function close() {
- destroy()
- pushHistory()
- }
- function destroy() {
- if (el && el.parentNode) {
- el.parentNode.removeChild(el)
- el = undefined
- }
- }
- self.create = function () {
- destroy()
- el = document.createElement("div")
- sidebar.insertBefore(el, sidebar.firstChild)
- el.scrollIntoView(false)
- el.classList.add("infobox")
- el.close = close
- el.destroy = destroy
- var closeButton = document.createElement("button")
- closeButton.classList.add("close")
- closeButton.onclick = close
- el.appendChild(closeButton)
- return el
- }
- return self
- }
- function showNodeinfo(config, infobox, gotoAnything, d) {
- var el = infobox.create()
- var h2 = document.createElement("h2")
- h2.textContent = d.nodeinfo.hostname
- var span = document.createElement("span")
- span.classList.add(d.flags.online ? "online" : "offline")
- span.textContent = " (" + (d.flags.online ? "online" : "offline, " + d.lastseen.fromNow(true)) + ")"
- h2.appendChild(span)
- el.appendChild(h2)
- var attributes = document.createElement("table")
- attributes.classList.add("attributes")
- attributeEntry(attributes, "Gateway", d.flags.gateway ? "ja" : null)
- attributeEntry(attributes, "In der Karte", has_location(d) ? "ja" : "nein")
- if (config.showContact)
- attributeEntry(attributes, "Kontakt", dictGet(d.nodeinfo, ["owner", "contact"]))
- attributeEntry(attributes, "Hardware", dictGet(d.nodeinfo, ["hardware", "model"]))
- attributeEntry(attributes, "Primäre MAC", dictGet(d.nodeinfo, ["network", "mac"]))
- attributeEntry(attributes, "Firmware", showFirmware(d))
- attributeEntry(attributes, "Uptime", showUptime(d))
- attributeEntry(attributes, "Teil des Netzes", showFirstseen(d))
- attributeEntry(attributes, "Arbeitsspeicher", showRAM(d))
- attributeEntry(attributes, "IP Adressen", showIPs(d))
- attributeEntry(attributes, "Clients", showClients(d))
- el.appendChild(attributes)
- if (d.neighbours.length > 0) {
- var h3 = document.createElement("h3")
- h3.textContent = "Nachbarknoten (" + d.neighbours.length + ")"
- el.appendChild(h3)
- var table = document.createElement("table")
- var thead = document.createElement("thead")
- var tr = document.createElement("tr")
- var th1 = document.createElement("th")
- th1.textContent = "Knoten"
- th1.classList.add("sort-default")
- tr.appendChild(th1)
- var th2 = document.createElement("th")
- th2.textContent = "TQ"
- tr.appendChild(th2)
- var th3 = document.createElement("th")
- th3.textContent = "Entfernung"
- tr.appendChild(th3)
- thead.appendChild(tr)
- table.appendChild(thead)
- var tbody = document.createElement("tbody")
- d.neighbours.forEach( function (d) {
- var tr = document.createElement("tr")
- var td1 = document.createElement("td")
- var a1 = document.createElement("a")
- a1.classList.add("hostname")
- a1.textContent = d.node.nodeinfo.hostname
- a1.href = "#"
- a1.onclick = gotoAnything.node(d.node)
- td1.appendChild(a1)
- if (d.link.vpn)
- td1.appendChild(document.createTextNode(" (VPN)"))
- if (has_location(d.node)) {
- var span = document.createElement("span")
- span.classList.add("icon")
- span.classList.add("ion-location")
- td1.appendChild(span)
- }
- tr.appendChild(td1)
- var td2 = document.createElement("td")
- var a2 = document.createElement("a")
- a2.href = "#"
- a2.textContent = showTq(d.link)
- a2.onclick = gotoAnything.link(d.link)
- td2.appendChild(a2)
- tr.appendChild(td2)
- var td3 = document.createElement("td")
- var a3 = document.createElement("a")
- a3.href = "#"
- a3.textContent = showDistance(d.link)
- a3.onclick = gotoAnything.link(d.link)
- td3.appendChild(a3)
- td3.setAttribute("data-sort", d.link.distance !== undefined ? -d.link.distance : 1)
- tr.appendChild(td3)
- tbody.appendChild(tr)
- })
- table.appendChild(tbody)
- new Tablesort(table)
- el.appendChild(table)
- }
- function showFirmware(d) {
- var release = dictGet(d.nodeinfo, ["software", "firmware", "release"])
- var base = dictGet(d.nodeinfo, ["software", "firmware", "base"])
- if (release === null || base === null)
- return
- return release + " / " + base
- }
- function showUptime(d) {
- if (!("uptime" in d.statistics))
- return
- return moment.duration(d.statistics.uptime, "seconds").humanize()
- }
- function showFirstseen(d) {
- if (!("firstseen" in d))
- return
- return d.firstseen.fromNow(true)
- }
- function showClients(d) {
- if (!d.flags.online)
- return
- return function (el) {
- el.appendChild(document.createTextNode(d.statistics.clients > 0 ? d.statistics.clients : "keine"))
- el.appendChild(document.createElement("br"))
- var span = document.createElement("span")
- span.classList.add("clients")
- span.textContent = " ".repeat(d.statistics.clients)
- el.appendChild(span)
- }
- }
- function showIPs(d) {
- var ips = dictGet(d.nodeinfo, ["network", "addresses"])
- if (ips === null)
- return
- ips.sort()
- return function (el) {
- ips.forEach( function (ip, i) {
- var link = !ip.startsWith("fe80:")
- if (i > 0)
- el.appendChild(document.createElement("br"))
- if (link) {
- var a = document.createElement("a")
- a.href = "http://[" + ip + "]/"
- a.textContent = ip
- el.appendChild(a)
- } else
- el.appendChild(document.createTextNode(ip))
- })
- }
- }
- function showRAM(d) {
- if (!("memory_usage" in d.statistics))
- return
- return function (el) {
- el.appendChild(showBar("memory-usage", d.statistics.memory_usage))
- }
- }
- }
- function attributeEntry(el, label, value) {
- if (value === null || value == undefined)
- return
- var tr = document.createElement("tr")
- var th = document.createElement("th")
- th.textContent = label
- tr.appendChild(th)
- var td = document.createElement("td")
- if (typeof value == "function")
- value(td)
- else
- td.appendChild(document.createTextNode(value))
- tr.appendChild(td)
- el.appendChild(tr)
- return td
- }
- function showBar(className, v) {
- var span = document.createElement("span")
- span.classList.add("bar")
- span.classList.add(className)
- var bar = document.createElement("span")
- bar.style.width = (v * 100) + "%"
- span.appendChild(bar)
- var label = document.createElement("label")
- label.textContent = (Math.round(v * 100)) + " %"
- span.appendChild(label)
- return span
- }
- function showLinkinfo(config, infobox, gotoAnything, d) {
- var el = infobox.create()
- var h2 = document.createElement("h2")
- a1 = document.createElement("a")
- a1.href = "#"
- a1.onclick = gotoAnything.node(d.source.node)
- a1.textContent = d.source.node.nodeinfo.hostname
- h2.appendChild(a1)
- h2.appendChild(document.createTextNode(" – "))
- a2 = document.createElement("a")
- a2.href = "#"
- a2.onclick = gotoAnything.node(d.target.node)
- a2.textContent = d.target.node.nodeinfo.hostname
- h2.appendChild(a2)
- el.appendChild(h2)
- var attributes = document.createElement("table")
- attributes.classList.add("attributes")
- attributeEntry(attributes, "TQ", showTq(d))
- attributeEntry(attributes, "Entfernung", showDistance(d))
- attributeEntry(attributes, "VPN", d.vpn ? "ja" : "nein")
- el.appendChild(attributes)
- }
- function pushHistory(d) {
- var s = "#!"
- if (d) {
- if ("node" in d)
- s += "n:" + d.node.nodeinfo.node_id
- if ("link" in d)
- s += "l:" + linkId(d.link)
- }
- window.history.pushState(s, undefined, s)
- }
- function gotoHistory(gotoAnything, dict, s) {
- if (!s.startsWith("#!"))
- return
- s = s.slice(2)
- var args = s.split(":")
- if (args[0] === "n") {
- var id = args[1]
- if (id in dict.nodes)
- gotoAnything.node(dict.nodes[id], true, false)()
- }
- if (args[0] === "l") {
- var id = args[1]
- if (id in dict.links)
- gotoAnything.link(dict.links[id], true, false)()
- }
- }
- function gotoBuilder(config, infobox, nodes, links) {
- var markers = {}
- var self = this
- function gotoNode(d, showMap, push) {
- showMap = trueDefault(showMap)
- push = trueDefault(push)
- if (showMap && d.nodeinfo.node_id in markers)
- markers[d.nodeinfo.node_id]()
- nodes(config, infobox, self, d)
- if (push)
- pushHistory( { node: d })
- return false
- }
- function gotoLink(d, showMap, push) {
- showMap = trueDefault(showMap)
- push = trueDefault(push)
- if (showMap && linkId(d) in markers)
- markers[linkId(d)]()
- links(config, infobox, self, d)
- if (push)
- pushHistory( { link: d })
- return false
- }
- function addMarkers(d) {
- markers = d
- }
- this.node = function (d, m, p) { return function () { return gotoNode(d, m, p) }}
- this.link = function (d, m, p) { return function () { return gotoLink(d, m, p) }}
- this.addMarkers = function (d) {
- markers = d
- }
- return this
- }
|