Merge branch 'autoreload'

This commit is contained in:
Nils Schneider 2015-04-07 17:49:37 +02:00
commit 88d0aa2f92
13 changed files with 469 additions and 331 deletions

6
app.js
View File

@ -10,6 +10,7 @@ require.config({
"d3": "../bower_components/d3/d3.min", "d3": "../bower_components/d3/d3.min",
"numeral": "../bower_components/numeraljs/min/numeral.min", "numeral": "../bower_components/numeraljs/min/numeral.min",
"numeral-intl": "../bower_components/numeraljs/min/languages.min", "numeral-intl": "../bower_components/numeraljs/min/languages.min",
"virtual-dom": "../bower_components/virtual-dom/dist/virtual-dom",
"helper": "../helper" "helper": "../helper"
}, },
shim: { shim: {
@ -17,7 +18,10 @@ require.config({
"tablesort": { "tablesort": {
exports: "Tablesort" exports: "Tablesort"
}, },
"numeral-intl": ["numeral"], "numeral-intl": {
deps: ["numeral"],
exports: "numeral"
},
"tablesort.numeric": ["tablesort"], "tablesort.numeric": ["tablesort"],
"helper": ["numeral-intl"] "helper": ["numeral-intl"]
} }

View File

@ -21,7 +21,8 @@
"r.js": "~2.1.16", "r.js": "~2.1.16",
"d3": "~3.5.5", "d3": "~3.5.5",
"numeraljs": "~1.5.3", "numeraljs": "~1.5.3",
"roboto-fontface": "~0.3.0" "roboto-fontface": "~0.3.0",
"virtual-dom": "~2.0.1"
}, },
"authors": [ "authors": [
"Nils Schneider <nils@nilsschneider.net>" "Nils Schneider <nils@nilsschneider.net>"

View File

@ -1,13 +1,14 @@
define(["d3"], function (d3) { define(["d3"], function (d3) {
return function (config, linkScale, sidebar, router) { return function (config, linkScale, sidebar, router) {
var self = this var self = this
var nodes, links
var svg, vis, link, node var svg, vis, link, node
var nodesDict, linksDict var nodesDict, linksDict
var zoomBehavior var zoomBehavior
var force var force
var el var el
var doAnimation = false var doAnimation = false
var intNodes = []
var highlight
var LINK_DISTANCE = 70 var LINK_DISTANCE = 70
@ -19,18 +20,18 @@ define(["d3"], function (d3) {
if (!localStorageTest()) if (!localStorageTest())
return return
var save = nodes.map( function (d) { var save = intNodes.map( function (d) {
return { id: d.id, x: d.x, y: d.y } return { id: d.o.id, x: d.x, y: d.y }
}) })
localStorage.setItem("graph/nodeposition", JSON.stringify(save)) localStorage.setItem("graph/nodeposition", JSON.stringify(save))
} }
function nodeName(d) { function nodeName(d) {
if (d.node && d.node.nodeinfo) if (d.o.node && d.o.node.nodeinfo)
return d.node.nodeinfo.hostname return d.o.node.nodeinfo.hostname
else else
return d.id return d.o.id
} }
function dragstart(d) { function dragstart(d) {
@ -97,6 +98,48 @@ define(["d3"], function (d3) {
animatePanzoom(translate, scale) animatePanzoom(translate, scale)
} }
function updateHighlight(nopanzoom) {
if (highlight !== undefined)
if (highlight.type === "node") {
var n = nodesDict[highlight.o.nodeinfo.node_id]
if (n) {
link.classed("highlight", false)
node.classed("highlight", function (e) {
return e.o.node === n.o.node && n.o.node !== undefined
})
if (!nopanzoom)
panzoomTo([n.x, n.y], [n.x, n.y])
}
return
} else if (highlight.type === "link") {
var l = linksDict[highlight.o.id]
if (l) {
node.classed("highlight", false)
link.classed("highlight", function (e) {
return e.o === l.o && l.o !== undefined
})
if (!nopanzoom) {
var x = d3.extent([l.source, l.target], function (d) { return d.x })
var y = d3.extent([l.source, l.target], function (d) { return d.y })
panzoomTo([x[0], y[0]], [x[1], y[1]])
}
}
return
}
node.classed("highlight", false)
link.classed("highlight", false)
if (!nopanzoom)
panzoomTo([0, 0], force.size())
}
function tickEvent() { function tickEvent() {
link.selectAll("line") link.selectAll("line")
.attr("x1", function(d) { return d.source.x }) .attr("x1", function(d) { return d.source.x })
@ -132,7 +175,7 @@ define(["d3"], function (d3) {
.gravity(0.05) .gravity(0.05)
.linkDistance(LINK_DISTANCE) .linkDistance(LINK_DISTANCE)
.linkStrength(function (d) { .linkStrength(function (d) {
return 1 / d.tq return 1 / d.o.tq
}) })
.on("tick", tickEvent) .on("tick", tickEvent)
.on("end", savePositions) .on("end", savePositions)
@ -145,24 +188,44 @@ define(["d3"], function (d3) {
.on("dragend", dragend) .on("dragend", dragend)
self.setData = function (data) { self.setData = function (data) {
var nodePositions = {} var oldNodes = {}
if (localStorageTest()) { intNodes.forEach( function (d) {
var save = JSON.parse(localStorage.getItem("graph/nodeposition")) oldNodes[d.o.id] = d
if (save)
save.forEach( function (d) {
nodePositions[d.id] = d
}) })
}
links = data.graph.links.filter( function (d) { intNodes = data.graph.nodes.map( function (d) {
var e
if (d.id in oldNodes)
e = oldNodes[d.id]
else
e = {}
e.o = d
return e
})
var newNodesDict = {}
intNodes.forEach( function (d) {
newNodesDict[d.o.id] = d
})
var intLinks = data.graph.links.filter( function (d) {
return !d.vpn return !d.vpn
}).map( function (d) {
var source = newNodesDict[d.source.id]
var target = newNodesDict[d.target.id]
return {o: d, source: source, target: target}
}) })
link = vis.select("g.links") link = vis.select("g.links")
.selectAll("g.link") .selectAll("g.link")
.data(links, function (d) { return d.id }) .data(intLinks, function (d) { return d.o.id })
link.exit().remove()
var linkEnter = link.enter().append("g") var linkEnter = link.enter().append("g")
.attr("class", "link") .attr("class", "link")
@ -175,34 +238,34 @@ define(["d3"], function (d3) {
.append("title") .append("title")
link.selectAll("line") link.selectAll("line")
.style("stroke", function (d) { return linkScale(d.tq) }) .style("stroke", function (d) { return linkScale(d.o.tq).hex() })
link.selectAll("title").text(showTq) link.selectAll("title").text(function (d) { return showTq(d.o) })
linksDict = {} linksDict = {}
link.each( function (d) { link.each( function (d) {
if (d.source.node && d.target.node) if (d.o.source.node && d.o.target.node)
linksDict[d.id] = d linksDict[d.o.id] = d
}) })
nodes = data.graph.nodes
node = vis.select("g.nodes") node = vis.select("g.nodes")
.selectAll(".node") .selectAll(".node")
.data(nodes, function(d) { return d.id }) .data(intNodes, function(d) { return d.o.id })
node.exit().remove()
var nodeEnter = node.enter().append("circle") var nodeEnter = node.enter().append("circle")
.attr("r", 8) .attr("r", 8)
.on("click", function (d) { .on("click", function (d) {
if (!d3.event.defaultPrevented) if (!d3.event.defaultPrevented)
router.node(d.node)() router.node(d.o.node)()
}) })
.call(draggableNode) .call(draggableNode)
node.attr("class", function (d) { node.attr("class", function (d) {
var s = ["node"] var s = ["node"]
if (!d.node) if (!d.o.node)
s.push("unknown") s.push("unknown")
return s.join(" ") return s.join(" ")
@ -211,65 +274,59 @@ define(["d3"], function (d3) {
nodesDict = {} nodesDict = {}
node.each( function (d) { node.each( function (d) {
if (d.node) if (d.o.node)
nodesDict[d.node.nodeinfo.node_id] = d nodesDict[d.o.node.nodeinfo.node_id] = d
}) })
nodeEnter.append("title") nodeEnter.append("title")
if (localStorageTest()) {
var save = JSON.parse(localStorage.getItem("graph/nodeposition"))
if (save) {
var nodePositions = {}
save.forEach( function (d) {
nodePositions[d.id] = d
})
nodeEnter.each( function (d) { nodeEnter.each( function (d) {
if (nodePositions[d.id]) { if (nodePositions[d.o.id]) {
d.x = nodePositions[d.id].x d.x = nodePositions[d.o.id].x
d.y = nodePositions[d.id].y d.y = nodePositions[d.o.id].y
} }
}) })
}
}
node.selectAll("title").text(nodeName) node.selectAll("title").text(nodeName)
var diameter = graphDiameter(nodes) var diameter = graphDiameter(intNodes)
force.nodes(nodes) force.nodes(intNodes)
.links(links) .links(intLinks)
.size([diameter, diameter]) .size([diameter, diameter])
.start()
updateHighlight(true)
if (node.enter().size() + link.enter().size() > 0)
force.start()
} }
self.resetView = function () { self.resetView = function () {
node.classed("highlight", false) highlight = undefined
link.classed("highlight", false) updateHighlight()
panzoomTo([0, 0], force.size())
doAnimation = true doAnimation = true
} }
self.gotoNode = function (d) { self.gotoNode = function (d) {
link.classed("highlight", false) highlight = {type: "node", o: d}
node.classed("highlight", function (e) { updateHighlight()
return e.node === d && d !== undefined
})
var n = nodesDict[d.nodeinfo.node_id]
if (n)
panzoomTo([n.x, n.y], [n.x, n.y])
doAnimation = true doAnimation = true
} }
self.gotoLink = function (d) { self.gotoLink = function (d) {
node.classed("highlight", false) highlight = {type: "link", o: d}
link.classed("highlight", function (e) { updateHighlight()
return e === d && d !== undefined
})
var l = linksDict[d.id]
if (l) {
var x = d3.extent([l.source, l.target], function (d) { return d.x })
var y = d3.extent([l.source, l.target], function (d) { return d.y })
panzoomTo([x[0], y[0]], [x[1], y[1]])
}
doAnimation = true doAnimation = true
} }

View File

@ -13,11 +13,16 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Linklist,
var linkScale = chroma.scale(chroma.interpolate.bezier(["green", "yellow", "red"])).domain([1, 5]) var linkScale = chroma.scale(chroma.interpolate.bezier(["green", "yellow", "red"])).domain([1, 5])
var sidebar var sidebar
function dataTargetRemove(d) {
dataTargets = dataTargets.filter( function (e) { return d !== e })
}
function removeContent() { function removeContent() {
if (!content) if (!content)
return return
router.removeTarget(content) router.removeTarget(content)
dataTargetRemove(content)
content.destroy() content.destroy()
contentDiv.removeChild(content.div) contentDiv.removeChild(content.div)
content = null content = null

View File

@ -1,75 +1,56 @@
define(["tablesort", "tablesort.numeric"], function (Tablesort) { define(["sorttable", "virtual-dom"], function (SortTable, V) {
return function(linkScale, router) { function linkName(d) {
var self = this return d.source.node.nodeinfo.hostname + " " + d.target.node.nodeinfo.hostname
var el
self.render = function (d) {
el = document.createElement("div")
d.appendChild(el)
} }
self.setData = function (data) { var headings = [{ name: "Knoten",
if (data.graph.links.length === 0) sort: function (a, b) {
return return linkName(a).localeCompare(linkName(b))
},
reverse: false
},
{ name: "TQ",
sort: function (a, b) { return a.tq - b.tq},
reverse: true
},
{ name: "Entfernung",
sort: function (a, b) {
return (a.distance === undefined ? -1 : a.distance) -
(b.distance === undefined ? -1 : b.distance)
},
reverse: true
}]
return function(linkScale, router) {
var table = new SortTable(headings, 2, renderRow)
function renderRow(d) {
var td1Content = [V.h("a", {href: "#", onclick: router.link(d)}, linkName(d))]
if (d.vpn)
td1Content.push(" (VPN)")
var td1 = V.h("td", td1Content)
var td2 = V.h("td", {style: {color: linkScale(d.tq).hex()}}, showTq(d))
var td3 = V.h("td", showDistance(d))
return V.h("tr", [td1, td2, td3])
}
this.render = function (d) {
var el = document.createElement("div")
el.last = V.h("div")
d.appendChild(el)
var h2 = document.createElement("h2") var h2 = document.createElement("h2")
h2.textContent = "Verbindungen" h2.textContent = "Verbindungen"
el.appendChild(h2) el.appendChild(h2)
var table = document.createElement("table") el.appendChild(table.el)
var thead = document.createElement("thead") }
var tr = document.createElement("tr") this.setData = function (d) {
var th1 = document.createElement("th") table.setData(d.graph.links)
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")
data.graph.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 = router.link(d)
td1.appendChild(a)
row.appendChild(td1)
if (d.vpn)
td1.appendChild(document.createTextNode(" (VPN)"))
var td2 = document.createElement("td")
td2.textContent = showTq(d)
td2.style.color = linkScale(d.tq)
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)
} }
} }
}) })

View File

@ -88,14 +88,24 @@ function (config, moment, Router, L, GUI, numeral) {
var urls = [ config.dataPath + "nodes.json", var urls = [ config.dataPath + "nodes.json",
config.dataPath + "graph.json" config.dataPath + "graph.json"
] ]
function update() {
Promise.all(urls.map(getJSON)) return Promise.all(urls.map(getJSON))
.then(handleData) .then(handleData)
}
update()
.then(function (d) { .then(function (d) {
var gui = new GUI(config, router) var gui = new GUI(config, router)
gui.setData(d) gui.setData(d)
router.setData(d) router.setData(d)
router.start() router.start()
window.setInterval(function () {
update().then(function (d) {
gui.setData(d)
router.setData(d)
})
}, 60000)
}) })
.catch(function (e) { .catch(function (e) {
console.log(e) console.log(e)

View File

@ -57,6 +57,7 @@ define(["d3", "leaflet", "moment", "leaflet.label"], function (d3, L, moment) {
return function (config, linkScale, sidebar, router) { return function (config, linkScale, sidebar, router) {
var self = this var self = this
var barycenter var barycenter
var groupOnline, groupOffline, groupNew, groupLost, groupLines
var el = document.createElement("div") var el = document.createElement("div")
el.classList.add("map") el.classList.add("map")
@ -73,6 +74,62 @@ define(["d3", "leaflet", "moment", "leaflet.label"], function (d3, L, moment) {
var nodeDict = {} var nodeDict = {}
var linkDict = {} var linkDict = {}
var highlight
function resetMarkerStyles(nodes, links) {
Object.keys(nodes).forEach( function (d) {
nodes[d].resetStyle()
})
Object.keys(links).forEach( function (d) {
links[d].resetStyle()
})
}
function setView(bounds) {
map.fitBounds(bounds, {paddingTopLeft: [sidebar.getWidth(), 0]})
}
function resetZoom() {
setView(barycenter.getBounds())
}
function goto(m) {
var bounds
if ("getBounds" in m)
bounds = m.getBounds()
else
bounds = L.latLngBounds([m.getLatLng()])
setView(bounds)
return m
}
function updateView(nopanzoom) {
resetMarkerStyles(nodeDict, linkDict)
var m
if (highlight !== undefined)
if (highlight.type === "node") {
m = nodeDict[highlight.o.nodeinfo.node_id]
if (m)
m.setStyle({ fillColor: m.options.color, color: "orange", weight: 20, fillOpacity: 1, opacity: 0.7 })
} else if (highlight.type === "link") {
m = linkDict[highlight.o.id]
if (m)
m.setStyle({ weight: 7, opacity: 1, dashArray: "10, 10" })
}
if (!nopanzoom)
if (m)
goto(m)
else
resetZoom()
}
function calcBarycenter(nodes) { function calcBarycenter(nodes) {
nodes = nodes.map(function (d) { return d.nodeinfo.location }) nodes = nodes.map(function (d) { return d.nodeinfo.location })
@ -94,8 +151,23 @@ define(["d3", "leaflet", "moment", "leaflet.label"], function (d3, L, moment) {
nodeDict = {} nodeDict = {}
linkDict = {} linkDict = {}
if (groupOffline)
groupOffline.clearLayers()
if (groupOnline)
groupOnline.clearLayers()
if (groupNew)
groupNew.clearLayers()
if (groupLost)
groupLost.clearLayers()
if (groupLines)
groupLines.clearLayers()
var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router) var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router)
L.featureGroup(lines).addTo(map) groupLines = L.featureGroup(lines).addTo(map)
barycenter = calcBarycenter(data.nodes.all.filter(has_location)) barycenter = calcBarycenter(data.nodes.all.filter(has_location))
@ -119,71 +191,27 @@ define(["d3", "leaflet", "moment", "leaflet.label"], function (d3, L, moment) {
return iconLost return iconLost
}, router)) }, router))
L.featureGroup(markersOffline).addTo(map) groupOffline = L.featureGroup(markersOffline).addTo(map)
L.featureGroup(markersOnline).addTo(map) groupOnline = L.featureGroup(markersOnline).addTo(map)
L.featureGroup(markersNew).addTo(map) groupNew = L.featureGroup(markersNew).addTo(map)
L.featureGroup(markersLost).addTo(map) groupLost = L.featureGroup(markersLost).addTo(map)
updateView(true)
} }
function resetMarkerStyles(nodes, links) { self.resetView = function () {
Object.keys(nodes).forEach( function (d) { highlight = undefined
nodes[d].resetStyle() updateView()
})
Object.keys(links).forEach( function (d) {
links[d].resetStyle()
})
} }
function setView(bounds) {
map.fitBounds(bounds, {paddingTopLeft: [sidebar.getWidth(), 0]})
}
function resetView() {
resetMarkerStyles(nodeDict, linkDict)
setView(barycenter.getBounds())
}
function goto(dict, id) {
var m = dict[id]
if (m === undefined)
return undefined
var bounds
if ("getBounds" in m)
bounds = m.getBounds()
else
bounds = L.latLngBounds([m.getLatLng()])
setView(bounds)
return m
}
self.resetView = resetView
self.gotoNode = function (d) { self.gotoNode = function (d) {
resetMarkerStyles(nodeDict, linkDict) highlight = {type: "node", o: d}
updateView()
var m = goto(nodeDict, d.nodeinfo.node_id)
if (m)
m.setStyle({ fillColor: m.options.color, color: "orange", weight: 20, fillOpacity: 1, opacity: 0.7 })
else
resetView()
} }
self.gotoLink = function (d) { self.gotoLink = function (d) {
resetMarkerStyles(nodeDict, linkDict) highlight = {type: "link", o: d}
updateView()
var m = goto(linkDict, d.id)
if (m)
m.setStyle({ weight: 7, opacity: 1, dashArray: "10, 10" })
else
resetView()
} }
self.destroy = function () { self.destroy = function () {

View File

@ -1,7 +1,7 @@
define(function () { define(function () {
return function () { return function () {
var self = this var self = this
var p var stats, timestamp
self.setData = function (d) { self.setData = function (d) {
var totalNodes = sum(d.nodes.all.filter(online).map(one)) var totalNodes = sum(d.nodes.all.filter(online).map(one))
@ -12,12 +12,11 @@ define(function () {
return d.flags.gateway return d.flags.gateway
}).map(one)) }).map(one))
p.textContent = totalNodes + " Knoten (online), " + stats.textContent = totalNodes + " Knoten (online), " +
totalClients + " Clients, " + totalClients + " Clients, " +
totalGateways + " Gateways" totalGateways + " Gateways"
p.appendChild(document.createElement("br")) timestamp.textContent = "Diese Daten sind von " + d.timestamp.format("LLLL") + "."
p.appendChild(document.createTextNode("Diese Daten sind von " + d.timestamp.format("LLLL") + "."))
} }
self.render = function (el) { self.render = function (el) {
@ -25,8 +24,13 @@ define(function () {
h2.textContent = "Übersicht" h2.textContent = "Übersicht"
el.appendChild(h2) el.appendChild(h2)
p = document.createElement("p") var p = document.createElement("p")
el.appendChild(p) el.appendChild(p)
stats = document.createTextNode("")
p.appendChild(stats)
p.appendChild(document.createElement("br"))
timestamp = document.createTextNode("")
p.appendChild(timestamp)
} }
return self return self

View File

@ -1,12 +1,12 @@
define(["tablesort", "tablesort.numeric"], function (Tablesort) { define(["sorttable", "virtual-dom", "numeral"], function (SortTable, V, numeral) {
return function(router) { function getUptime(now, d) {
function showUptime(el, now, d) {
var uptime
if (d.flags.online && "uptime" in d.statistics) if (d.flags.online && "uptime" in d.statistics)
uptime = Math.round(d.statistics.uptime / 3600) return Math.round(d.statistics.uptime / 3600)
else if (!d.flags.online && "lastseen" in d) else if (!d.flags.online && "lastseen" in d)
uptime = Math.round(-(now - d.lastseen) / 3600000) return Math.round(-(now - d.lastseen) / 3600000)
}
function showUptime(uptime) {
var s = "" var s = ""
if (uptime !== undefined) if (uptime !== undefined)
@ -15,85 +15,68 @@ define(["tablesort", "tablesort.numeric"], function (Tablesort) {
else else
s = uptime + "h" s = uptime + "h"
el.textContent = s return s
el.setAttribute("data-sort", uptime !== undefined ? -uptime : 0)
} }
var self = this var headings = [{ name: "Knoten",
var el sort: function (a, b) {
return a.nodeinfo.hostname.localeCompare(b.nodeinfo.hostname)
},
reverse: false
},
{ name: "Uptime",
sort: function (a, b) { return a.uptime - b.uptime},
reverse: true
},
{ name: "Clients",
sort: function (a, b) {
return ("clients" in a.statistics ? a.statistics.clients : -1) -
("clients" in b.statistics ? b.statistics.clients : -1)
},
reverse: true
}]
self.render = function (d) { return function(router) {
el = document.createElement("div") function renderRow(d) {
var td1Content = []
var aClass = ["hostname", d.flags.online ? "online" : "offline"]
td1Content.push(V.h("a", { className: aClass.join(" "),
onclick: router.node(d),
href: "#"
}, d.nodeinfo.hostname))
if (has_location(d))
td1Content.push(V.h("span", {className: "icon ion-location"}))
var td1 = V.h("td", td1Content)
var td2 = V.h("td", showUptime(d.uptime))
var td3 = V.h("td", numeral("clients" in d.statistics ? d.statistics.clients : "").format("0,0"))
return V.h("tr", [td1, td2, td3])
}
var table = new SortTable(headings, 0, renderRow)
this.render = function (d) {
var el = document.createElement("div")
d.appendChild(el) d.appendChild(el)
}
self.setData = function (data) {
if (data.nodes.all.length === 0)
return
var h2 = document.createElement("h2") var h2 = document.createElement("h2")
h2.textContent = "Alle Knoten" h2.textContent = "Alle Knoten"
el.appendChild(h2) el.appendChild(h2)
var table = document.createElement("table") el.appendChild(table.el)
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 = "Uptime"
tr.appendChild(th2)
var th3 = document.createElement("th")
th3.textContent = "Clients"
tr.appendChild(th3)
thead.appendChild(tr)
table.appendChild(thead)
var tbody = document.createElement("tbody")
data.nodes.all.forEach( function (d) {
var row = document.createElement("tr")
var td1 = document.createElement("td")
var a = document.createElement("a")
a.textContent = d.nodeinfo.hostname
a.href = "#"
a.onclick = router.node(d)
a.classList.add("hostname")
a.classList.add(d.flags.online ? "online" : "offline")
td1.appendChild(a)
row.appendChild(td1)
if (has_location(d)) {
var span = document.createElement("span")
span.classList.add("icon")
span.classList.add("ion-location")
td1.appendChild(span)
} }
var td2 = document.createElement("td") this.setData = function (d) {
showUptime(td2, data.now, d) var data = d.nodes.all.map(function (e) {
row.appendChild(td2) var n = Object.create(e)
n.uptime = getUptime(d.now, e)
var td3 = document.createElement("td") return n
td3.textContent = "clients" in d.statistics ? d.statistics.clients : ""
row.appendChild(td3)
tbody.appendChild(row)
}) })
table.appendChild(tbody) table.setData(data)
new Tablesort(table)
el.appendChild(table)
} }
} }
}) })

View File

@ -1,4 +1,6 @@
define(["chroma-js"], function (Chroma) { define(["chroma-js", "virtual-dom", "numeral-intl"],
function (Chroma, V, numeral) {
return function () { return function () {
var self = this var self = this
var fwTable, hwTable, autoTable, gwTable var fwTable, hwTable, autoTable, gwTable
@ -23,30 +25,33 @@ define(["chroma-js"], function (Chroma) {
} }
function fillTable(table, data) { function fillTable(table, data) {
if (!table.last)
table.last = V.h("table")
var max = 0 var max = 0
data.forEach(function (d) { data.forEach(function (d) {
if (d[1] > max) if (d[1] > max)
max = d[1] max = d[1]
}) })
data.forEach(function (d) { var items = data.map(function (d) {
var v = d[1] / max var v = d[1] / max
var row = document.createElement("tr")
var th = document.createElement("th")
var td = document.createElement("td")
var span = document.createElement("span")
th.textContent = d[0]
span.style.width = Math.round(v * 100) + "%"
span.style.backgroundColor = scale(v).hex()
var c1 = Chroma.contrast(scale(v), "white") var c1 = Chroma.contrast(scale(v), "white")
var c2 = Chroma.contrast(scale(v), "black") var c2 = Chroma.contrast(scale(v), "black")
span.style.color = c1 > c2 ? "white" : "black"
span.textContent = d[1] var th = V.h("th", d[0])
td.appendChild(span) var td = V.h("td", V.h("span", {style: {
row.appendChild(th) width: Math.round(v * 100) + "%",
row.appendChild(td) backgroundColor: scale(v).hex(),
table.appendChild(row) color: c1 > c2 ? "white" : "black"
}}, numeral(d[1]).format("0,0")))
return V.h("tr", [th, td])
}) })
var tableNew = V.h("table", items)
table = V.patch(table, V.diff(table.last, tableNew))
table.last = tableNew
} }
self.setData = function (data) { self.setData = function (data) {

View File

@ -1,7 +1,7 @@
define(["moment"], function (moment) { define(["moment", "virtual-dom"], function (moment, V) {
return function(config, nodes, field, router, title) { return function(config, nodes, field, router, title) {
var self = this var self = this
var el var el, tbody
self.render = function (d) { self.render = function (d) {
el = document.createElement("div") el = document.createElement("div")
@ -11,52 +11,54 @@ define(["moment"], function (moment) {
self.setData = function (data) { self.setData = function (data) {
var list = data.nodes[nodes] var list = data.nodes[nodes]
if (list.length === 0) if (list.length === 0) {
return while (el.firstChild)
el.removeChild(el.firstChild)
tbody = null
return
}
if (!tbody) {
var h2 = document.createElement("h2") var h2 = document.createElement("h2")
h2.textContent = title h2.textContent = title
el.appendChild(h2) el.appendChild(h2)
var table = document.createElement("table") var table = document.createElement("table")
el.appendChild(table) el.appendChild(table)
var tbody = document.createElement("tbody") tbody = document.createElement("tbody")
tbody.last = V.h("tbody")
list.forEach( function (d) { table.appendChild(tbody)
var time = moment(d[field]).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 = router.node(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 && config.showContact) { var items = list.map( function (d) {
var contact = d.nodeinfo.owner.contact var time = moment(d[field]).from(data.now)
td1.appendChild(document.createTextNode(" " + contact + "")) var td1Content = []
}
var td2 = document.createElement("td") var aClass = ["hostname", d.flags.online ? "online" : "offline"]
td2.textContent = time
row.appendChild(td1) td1Content.push(V.h("a", { className: aClass.join(" "),
row.appendChild(td2) onclick: router.node(d),
tbody.appendChild(row) href: "#"
}, d.nodeinfo.hostname))
if (has_location(d))
td1Content.push(V.h("span", {className: "icon ion-location"}))
if ("owner" in d.nodeinfo && config.showContact)
td1Content.push(" - " + d.nodeinfo.owner.contact)
var td1 = V.h("td", td1Content)
var td2 = V.h("td", time)
return V.h("tr", [td1, td2])
}) })
table.appendChild(tbody) var tbodyNew = V.h("tbody", items)
el.appendChild(table) tbody = V.patch(tbody, V.diff(tbody.last, tbodyNew))
tbody.last = tbodyNew
} }
return self return self

57
lib/sorttable.js Normal file
View File

@ -0,0 +1,57 @@
define(["virtual-dom"], function (V) {
return function(headings, sortIndex, renderRow) {
var data
var sortReverse = false
var el = document.createElement("table")
var elLast = V.h("table")
function sortTable(i) {
sortReverse = i === sortIndex ? !sortReverse : false
sortIndex = i
updateView()
}
function sortTableHandler(i) {
return function () { sortTable(i) }
}
function updateView() {
var children = []
if (data.length !== 0) {
var th = headings.map(function (d, i) {
var properties = { onclick: sortTableHandler(i),
className: "sort-header"
}
if (sortIndex === i)
properties.className += sortReverse ? " sort-up" : " sort-down"
return V.h("th", properties, d.name)
})
var links = data.slice(0).sort(headings[sortIndex].sort)
if (headings[sortIndex].reverse ? !sortReverse : sortReverse)
links = links.reverse()
children.push(V.h("thead", V.h("tr", th)))
children.push(V.h("tbody", links.map(renderRow)))
}
var elNew = V.h("table", children)
el = V.patch(el, V.diff(elLast, elNew))
elLast = elNew
}
this.setData = function (d) {
data = d
updateView()
}
this.el = el
return this
}
})

View File

@ -19,7 +19,8 @@ module.exports = function (grunt) {
"strict": [2, "never"], "strict": [2, "never"],
"no-multi-spaces": 0, "no-multi-spaces": 0,
"no-new": 0, "no-new": 0,
"no-shadow": 0 "no-shadow": 0,
"no-use-before-define": [1, "nofunc"]
} }
}, },
sources: { sources: {