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) intNodes = data.graph.nodes.map( function (d) {
save.forEach( function (d) { var e
nodePositions[d.id] = d if (d.id in oldNodes)
}) e = oldNodes[d.id]
} else
e = {}
links = data.graph.links.filter( function (d) { 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")
nodeEnter.each( function (d) {
if (nodePositions[d.id]) { if (localStorageTest()) {
d.x = nodePositions[d.id].x var save = JSON.parse(localStorage.getItem("graph/nodeposition"))
d.y = nodePositions[d.id].y
if (save) {
var nodePositions = {}
save.forEach( function (d) {
nodePositions[d.id] = d
})
nodeEnter.each( function (d) {
if (nodePositions[d.o.id]) {
d.x = nodePositions[d.o.id].x
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) { var headings = [{ name: "Knoten",
el = document.createElement("div") sort: function (a, b) {
d.appendChild(el) 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])
} }
self.setData = function (data) { this.render = function (d) {
if (data.graph.links.length === 0) var el = document.createElement("div")
return 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() {
return Promise.all(urls.map(getJSON))
.then(handleData)
}
Promise.all(urls.map(getJSON)) update()
.then(handleData)
.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,99 +1,82 @@
define(["tablesort", "tablesort.numeric"], function (Tablesort) { define(["sorttable", "virtual-dom", "numeral"], function (SortTable, V, numeral) {
function getUptime(now, d) {
if (d.flags.online && "uptime" in d.statistics)
return Math.round(d.statistics.uptime / 3600)
else if (!d.flags.online && "lastseen" in d)
return Math.round(-(now - d.lastseen) / 3600000)
}
function showUptime(uptime) {
var s = ""
if (uptime !== undefined)
if (Math.abs(uptime) >= 24)
s = Math.round(uptime / 24) + "d"
else
s = uptime + "h"
return s
}
var headings = [{ name: "Knoten",
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
}]
return function(router) { return function(router) {
function showUptime(el, now, d) { function renderRow(d) {
var uptime var td1Content = []
if (d.flags.online && "uptime" in d.statistics) var aClass = ["hostname", d.flags.online ? "online" : "offline"]
uptime = Math.round(d.statistics.uptime / 3600)
else if (!d.flags.online && "lastseen" in d)
uptime = Math.round(-(now - d.lastseen) / 3600000)
var s = "" td1Content.push(V.h("a", { className: aClass.join(" "),
onclick: router.node(d),
href: "#"
}, d.nodeinfo.hostname))
if (uptime !== undefined) if (has_location(d))
if (Math.abs(uptime) >= 24) td1Content.push(V.h("span", {className: "icon ion-location"}))
s = Math.round(uptime / 24) + "d"
else
s = uptime + "h"
el.textContent = s var td1 = V.h("td", td1Content)
el.setAttribute("data-sort", uptime !== undefined ? -uptime : 0) 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 self = this var table = new SortTable(headings, 0, renderRow)
var el
self.render = function (d) { this.render = function (d) {
el = document.createElement("div") 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") this.setData = function (d) {
var th1 = document.createElement("th") var data = d.nodes.all.map(function (e) {
th1.textContent = "Knoten" var n = Object.create(e)
th1.classList.add("sort-default") n.uptime = getUptime(d.now, e)
tr.appendChild(th1) return n
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")
showUptime(td2, data.now, d)
row.appendChild(td2)
var td3 = document.createElement("td")
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) {
while (el.firstChild)
el.removeChild(el.firstChild)
tbody = null
return return
}
var h2 = document.createElement("h2") if (!tbody) {
h2.textContent = title var h2 = document.createElement("h2")
el.appendChild(h2) h2.textContent = title
var table = document.createElement("table") el.appendChild(h2)
el.appendChild(table)
var tbody = document.createElement("tbody") var table = document.createElement("table")
el.appendChild(table)
list.forEach( function (d) { tbody = document.createElement("tbody")
var time = moment(d[field]).fromNow() tbody.last = V.h("tbody")
table.appendChild(tbody)
}
var row = document.createElement("tr") var items = list.map( function (d) {
var td1 = document.createElement("td") var time = moment(d[field]).from(data.now)
var a = document.createElement("a") var td1Content = []
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 aClass = ["hostname", d.flags.online ? "online" : "offline"]
var span = document.createElement("span")
span.classList.add("icon")
span.classList.add("ion-location")
td1.appendChild(span)
}
if ("owner" in d.nodeinfo && config.showContact) { td1Content.push(V.h("a", { className: aClass.join(" "),
var contact = d.nodeinfo.owner.contact onclick: router.node(d),
td1.appendChild(document.createTextNode(" " + contact + "")) href: "#"
} }, d.nodeinfo.hostname))
var td2 = document.createElement("td") if (has_location(d))
td2.textContent = time td1Content.push(V.h("span", {className: "icon ion-location"}))
row.appendChild(td1) if ("owner" in d.nodeinfo && config.showContact)
row.appendChild(td2) td1Content.push(" - " + d.nodeinfo.owner.contact)
tbody.appendChild(row)
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: {