Skip to content
Snippets Groups Projects
Unverified Commit 88906f23 authored by Matthias Schiffer's avatar Matthias Schiffer
Browse files

gluon-status-page: reimplement based on gluon-web

This new status page is significantly smaller than the old one. It always
loads its resources from the same host as the page itself, not requiring
cross-origin requests anymore.

It also uses the common i18n infrastructure of gluon-web.

Fixes #914
parent b1aa5390
No related branches found
No related tags found
No related merge requests found
Showing
with 0 additions and 1507 deletions
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<style>
undivert(style.css)
</style>
<script>
var bootstrapUrl = "/cgi-bin/nodeinfo";
undivert(app.js)
</script>
</head>
<body>
<noscript>Bitte Javascript aktivieren.</noscript>
</body>
</html>
"use strict"
define([ "lib/gui/nodeinfo"
, "lib/gui/statistics"
, "lib/gui/neighbours"
, "lib/gui/menu"
, "lib/streams"
, "lib/neighbourstream"
], function ( NodeInfo
, Statistics
, Neighbours
, Menu
, Streams
, NeighbourStream
) {
function VerticalSplit(parent) {
var el = document.createElement("div")
el.className = "vertical-split"
parent.appendChild(el)
el.push = function (child) {
var header = document.createElement("h2")
header.appendChild(child.title)
var div = document.createElement("div")
div.className = "frame"
div.node = child
div.appendChild(header)
el.appendChild(div)
child.render(div)
return function () {
div.node.destroy()
el.removeChild(div)
}
}
el.clear = function () {
while (el.firstChild) {
el.firstChild.node.destroy()
el.removeChild(el.firstChild)
}
}
return el
}
var h1
return function (mgmtBus, nodesBus) {
function setTitle(node, state) {
var title = node ? node.hostname : "(not connected)"
document.title = title
h1.textContent = title
var icon = document.createElement("i")
icon.className = "icon-down-dir"
h1.appendChild(icon)
switch (state) {
case "connect":
stateIcon.className = "icon-arrows-cw animate-spin"
break
case "fail":
stateIcon.className = "icon-attention"
break
default:
stateIcon.className = ""
break
}
}
var nodes = []
function nodeMenu() {
var myNodes = nodes.slice()
myNodes.sort(function (a, b) {
a = a.hostname
b = b.hostname
return (a < b) ? -1 : (a > b)
})
var menu = myNodes.map(function (d) {
return [d.hostname, function () {
mgmtBus.pushEvent("goto", d)
}]
})
new Menu(menu).apply(this)
}
var header = document.createElement("header")
h1 = document.createElement("h1")
header.appendChild(h1)
h1.onclick = nodeMenu
var icons = document.createElement("p")
icons.className = "icons"
header.appendChild(icons)
var stateIcon = document.createElement("i")
icons.appendChild(stateIcon)
document.body.appendChild(header)
var container = document.createElement("div")
container.className = "container"
document.body.appendChild(container)
setTitle()
var content = new VerticalSplit(container)
function nodeChanged(nodeInfo) {
setTitle(nodeInfo, "connect")
content.clear()
content.push(new NodeInfo(nodeInfo))
}
function nodeNotArrived(nodeInfo) {
setTitle(nodeInfo, "fail")
}
function nodeArrived(nodeInfo, ip) {
setTitle(nodeInfo)
var neighbourStream = new NeighbourStream(mgmtBus, nodesBus, ip)
var statisticsStream = new Streams.Statistics(ip)
content.push(new Statistics(statisticsStream))
content.push(new Neighbours(nodeInfo, neighbourStream, mgmtBus))
}
function newNodes(d) {
nodes = []
for (var nodeId in d)
nodes.push(d[nodeId])
}
mgmtBus.onEvent({ "goto": nodeChanged
, "arrived": nodeArrived
, "gotoFailed": nodeNotArrived
})
nodesBus.map(".nodes").onValue(newNodes)
return this
}
})
"use strict"
define(function () {
return function (menu) {
return function () {
var background = document.createElement("div")
background.className = "menu-background"
document.body.appendChild(background)
document.body.classList.add("noscroll")
var offset = this.getBoundingClientRect()
var container = document.createElement("ul")
container.className = "menu"
container.style.top = offset.top + "px"
container.style.left = offset.left + "px"
background.onclick = destroy
menu.forEach(function (item) {
var li = document.createElement("li")
li.textContent = item[0]
li.action = item[1]
li.onclick = function () {
destroy()
this.action()
}
container.appendChild(li)
})
document.body.appendChild(container)
function destroy() {
document.body.classList.remove("noscroll")
document.body.removeChild(background)
document.body.removeChild(container)
}
}
}
})
"use strict"
define([ "lib/helper", "lib/gui/signalgraph", "lib/gui/signal"],
function (Helper, SignalGraph, Signal) {
var graphColors = ["#396AB1", "#DA7C30", "#3E9651", "#CC2529", "#535154", "#6B4C9A", "#922428", "#948B3D"]
//graphColors = ["#7293CB", "#E1974C", "#84BA5B", "#D35E60", "#808585", "#9067A7", "#AB6857", "#CCC210"];
var inactiveTime = 200
function SignalEntry(graph, color, stream) {
var signal = new Signal(color)
var remove = graph.add(signal)
var unsubscribe = stream.onValue(update)
this.destroy = function () {
unsubscribe()
remove()
}
this.getSignal = function () {
return signal
}
return this
function update(d) {
if ("wifi" in d)
signal.set(d.wifi.inactive > inactiveTime ? null : d.wifi.signal)
}
}
function TableEntry(parent, nodeInfo, color, stream, mgmtBus, signal) {
var el = parent.insertRow()
var tdHostname = el.insertCell()
var tdTQ = el.insertCell()
var tdSignal = el.insertCell()
var tdDistance = el.insertCell()
var tdInactive = el.insertCell()
var marker = document.createElement("span")
marker.textContent = ""
marker.style.color = color
tdHostname.appendChild(marker)
var hostname = document.createElement("span")
tdHostname.appendChild(hostname)
var infoSet = false
var unsubscribe = stream.onValue(update)
el.onmouseenter = function () {
el.classList.add("highlight")
signal.setHighlight(true)
}
el.onmouseleave = function () {
el.classList.remove("highlight")
signal.setHighlight(false)
}
el.destroy = function () {
unsubscribe()
parent.tBodies[0].removeChild(el)
}
return el
function update(d) {
if ("wifi" in d) {
var signal = d.wifi.signal
var inactive = d.wifi.inactive
el.classList.toggle("inactive", inactive > inactiveTime)
tdSignal.textContent = signal
tdInactive.textContent = Math.round(inactive / 1000) + " s"
}
if ("batadv" in d)
tdTQ.textContent = Math.round(d.batadv.tq / 2.55) + " %"
else
tdTQ.textContent = ""
if (infoSet)
return
if ("nodeInfo" in d) {
infoSet = true
var link = document.createElement("a")
link.textContent = d.nodeInfo.hostname
link.href = "#"
link.nodeInfo = d.nodeInfo
link.onclick = function () {
mgmtBus.pushEvent("goto", this.nodeInfo)
return false
}
while (hostname.firstChild)
hostname.removeChild(hostname.firstChild)
hostname.appendChild(link)
try {
var distance = Helper.haversine(nodeInfo.location.latitude, nodeInfo.location.longitude,
d.nodeInfo.location.latitude, d.nodeInfo.location.longitude)
tdDistance.textContent = Math.round(distance * 1000) + " m"
} catch (e) {
tdDistance.textContent = ""
}
} else
hostname.textContent = d.id
}
}
function Interface(parent, nodeInfo, iface, stream, mgmtBus) {
var colors = graphColors.slice(0)
var el = document.createElement("div")
el.ifname = iface
parent.appendChild(el)
var h = document.createElement("h3")
h.textContent = iface
el.appendChild(h)
var table = document.createElement("table")
var tr = table.insertRow()
table.classList.add("datatable")
var th = document.createElement("th")
th.textContent = Helper._("Node")
tr.appendChild(th)
th = document.createElement("th")
th.textContent = "TQ"
tr.appendChild(th)
th = document.createElement("th")
th.textContent = "dBm"
tr.appendChild(th)
th = document.createElement("th")
th.textContent = Helper._("Distance")
tr.appendChild(th)
th = document.createElement("th")
th.textContent = Helper._("Inactive")
tr.appendChild(th)
el.appendChild(table)
var wrapper = document.createElement("div")
wrapper.className = "signalgraph"
el.appendChild(wrapper)
var canvas = document.createElement("canvas")
canvas.className = "signal-history"
canvas.height = 200
wrapper.appendChild(canvas)
var graph = new SignalGraph(canvas, -100, 0, true)
var stopStream = stream.skipDuplicates(sameKeys).onValue(update)
var managedNeighbours = {}
function update(d) {
var notUpdated = new Set()
var id
for (id in managedNeighbours)
notUpdated.add(id)
for (id in d) {
if (!(id in managedNeighbours)) {
var neighbourStream = stream.map("." + id).filter( function (d) { return d !== undefined })
var color = colors.shift()
var signal = new SignalEntry(graph, color, neighbourStream)
managedNeighbours[id] = { views: [ signal,
new TableEntry(table, nodeInfo, color, neighbourStream, mgmtBus, signal.getSignal())
],
color: color
}
}
notUpdated.delete(id)
}
notUpdated.forEach(function (id) {
managedNeighbours[id].views.forEach( function (d) { d.destroy() })
colors.push(managedNeighbours[id].color)
delete managedNeighbours[id]
})
}
el.destroy = function () {
stopStream()
for (var id in managedNeighbours)
managedNeighbours[id].views.forEach( function (d) { d.destroy() })
el.removeChild(h)
el.removeChild(wrapper)
el.removeChild(table)
}
}
function sameKeys(a, b) {
a = Object.keys(a).sort()
b = Object.keys(b).sort()
return !(a < b || a > b)
}
function getter(k) {
return function(obj) {
return obj[k]
}
}
return function (nodeInfo, stream, mgmtBus) {
var stopStream, div
function render(el) {
div = document.createElement("div")
el.appendChild(div)
stopStream = stream.skipDuplicates(sameKeys).onValue(update)
function update(d) {
var have = {}
var remove = []
if (div.hasChildNodes()) {
var children = div.childNodes
for (var i = 0; i < children.length; i++) {
var a = children[i]
if (a.ifname in d)
have[a.ifname] = true
else {
a.destroy()
remove.push(a)
}
}
}
remove.forEach(function (d) { div.removeChild(d) })
for (var k in d) {
if (!(k in have))
new Interface(div, nodeInfo, k, stream.map(getter(k)), mgmtBus)
}
}
}
function destroy() {
stopStream()
while (div.firstChild) {
div.firstChild.destroy()
div.removeChild(div.firstChild)
}
}
return { title: document.createTextNode(Helper._("Neighbors"))
, render: render
, destroy: destroy
}
}
})
"use strict"
define(["lib/helper"], function (Helper) {
return function (nodeInfo) {
var el = document.createElement("div")
update(nodeInfo)
function dlEntry(dl, dict, key, prettyName, transform) {
var v = Helper.dictGet(dict, key.split("."))
if (v === null)
return
if (transform) {
v = transform(v)
}
var dt = document.createElement("dt")
var dd = document.createElement("dd")
dt.textContent = prettyName
if (v instanceof Array) {
var tn = v.map(function (d) { return document.createTextNode(d) })
tn.forEach(function (node) {
if (dd.hasChildNodes())
dd.appendChild(document.createElement("br"))
dd.appendChild(node)
})
} else
dd.textContent = v
dl.appendChild(dt)
dl.appendChild(dd)
}
function enabledDisabled(v) {
if (v) {
return Helper._("enabled");
}
return Helper._("disabled");
}
function update(nodeInfo) {
var list = document.createElement("dl")
dlEntry(list, nodeInfo, "hostname", Helper._("Node name"))
dlEntry(list, nodeInfo, "owner.contact", Helper._("Contact"))
dlEntry(list, nodeInfo, "hardware.model", Helper._("Model"))
dlEntry(list, nodeInfo, "network.mac", Helper._("Primary MAC"))
dlEntry(list, nodeInfo, "network.addresses", Helper._("IP Address"))
dlEntry(list, nodeInfo, "software.firmware.release", Helper._("Firmware"))
dlEntry(list, nodeInfo, "software.fastd.enabled", Helper._("Mesh VPN"), enabledDisabled)
dlEntry(list, nodeInfo, "software.autoupdater.enabled", Helper._("Automatic updates"), enabledDisabled)
dlEntry(list, nodeInfo, "software.autoupdater.branch", Helper._("Branch"))
el.appendChild(list)
}
return { title: document.createTextNode(Helper._("Overview"))
, render: function (d) { d.appendChild(el) }
, destroy: function () {}
}
}
})
"use strict"
define(function () {
return function (color) {
var canvas = document.createElement("canvas")
var ctx = canvas.getContext("2d")
var v = null
var radius = 1.2
var highlight = false
function drawPixel(x, y) {
ctx.beginPath()
ctx.fillStyle = color
ctx.arc(x, y, radius, 0, Math.PI * 2, false)
ctx.closePath()
ctx.fill()
}
this.resize = function (w, h) {
canvas.width = w
canvas.height = h
}
this.draw = function (x, scale) {
var y = scale(v)
ctx.clearRect(x, 0, 5, canvas.height)
if (y)
drawPixel(x, y)
}
this.canvas = canvas
this.set = function (d) {
v = d
}
this.setHighlight = function (d) {
highlight = d
}
this.getHighlight = function () {
return highlight
}
return this
}
})
"use strict"
define(function () {
return function (canvas, min, max) {
var i = 0
var graphWidth
var last = 0
var signals = []
var ctx = canvas.getContext("2d")
resize()
window.addEventListener("resize", resize, false)
window.requestAnimationFrame(step)
function step(timestamp) {
var delta = timestamp - last
if (delta > 40) {
draw()
last = timestamp
}
window.requestAnimationFrame(step)
}
function drawGrid() {
var gridctx = ctx
var nLines = Math.floor(canvas.height / 40)
gridctx.save()
gridctx.lineWidth = 0.5
gridctx.strokeStyle = "rgba(0, 0, 0, 0.25)"
gridctx.fillStyle = "rgba(0, 0, 0, 0.5)"
gridctx.textAlign = "end"
gridctx.textBaseline = "bottom"
gridctx.beginPath()
for (var i = 0; i < nLines; i++) {
var y = canvas.height - i * 40
gridctx.moveTo(0, y - 0.5)
gridctx.lineTo(canvas.width, y - 0.5)
var dBm = Math.round(scaleInverse(y, min, max, canvas.height)) + " dBm"
gridctx.save()
gridctx.strokeStyle = "rgba(255, 255, 255, 0.9)"
gridctx.lineWidth = 4
gridctx.miterLimit = 2
gridctx.strokeText(dBm, canvas.width - 5, y - 2.5)
gridctx.fillText(dBm, canvas.width - 5, y - 2.5)
gridctx.restore()
}
gridctx.stroke()
gridctx.strokeStyle = "rgba(0, 0, 0, 0.83)"
gridctx.lineWidth = 1.5
gridctx.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1)
gridctx.restore()
}
function draw() {
var anyHighlight = signals.some( function (d) { return d.getHighlight() })
signals.forEach( function (d) {
d.draw(i, function (v) {
return scale(v, min, max, canvas.height)
})
})
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.save()
signals.forEach( function (d) {
if (anyHighlight)
ctx.globalAlpha = 0.1
if (d.getHighlight())
ctx.globalAlpha = 1
ctx.drawImage(d.canvas, 0, 0)
})
ctx.restore()
ctx.save()
ctx.beginPath()
ctx.strokeStyle = "rgba(255, 180, 0, 0.15)"
ctx.lineWidth = 5
ctx.moveTo(i + 2.5, 0)
ctx.lineTo(i + 2.5, canvas.height)
ctx.stroke()
drawGrid()
i = (i + 1) % graphWidth
}
function scaleInverse(n, min, max, height) {
return (min * n + max * height - max * n) / height
}
function scale(n, min, max, height) {
return (1 - (n - min) / (max - min)) * height
}
function resize() {
var newWidth = canvas.parentNode.clientWidth
if (newWidth === 0 || newWidth === canvas.width)
return
var lastImage = ctx.getImageData(0, 0, newWidth, canvas.height)
canvas.width = newWidth
graphWidth = canvas.width
ctx.putImageData(lastImage, 0, 0)
signals.forEach( function (d) {
d.resize(canvas.width, canvas.height)
})
}
this.add = function (d) {
signals.push(d)
d.resize(canvas.width, canvas.height)
return function () {
signals = signals.filter( function (e) { return e !== d } )
}
}
return this
}
})
"use strict"
define(["lib/helper"], function (Helper) {
function streamElement(type, stream) {
var el = document.createElement(type)
el.destroy = stream.onValue(update)
function update(d) {
el.textContent = d
}
return el
}
function streamNode(stream) {
var el = document.createTextNode("")
el.destroy = stream.onValue(update)
function update(d) {
el.textContent = d
}
return el
}
function mkRow(table, label, stream, sorted) {
var i = -1
if (sorted) {
for (i = 0; i < table.rows.length; i++) {
if (label < table.rows[i].firstChild.textContent)
break
}
}
var tr = table.insertRow(i)
var th = document.createElement("th")
var td = streamElement("td", stream)
th.textContent = label
tr.appendChild(th)
tr.appendChild(td)
tr.destroy = function () {
td.destroy()
table.tBodies[0].removeChild(tr)
}
return tr
}
function mkTrafficRow(table, children, label, stream, selector) {
var tr = table.insertRow()
var th = document.createElement("th")
th.textContent = label
tr.appendChild(th)
var td = tr.insertCell()
var traffic = stream.slidingWindow(2, 2)
var pkts = streamNode(traffic.map(deltaUptime(selector + ".packets")).map(prettyPackets))
var bw = streamNode(traffic.map(deltaUptime(selector + ".bytes")).map(prettyBits))
var bytes = streamNode(stream.map(selector).map(".bytes").map(prettyBytes))
td.appendChild(pkts)
td.appendChild(document.createElement("br"))
td.appendChild(bw)
td.appendChild(document.createElement("br"))
td.appendChild(bytes)
children.push(pkts)
children.push(bw)
children.push(bytes)
}
function mkMeshVPN(el, stream) {
var children = {}
var init = false
var h = document.createElement("h3")
h.textContent = "Mesh-VPN"
var table = document.createElement("table")
var unsubscribe = stream.onValue( function (d) {
function addPeer(peer, path) {
return { peer: peer, path: path }
}
function addPeers(d, path) {
if (!("peers" in d))
return []
var peers = []
for (var peer in d.peers)
peers.push(addPeer(peer, path + ".peers." + peer))
return peers
}
function addGroup(d, path) {
var peers = []
peers = peers.concat(addPeers(d, path))
if ("groups" in d)
for (var group in d.groups)
peers = peers.concat(addGroup(d.groups[group], path + ".groups." + group))
return peers
}
if (d === undefined)
clear()
else {
if (!init) {
init = true
el.appendChild(h)
el.appendChild(table)
}
var peers = addGroup(d, "")
var paths = new Set(peers.map(function (d) { return d.path } ))
for (var path in children)
if (!paths.has(path)) {
children[path].destroy()
delete children[path]
}
peers.forEach( function (peer) {
if (!(peer.path in children))
children[peer.path] = mkRow(table, peer.peer,
stream.startWith(d)
.map(peer.path)
.filter(function (d) { return d !== undefined })
.map(prettyPeer), true)
})
}
})
function clear() {
if (init) {
init = false
el.removeChild(h)
el.removeChild(table)
}
for (var peer in children)
children[peer].destroy()
children = {}
}
function destroy() {
unsubscribe()
clear()
}
return { destroy: destroy }
}
function deltaUptime(selector) {
return function (d) {
var deltaTime = d[1].uptime - d[0].uptime
var d0 = Helper.dictGet(d[0], selector.split(".").splice(1))
var d1 = Helper.dictGet(d[1], selector.split(".").splice(1))
return (d1 - d0) / deltaTime
}
}
function prettyPeer(d) {
if (d === null)
return Helper._("not connected")
else
return Helper._("connected") + " (" + prettyUptime(d.established) + ")"
}
function prettyPackets(d) {
var v = Helper.formatNumberFixed(d, 0)
return v + " "+ Helper._("Packets/s")
}
function prettyPrefix(prefixes, step, d) {
var prefix = 0
while (d > step && prefix < prefixes.length - 1) {
d /= step
prefix++
}
d = Helper.formatNumber(d, 3)
return d + " " + prefixes[prefix]
}
function prettySize(d) {
return prettyPrefix([ "", "k", "M", "G", "T" ], 1024, d)
}
function prettyBits(d) {
return prettySize(d * 8) + "bps"
}
function prettyBytes(d) {
return prettySize(d) + "B"
}
function prettyUptime(seconds) {
var minutes = Math.round(seconds / 60)
var days = Math.floor(minutes / 1440)
var hours = Math.floor((minutes % 1440) / 60)
minutes = Math.floor(minutes % 60)
var out = ""
if (days === 1)
out += "1 " + Helper._("Day") + ", "
else if (days > 1)
out += days + " " + Helper._("Days") + ", "
out += hours + ":"
if (minutes < 10)
out += "0"
out += minutes
return out
}
function prettyNVRAM(usage) {
return Helper.formatNumber(usage * 100, 3) + "% " + Helper._("used")
}
function prettyLoad(load) {
return Helper.formatNumberFixed(load, 2)
}
function prettyRAM(memory) {
var usage = 1 - (memory.free + memory.buffers + memory.cached) / memory.total
return prettyNVRAM(usage)
}
return function (stream) {
var children = []
var el = document.createElement("div")
var table = document.createElement("table")
children.push(mkRow(table, Helper._("Uptime"), stream.map(".uptime").map(prettyUptime)))
children.push(mkRow(table, Helper._("Load average"), stream.map(".loadavg").map(prettyLoad)))
children.push(mkRow(table, "RAM", stream.map(".memory").map(prettyRAM)))
children.push(mkRow(table, "NVRAM", stream.map(".rootfs_usage").map(prettyNVRAM)))
children.push(mkRow(table, Helper._("Gateway"), stream.map(".gateway")))
children.push(mkRow(table, Helper._("Clients"), stream.map(".clients.total")))
el.appendChild(table)
var h = document.createElement("h3")
h.textContent = Helper._("Traffic")
el.appendChild(h)
table = document.createElement("table")
mkTrafficRow(table, children, Helper._("Transmitted"), stream, ".traffic.tx")
mkTrafficRow(table, children, Helper._("Received"), stream, ".traffic.rx")
mkTrafficRow(table, children, Helper._("Forwarded"), stream, ".traffic.forward")
el.appendChild(table)
children.push(mkMeshVPN(el, stream.map(".mesh_vpn")))
function destroy() {
children.forEach(function (d) {d.destroy()})
}
return { title: document.createTextNode(Helper._("Statistic"))
, render: function (d) { d.appendChild(el) }
, destroy: destroy
}
}
})
"use strict"
define([ "bacon" ], function (Bacon) {
function get(url) {
return Bacon.fromBinder(function(sink) {
var req = new XMLHttpRequest()
req.open("GET", url)
req.onload = function() {
if (req.status === 200)
sink(new Bacon.Next(req.response))
else
sink(new Bacon.Error(req.statusText))
sink(new Bacon.End())
}
req.onerror = function() {
sink(new Bacon.Error("network error"))
sink(new Bacon.End())
}
req.send()
return function () {}
})
}
function getJSON(url) {
return get(url).map(JSON.parse)
}
function buildUrl(ip, object, param) {
var url = "http://[" + ip + "]/cgi-bin/" + object
if (param) url += "?" + param
return url
}
function request(ip, object, param) {
return getJSON(buildUrl(ip, object, param))
}
function dictGet(dict, key) {
var k = key.shift()
if (!(k in dict))
return null
if (key.length === 0)
return dict[k]
return dictGet(dict[k], key)
}
function localizeNumber(d) {
var sep = ','
return d.replace('.', sep)
}
function formatNumberFixed(d, digits) {
return localizeNumber(d.toFixed(digits))
}
function formatNumber(d, digits) {
digits--
for (var v = d; v >= 10 && digits > 0; v /= 10)
digits--
// avoid toPrecision as it might produce strings in exponential notation
return formatNumberFixed(d, digits)
}
function haversine() {
var radians = Array.prototype.map.call(arguments, function(deg) { return deg / 180.0 * Math.PI })
var lat1 = radians[0], lon1 = radians[1], lat2 = radians[2], lon2 = radians[3]
var R = 6372.8 // km
var dLat = lat2 - lat1
var dLon = lon2 - lon1
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(lat1) * Math.cos(lat2)
var c = 2 * Math.asin(Math.sqrt(a))
return R * c
}
function _(s) {
var i, lang, langs, dict = {
"de": {
"Node": "Knoten",
"Distance": "Entfernung",
"Inactive": "Inaktiv",
"Node name": "Knotenname",
"Contact": "Kontakt",
"Model": "Modell",
"Primary MAC": "Primäre MAC",
"IP Address": "IP-Adresse",
"Automatic updates": "Automatische Updates",
"Overview": "Übersicht",
"used": "belegt",
"Uptime": "Laufzeit",
"Load average": "Systemlast",
"Transmitted": "Gesendet",
"Received": "Empfangen",
"Forwarded": "Weitergeleitet",
"Day": "Tag",
"Days": "Tage",
"connected": "verbunden",
"not connected": "nicht verbunden",
"Packets/s": "Pakete/s",
"Statistic": "Statistik",
"Neighbors": "Nachbarknoten",
"Mesh VPN": "Mesh-VPN",
"enabled": "aktiviert",
"disabled": "deaktiviert"
},
"ru": {
"Node": "Узел",
"Distance": "Дальность",
"Inactive": "Не активен",
"Node name": "Имя узла",
"Contact": "Контакт",
"Model": "Модель",
"Primary MAC": "Основной MAC",
"IP Address": "IP Адрес",
"Automatic updates": "Автоматические обновления",
"Overview": "Обзор",
"used": "используется",
"Uptime": "Время работы",
"Load average": "Загрузка системы",
"Gateway": "Шлюз",
"Clients": "Клиенты",
"Transmitted": "Передано",
"Received": "Получено",
"Forwarded": "Переправленно",
"Day": "День",
"Days": "Дней",
"connected": "подключено",
"not connected": "не подключено",
"Packets/s": "Пакетов/c",
"Statistic": "Статистика",
"Traffic": "Трафик",
"Neighbors": "Соседи",
"Firmware": "Прошивка",
"Branch": "Ветка"
}
}
if (navigator.languages)
langs = navigator.languages
else if (navigator.language)
langs = [navigator.language]
else
langs = []
for (i=0; i<langs.length; i++) {
lang = langs[i].split('-')[0]
if (lang == "en")
return s
else if (lang in dict && s in dict[lang])
return dict[lang][s]
}
return s
}
return { buildUrl: buildUrl
, request: request
, getJSON: getJSON
, dictGet: dictGet
, formatNumber: formatNumber
, formatNumberFixed: formatNumberFixed
, haversine: haversine
, _: _
}
})
"use strict"
define([ "bacon"
, "lib/helper"
, "lib/streams"
], function(Bacon, Helper, Streams) {
return function (mgmtBus, nodesBus, ip) {
function nodeQuerier() {
var asked = {}
var timeout = 6000
return function (ifname) {
var now = new Date().getTime()
if (ifname in asked && now - asked[ifname] < timeout)
return Bacon.never()
asked[ifname] = now
return Streams.nodeInfo(ip, ifname).map(function (d) {
return { "ifname": ifname
, "nodeInfo": d
}
})
}
}
var querierAsk = new Bacon.Bus()
var querier = querierAsk.flatMap(nodeQuerier())
querier.map(".nodeInfo").onValue(mgmtBus, "pushEvent", "nodeinfo")
function wrapIfname(ifname, d) {
return [ifname, d]
}
function extractIfname(d) {
var r = {}
for (var station in d) {
var ifname = d[station].ifname
delete d[station].ifname
if (!(ifname in r))
r[ifname] = {}
r[ifname][station] = d[station]
}
return r
}
function stationsStream(ifname) {
return new Streams.Stations(ip, ifname).map(wrapIfname, ifname)
}
function magic(interfaces) {
var ifnames = Object.keys(interfaces)
ifnames.forEach(querierAsk.push)
var wifiStream = Bacon.fromArray(ifnames)
.flatMap(stationsStream)
.scan({}, function (a, b) {
a[b[0]] = b[1]
return a
})
var batadvStream = new Streams.Batadv(ip).toProperty({})
return Bacon.combineWith(combine, wifiStream
, batadvStream.map(extractIfname)
, nodesBus.map(".macs")
)
}
function combine(wifi, batadv, macs) {
var interfaces = combineWithIfnames(wifi, batadv)
for (var ifname in interfaces) {
var stations = interfaces[ifname]
for (var station in stations) {
stations[station].id = station
if (station in macs)
stations[station].nodeInfo = macs[station]
else
querierAsk.push(ifname)
}
}
return interfaces
}
function combineWithIfnames(wifi, batadv) {
var ifnames = Object.keys(wifi).concat(Object.keys(batadv))
// remove duplicates
ifnames.filter(function(e, i) {
return ifnames.indexOf(e) === i
})
var out = {}
ifnames.forEach(function (ifname) {
out[ifname] = combineWifiBatadv(wifi[ifname], batadv[ifname])
})
return out
}
function combineWifiBatadv(wifi, batadv) {
var station
var out = {}
for (station in batadv) {
if (!(station in out))
out[station] = {}
out[station].batadv = batadv[station]
}
for (station in wifi) {
if (!(station in out))
out[station] = {}
out[station].wifi = wifi[station]
}
return out
}
return Helper.request(ip, "interfaces").flatMap(magic)
}
})
"use strict"
define(["bacon", "lib/helper"], function(Bacon, Helper) {
function nodeInfo(ip, ifname) {
return Bacon.fromBinder(function (sink) {
var url = Helper.buildUrl(ip, "dyn/neighbours-nodeinfo", ifname)
var evtSource = new EventSource(url)
evtSource.addEventListener("neighbour", function(e) {
var r = sink(new Bacon.Next(JSON.parse(e.data)))
if (r === Bacon.noMore)
tearDown()
}, false)
evtSource.addEventListener("eot", function() {
evtSource.close()
sink(new Bacon.End())
}, false)
function tearDown() {
evtSource.close()
}
return tearDown
})
}
function simpleStream(url) {
return Bacon.fromBinder(function (sink) {
var evtSource = new EventSource(url)
evtSource.onmessage = function (e) {
var r = sink(new Bacon.Next(JSON.parse(e.data)))
if (r === Bacon.noMore)
tearDown()
}
function tearDown() {
evtSource.close()
}
return tearDown
})
}
function batadv(ip) {
var url = Helper.buildUrl(ip, "dyn/neighbours-batadv")
return simpleStream(url)
}
function stations(ip, ifname) {
var url = Helper.buildUrl(ip, "dyn/stations", ifname)
return simpleStream(url)
}
function statistics(ip) {
var url = Helper.buildUrl(ip, "dyn/statistics")
return simpleStream(url).skipDuplicates(function (a, b) {return (a.uptime === b.uptime)})
}
return { nodeInfo: nodeInfo
, Batadv: batadv
, Stations: stations
, Statistics: statistics
}
})
"use strict"
require([ "bacon"
, "lib/helper"
, "lib/streams"
, "lib/gui"
], function(Bacon, Helper, Streams, GUI) {
var mgmtBus = new Bacon.Bus()
mgmtBus.pushEvent = function (key, a) {
var v = [key].concat(a)
return this.push(v)
}
mgmtBus.onEvent = function (events) {
return this.onValue(function (e) {
var d = e.slice() // shallow copy so calling shift doesn't change it
var ev = d.shift()
if (ev in events)
events[ev].apply(this, d)
})
}
var nodesBusIn = new Bacon.Bus()
var nodesBus = nodesBusIn.scan({ "nodes": {}
, "macs": {}
}, scanNodeInfo)
new GUI(mgmtBus, nodesBus)
mgmtBus.onEvent({ "goto": gotoNode
, "nodeinfo": function (d) { nodesBusIn.push(d) }
})
function tryIp(ip) {
return Helper.request(ip, "nodeinfo").map(function () { return ip })
}
var gotoEpoch = 0
function onEpoch(epoch, f) {
return function (d) {
if (epoch === gotoEpoch)
return f(d)
}
}
function gotoNode(nodeInfo) {
gotoEpoch++
var addresses = nodeInfo.network.addresses.filter(function (d) { return !/^fe80:/.test(d) })
var race = Bacon.fromArray(addresses).flatMap(tryIp).withStateMachine([], function (acc, ev) {
if (ev.isError())
return [acc.concat(ev.error), []]
else if (ev.isEnd() && acc.length > 0)
return [undefined, [new Bacon.Error(acc), ev]]
else if (ev.hasValue())
return [[], [ev, new Bacon.End()]]
})
race.onValue(onEpoch(gotoEpoch, function (d) {
mgmtBus.pushEvent("arrived", [nodeInfo, d])
}))
race.onError(onEpoch(gotoEpoch, function () {
mgmtBus.pushEvent("gotoFailed", nodeInfo)
}))
}
function scanNodeInfo(a, nodeInfo) {
a.nodes[nodeInfo.node_id] = nodeInfo
var mesh = Helper.dictGet(nodeInfo, ["network", "mesh"])
if (mesh)
for (var m in mesh)
for (var ifname in mesh[m].interfaces)
mesh[m].interfaces[ifname].forEach( function (d) {
a.macs[d] = nodeInfo
})
return a
}
var lsavailable = false
try {
localStorage.setItem("t", "t")
localStorage.removeItem("t")
lsavailable = true
} catch(e) {
lsavailable = false
}
if ( lsavailable && localStorage.nodes)
JSON.parse(localStorage.nodes).forEach(nodesBusIn.push)
nodesBus.map(".nodes").onValue(function (nodes) {
var out = []
for (var k in nodes)
out.push(nodes[k])
if (lsavailable)
localStorage.nodes = JSON.stringify(out)
})
var bootstrap = Helper.getJSON(bootstrapUrl)
bootstrap.onError(function () {
console.log("FIXME bootstrapping failed")
})
bootstrap.onValue(function (d) {
mgmtBus.pushEvent("nodeinfo", d)
mgmtBus.pushEvent("goto", d)
})
})
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment