Merge branch 'autoreload'
This commit is contained in:
commit
88d0aa2f92
6
app.js
6
app.js
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -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>"
|
||||
|
|
|
@ -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"))
|
||||
intNodes.forEach( function (d) {
|
||||
oldNodes[d.o.id] = d
|
||||
})
|
||||
|
||||
if (save)
|
||||
save.forEach( function (d) {
|
||||
nodePositions[d.id] = d
|
||||
})
|
||||
}
|
||||
intNodes = data.graph.nodes.map( function (d) {
|
||||
var e
|
||||
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
|
||||
}).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")
|
||||
nodeEnter.each( function (d) {
|
||||
if (nodePositions[d.id]) {
|
||||
d.x = nodePositions[d.id].x
|
||||
d.y = nodePositions[d.id].y
|
||||
|
||||
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.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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
107
lib/linklist.js
107
lib/linklist.js
|
@ -1,75 +1,56 @@
|
|||
define(["tablesort", "tablesort.numeric"], function (Tablesort) {
|
||||
return function(linkScale, router) {
|
||||
var self = this
|
||||
var el
|
||||
define(["sorttable", "virtual-dom"], function (SortTable, V) {
|
||||
function linkName(d) {
|
||||
return d.source.node.nodeinfo.hostname + " – " + d.target.node.nodeinfo.hostname
|
||||
}
|
||||
|
||||
self.render = function (d) {
|
||||
el = document.createElement("div")
|
||||
d.appendChild(el)
|
||||
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])
|
||||
}
|
||||
|
||||
self.setData = function (data) {
|
||||
if (data.graph.links.length === 0)
|
||||
return
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
14
lib/main.js
14
lib/main.js
|
@ -88,14 +88,24 @@ function (config, moment, Router, L, GUI, numeral) {
|
|||
var urls = [ config.dataPath + "nodes.json",
|
||||
config.dataPath + "graph.json"
|
||||
]
|
||||
function update() {
|
||||
return Promise.all(urls.map(getJSON))
|
||||
.then(handleData)
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
144
lib/map.js
144
lib/map.js
|
@ -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 () {
|
||||
|
|
|
@ -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), " +
|
||||
totalClients + " Clients, " +
|
||||
totalGateways + " Gateways"
|
||||
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
|
||||
|
|
145
lib/nodelist.js
145
lib/nodelist.js
|
@ -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) {
|
||||
function showUptime(el, now, d) {
|
||||
var uptime
|
||||
if (d.flags.online && "uptime" in d.statistics)
|
||||
uptime = Math.round(d.statistics.uptime / 3600)
|
||||
else if (!d.flags.online && "lastseen" in d)
|
||||
uptime = Math.round(-(now - d.lastseen) / 3600000)
|
||||
function renderRow(d) {
|
||||
var td1Content = []
|
||||
var aClass = ["hostname", d.flags.online ? "online" : "offline"]
|
||||
|
||||
var s = ""
|
||||
td1Content.push(V.h("a", { className: aClass.join(" "),
|
||||
onclick: router.node(d),
|
||||
href: "#"
|
||||
}, d.nodeinfo.hostname))
|
||||
|
||||
if (uptime !== undefined)
|
||||
if (Math.abs(uptime) >= 24)
|
||||
s = Math.round(uptime / 24) + "d"
|
||||
else
|
||||
s = uptime + "h"
|
||||
if (has_location(d))
|
||||
td1Content.push(V.h("span", {className: "icon ion-location"}))
|
||||
|
||||
el.textContent = s
|
||||
el.setAttribute("data-sort", uptime !== undefined ? -uptime : 0)
|
||||
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 self = this
|
||||
var el
|
||||
var table = new SortTable(headings, 0, renderRow)
|
||||
|
||||
self.render = function (d) {
|
||||
el = document.createElement("div")
|
||||
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")
|
||||
el.appendChild(table.el)
|
||||
}
|
||||
|
||||
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")
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
if (list.length === 0) {
|
||||
while (el.firstChild)
|
||||
el.removeChild(el.firstChild)
|
||||
|
||||
tbody = null
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var h2 = document.createElement("h2")
|
||||
h2.textContent = title
|
||||
el.appendChild(h2)
|
||||
var table = document.createElement("table")
|
||||
el.appendChild(table)
|
||||
if (!tbody) {
|
||||
var h2 = document.createElement("h2")
|
||||
h2.textContent = title
|
||||
el.appendChild(h2)
|
||||
|
||||
var tbody = document.createElement("tbody")
|
||||
var table = document.createElement("table")
|
||||
el.appendChild(table)
|
||||
|
||||
list.forEach( function (d) {
|
||||
var time = moment(d[field]).fromNow()
|
||||
tbody = document.createElement("tbody")
|
||||
tbody.last = V.h("tbody")
|
||||
table.appendChild(tbody)
|
||||
}
|
||||
|
||||
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)
|
||||
var items = list.map( function (d) {
|
||||
var time = moment(d[field]).from(data.now)
|
||||
var td1Content = []
|
||||
|
||||
if (has_location(d)) {
|
||||
var span = document.createElement("span")
|
||||
span.classList.add("icon")
|
||||
span.classList.add("ion-location")
|
||||
td1.appendChild(span)
|
||||
}
|
||||
var aClass = ["hostname", d.flags.online ? "online" : "offline"]
|
||||
|
||||
if ("owner" in d.nodeinfo && config.showContact) {
|
||||
var contact = d.nodeinfo.owner.contact
|
||||
td1.appendChild(document.createTextNode(" – " + contact + ""))
|
||||
}
|
||||
td1Content.push(V.h("a", { className: aClass.join(" "),
|
||||
onclick: router.node(d),
|
||||
href: "#"
|
||||
}, d.nodeinfo.hostname))
|
||||
|
||||
var td2 = document.createElement("td")
|
||||
td2.textContent = time
|
||||
if (has_location(d))
|
||||
td1Content.push(V.h("span", {className: "icon ion-location"}))
|
||||
|
||||
row.appendChild(td1)
|
||||
row.appendChild(td2)
|
||||
tbody.appendChild(row)
|
||||
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
57
lib/sorttable.js
Normal 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
|
||||
}
|
||||
})
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in a new issue