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

View File

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

View File

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

View File

@ -1,75 +1,56 @@
define(["tablesort", "tablesort.numeric"], function (Tablesort) {
return function(linkScale, router) {
var self = this
var el
self.render = function (d) {
el = document.createElement("div")
d.appendChild(el)
define(["sorttable", "virtual-dom"], function (SortTable, V) {
function linkName(d) {
return d.source.node.nodeinfo.hostname + " " + d.target.node.nodeinfo.hostname
}
self.setData = function (data) {
if (data.graph.links.length === 0)
return
var headings = [{ name: "Knoten",
sort: function (a, b) {
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")
h2.textContent = "Verbindungen"
el.appendChild(h2)
var table = document.createElement("table")
var thead = document.createElement("thead")
el.appendChild(table.el)
}
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")
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)
this.setData = function (d) {
table.setData(d.graph.links)
}
}
})

View File

@ -88,14 +88,24 @@ function (config, moment, Router, L, GUI, numeral) {
var urls = [ config.dataPath + "nodes.json",
config.dataPath + "graph.json"
]
Promise.all(urls.map(getJSON))
function update() {
return Promise.all(urls.map(getJSON))
.then(handleData)
}
update()
.then(function (d) {
var gui = new GUI(config, router)
gui.setData(d)
router.setData(d)
router.start()
window.setInterval(function () {
update().then(function (d) {
gui.setData(d)
router.setData(d)
})
}, 60000)
})
.catch(function (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) {
var self = this
var barycenter
var groupOnline, groupOffline, groupNew, groupLost, groupLines
var el = document.createElement("div")
el.classList.add("map")
@ -73,6 +74,62 @@ define(["d3", "leaflet", "moment", "leaflet.label"], function (d3, L, moment) {
var nodeDict = {}
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) {
nodes = nodes.map(function (d) { return d.nodeinfo.location })
@ -94,8 +151,23 @@ define(["d3", "leaflet", "moment", "leaflet.label"], function (d3, L, moment) {
nodeDict = {}
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)
L.featureGroup(lines).addTo(map)
groupLines = L.featureGroup(lines).addTo(map)
barycenter = calcBarycenter(data.nodes.all.filter(has_location))
@ -119,71 +191,27 @@ define(["d3", "leaflet", "moment", "leaflet.label"], function (d3, L, moment) {
return iconLost
}, router))
L.featureGroup(markersOffline).addTo(map)
L.featureGroup(markersOnline).addTo(map)
L.featureGroup(markersNew).addTo(map)
L.featureGroup(markersLost).addTo(map)
groupOffline = L.featureGroup(markersOffline).addTo(map)
groupOnline = L.featureGroup(markersOnline).addTo(map)
groupNew = L.featureGroup(markersNew).addTo(map)
groupLost = L.featureGroup(markersLost).addTo(map)
updateView(true)
}
function resetMarkerStyles(nodes, links) {
Object.keys(nodes).forEach( function (d) {
nodes[d].resetStyle()
})
Object.keys(links).forEach( function (d) {
links[d].resetStyle()
})
self.resetView = function () {
highlight = undefined
updateView()
}
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) {
resetMarkerStyles(nodeDict, linkDict)
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()
highlight = {type: "node", o: d}
updateView()
}
self.gotoLink = function (d) {
resetMarkerStyles(nodeDict, linkDict)
var m = goto(linkDict, d.id)
if (m)
m.setStyle({ weight: 7, opacity: 1, dashArray: "10, 10" })
else
resetView()
highlight = {type: "link", o: d}
updateView()
}
self.destroy = function () {

View File

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

View File

@ -1,12 +1,12 @@
define(["tablesort", "tablesort.numeric"], function (Tablesort) {
return function(router) {
function showUptime(el, now, d) {
var uptime
define(["sorttable", "virtual-dom", "numeral"], function (SortTable, V, numeral) {
function getUptime(now, d) {
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)
uptime = Math.round(-(now - d.lastseen) / 3600000)
return Math.round(-(now - d.lastseen) / 3600000)
}
function showUptime(uptime) {
var s = ""
if (uptime !== undefined)
@ -15,85 +15,68 @@ define(["tablesort", "tablesort.numeric"], function (Tablesort) {
else
s = uptime + "h"
el.textContent = s
el.setAttribute("data-sort", uptime !== undefined ? -uptime : 0)
return s
}
var self = this
var el
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
}]
self.render = function (d) {
el = document.createElement("div")
return function(router) {
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)
}
self.setData = function (data) {
if (data.nodes.all.length === 0)
return
var h2 = document.createElement("h2")
h2.textContent = "Alle Knoten"
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"
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)
el.appendChild(table.el)
}
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)
this.setData = function (d) {
var data = d.nodes.all.map(function (e) {
var n = Object.create(e)
n.uptime = getUptime(d.now, e)
return n
})
table.appendChild(tbody)
new Tablesort(table)
el.appendChild(table)
table.setData(data)
}
}
})

View File

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

View File

@ -1,7 +1,7 @@
define(["moment"], function (moment) {
define(["moment", "virtual-dom"], function (moment, V) {
return function(config, nodes, field, router, title) {
var self = this
var el
var el, tbody
self.render = function (d) {
el = document.createElement("div")
@ -11,52 +11,54 @@ define(["moment"], function (moment) {
self.setData = function (data) {
var list = data.nodes[nodes]
if (list.length === 0)
return
if (list.length === 0) {
while (el.firstChild)
el.removeChild(el.firstChild)
tbody = null
return
}
if (!tbody) {
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[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)
tbody = document.createElement("tbody")
tbody.last = V.h("tbody")
table.appendChild(tbody)
}
if ("owner" in d.nodeinfo && config.showContact) {
var contact = d.nodeinfo.owner.contact
td1.appendChild(document.createTextNode(" " + contact + ""))
}
var items = list.map( function (d) {
var time = moment(d[field]).from(data.now)
var td1Content = []
var td2 = document.createElement("td")
td2.textContent = time
var aClass = ["hostname", d.flags.online ? "online" : "offline"]
row.appendChild(td1)
row.appendChild(td2)
tbody.appendChild(row)
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"}))
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)
el.appendChild(table)
var tbodyNew = V.h("tbody", items)
tbody = V.patch(tbody, V.diff(tbody.last, tbodyNew))
tbody.last = tbodyNew
}
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"],
"no-multi-spaces": 0,
"no-new": 0,
"no-shadow": 0
"no-shadow": 0,
"no-use-before-define": [1, "nofunc"]
}
},
sources: {