change code style to ffrgb/meshviewer fork

This commit is contained in:
Milan Pässler 2017-03-17 03:14:57 +01:00
parent 59ba0ba29e
commit 418b630e02
42 changed files with 3505 additions and 3154 deletions

View file

@ -11,6 +11,6 @@ trim_trailing_whitespace = true
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true
[*.{js,html,scss,json}] [*.{js,html,scss,json,yml,md}]
indent_size = 2 indent_size = 2
indent_style = space indent_style = space

2
.gitignore vendored
View file

@ -3,3 +3,5 @@ node_modules/
build/ build/
.sass-cache/ .sass-cache/
config.json config.json
.idea/
.eslintrc

View file

@ -1,24 +1,24 @@
module.exports = function (grunt) { module.exports = function (grunt) {
grunt.loadNpmTasks("grunt-git-describe") grunt.loadNpmTasks("grunt-git-describe");
grunt.initConfig({ grunt.initConfig({
"git-describe": { "git-describe": {
options: {}, options: {},
default: {} default: {}
} }
}) });
grunt.registerTask("saveRevision", function () { grunt.registerTask("saveRevision", function () {
grunt.event.once("git-describe", function (rev) { grunt.event.once("git-describe", function (rev) {
grunt.option("gitRevision", rev) grunt.option("gitRevision", rev);
}) });
grunt.task.run("git-describe") grunt.task.run("git-describe");
}) });
grunt.loadTasks("tasks") grunt.loadTasks("tasks");
grunt.registerTask("default", ["bower-install-simple", "lint", "saveRevision", "copy", "sass", "postcss", "requirejs"]) grunt.registerTask("default", ["bower-install-simple", "lint", "saveRevision", "copy", "sass", "postcss", "requirejs"]);
grunt.registerTask("lint", ["eslint"]) grunt.registerTask("lint", ["eslint"]);
grunt.registerTask("dev", ["default", "connect:server", "watch"]) grunt.registerTask("dev", ["default", "connect:server", "watch"]);
} };

6
app.js
View file

@ -29,8 +29,8 @@ require.config({
"tablesort.numeric": ["tablesort"], "tablesort.numeric": ["tablesort"],
"helper": ["numeral-intl"] "helper": ["numeral-intl"]
} }
}) });
require(["main", "helper"], function (main) { require(["main", "helper"], function (main) {
getJSON("config.json").then(main) getJSON("config.json").then(main);
}) });

View file

@ -6,4 +6,4 @@
wrap: true, wrap: true,
optimize: "uglify", optimize: "uglify",
out: "app-combined.js" out: "app-combined.js"
}) });

233
helper.js
View file

@ -1,228 +1,241 @@
function get(url) { function get(url) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest() var req = new XMLHttpRequest();
req.open('GET', url) req.open('GET', url);
req.onload = function () { req.onload = function () {
if (req.status == 200) { if (req.status == 200) {
resolve(req.response) resolve(req.response);
} }
else { else {
reject(Error(req.statusText)) reject(Error(req.statusText));
}
} }
};
req.onerror = function () { req.onerror = function () {
reject(Error("Network Error")) reject(Error("Network Error"));
} };
req.send() req.send();
}) });
} }
function getJSON(url) { function getJSON(url) {
return get(url).then(JSON.parse) return get(url).then(JSON.parse);
} }
function sortByKey(key, d) { function sortByKey(key, d) {
return d.slice().sort(function (a, b) { return d.slice().sort(function (a, b) {
return a[key] - b[key] return a[key] - b[key];
}).reverse() }).reverse();
} }
function limit(key, m, d) { function limit(key, m, d) {
return d.filter(function (d) { return d.filter(function (d) {
return d[key].isAfter(m) return d[key].isAfter(m);
}) });
} }
function sum(a) { function sum(a) {
return a.reduce(function (a, b) { return a.reduce(function (a, b) {
return a + b return a + b;
}, 0) }, 0);
} }
function one() { function one() {
return 1 return 1;
} }
function trueDefault(d) { function trueDefault(d) {
return d === undefined ? true : d return d === undefined ? true : d;
} }
function dictGet(dict, key) { function dictGet(dict, key) {
var k = key.shift() var k = key.shift();
if (!(k in dict)) if (!(k in dict)) {
return null return null;
}
if (key.length == 0) if (key.length == 0) {
return dict[k] return dict[k];
}
return dictGet(dict[k], key) return dictGet(dict[k], key);
} }
function localStorageTest() { function localStorageTest() {
var test = 'test' var test = 'test';
try { try {
localStorage.setItem(test, test) localStorage.setItem(test, test);
localStorage.removeItem(test) localStorage.removeItem(test);
return true return true;
} catch (e) { } catch (e) {
return false return false;
} }
} }
function listReplace(s, subst) { function listReplace(s, subst) {
for (key in subst) { for (key in subst) {
var re = new RegExp(key, 'g') var re = new RegExp(key, 'g');
s = s.replace(re, subst[key]) s = s.replace(re, subst[key]);
} }
return s return s;
} }
/* Helpers working with nodes */ /* Helpers working with nodes */
function offline(d) { function offline(d) {
return !d.flags.online return !d.flags.online;
} }
function online(d) { function online(d) {
return d.flags.online return d.flags.online;
} }
function has_location(d) { function has_location(d) {
return "location" in d.nodeinfo && return "location" in d.nodeinfo &&
Math.abs(d.nodeinfo.location.latitude) < 90 && Math.abs(d.nodeinfo.location.latitude) < 90 &&
Math.abs(d.nodeinfo.location.longitude) < 180 Math.abs(d.nodeinfo.location.longitude) < 180;
} }
function subtract(a, b) { function subtract(a, b) {
var ids = {} var ids = {};
b.forEach(function (d) { b.forEach(function (d) {
ids[d.nodeinfo.node_id] = true ids[d.nodeinfo.node_id] = true;
}) });
return a.filter(function (d) { return a.filter(function (d) {
return !(d.nodeinfo.node_id in ids) return !(d.nodeinfo.node_id in ids);
}) });
} }
/* Helpers working with links */ /* Helpers working with links */
function showDistance(d) { function showDistance(d) {
if (isNaN(d.distance)) if (isNaN(d.distance)) {
return return;
}
return numeral(d.distance).format("0,0") + " m" return numeral(d.distance).format("0,0") + " m";
} }
function showTq(d) { function showTq(d) {
return numeral(1/d.tq).format("0%") return numeral(1 / d.tq).format("0%");
} }
/* Infobox stuff (XXX: move to module) */ /* Infobox stuff (XXX: move to module) */
function attributeEntry(el, label, value) { function attributeEntry(el, label, value) {
if (value === null || value == undefined) if (value === null || value == undefined) {
return return;
var tr = document.createElement("tr")
var th = document.createElement("th")
if (typeof label === "string")
th.textContent = label
else {
th.appendChild(label)
tr.className = "routerpic"
} }
tr.appendChild(th) var tr = document.createElement("tr");
var th = document.createElement("th");
if (typeof label === "string") {
th.textContent = label;
} else {
th.appendChild(label);
tr.className = "routerpic";
}
var td = document.createElement("td") tr.appendChild(th);
if (typeof value == "function") var td = document.createElement("td");
value(td)
else
td.appendChild(document.createTextNode(value))
tr.appendChild(td) if (typeof value == "function") {
value(td);
} else {
td.appendChild(document.createTextNode(value));
}
el.appendChild(tr) tr.appendChild(td);
return td el.appendChild(tr);
return td;
} }
function createIframe(opt, width, height) { function createIframe(opt, width, height) {
el = document.createElement("iframe") el = document.createElement("iframe");
width = typeof width !== 'undefined' ? width : '100%' width = typeof width !== 'undefined' ? width : '100%';
height = typeof height !== 'undefined' ? height : '350px' height = typeof height !== 'undefined' ? height : '350px';
if (opt.src) if (opt.src) {
el.src = opt.src el.src = opt.src;
else } else {
el.src = opt el.src = opt;
}
if (opt.frameBorder) if (opt.frameBorder) {
el.frameBorder = opt.frameBorder el.frameBorder = opt.frameBorder;
else } else {
el.frameBorder = 1 el.frameBorder = 1;
}
if (opt.width) if (opt.width) {
el.width = opt.width el.width = opt.width;
else } else {
el.width = width el.width = width;
}
if (opt.height) if (opt.height) {
el.height = opt.height el.height = opt.height;
else } else {
el.height = height el.height = height;
}
el.scrolling = "no" el.scrolling = "no";
el.seamless = "seamless" el.seamless = "seamless";
return el return el;
} }
function showStat(o, subst) { function showStat(o, subst) {
var content, caption var content, caption;
subst = typeof subst !== 'undefined' ? subst : {} subst = typeof subst !== 'undefined' ? subst : {};
if (o.thumbnail) { if (o.thumbnail) {
content = document.createElement("img") content = document.createElement("img");
content.src = listReplace(o.thumbnail, subst) content.src = listReplace(o.thumbnail, subst);
} }
if (o.caption) { if (o.caption) {
caption = listReplace(o.caption, subst) caption = listReplace(o.caption, subst);
if (!content) if (!content) {
content = document.createTextNode(caption) content = document.createTextNode(caption);
}
} }
if (o.iframe) { if (o.iframe) {
content = createIframe(o.iframe, o.width, o.height) content = createIframe(o.iframe, o.width, o.height);
if (o.iframe.src) if (o.iframe.src) {
content.src = listReplace(o.iframe.src, subst) content.src = listReplace(o.iframe.src, subst);
else } else {
content.src = listReplace(o.iframe, subst) content.src = listReplace(o.iframe, subst);
}
} }
var p = document.createElement("p") var p = document.createElement("p");
if (o.href) { if (o.href) {
var link = document.createElement("a") var link = document.createElement("a");
link.target = "_blank" link.target = "_blank";
link.href = listReplace(o.href, subst) link.href = listReplace(o.href, subst);
link.appendChild(content) link.appendChild(content);
if (caption && o.thumbnail) if (caption && o.thumbnail) {
link.title = caption link.title = caption;
}
p.appendChild(link)
} else p.appendChild(link);
p.appendChild(content) } else {
p.appendChild(content);
return p }
return p;
} }

View file

@ -10,7 +10,7 @@
<script src="vendor/es6-shim/es6-shim.min.js"></script> <script src="vendor/es6-shim/es6-shim.min.js"></script>
<script src="app.js"></script> <script src="app.js"></script>
<script> <script>
console.log("Version: #revision#") console.log("Version: #revision#");
</script> </script>
</head> </head>
<body> <body>

View file

@ -1,38 +1,38 @@
define(function () { define(function () {
return function () { return function () {
this.render = function (d) { this.render = function (d) {
var el = document.createElement("div") var el = document.createElement("div");
d.appendChild(el) d.appendChild(el);
var s = "<h2>Über HopGlass</h2>" var s = "<h2>Über HopGlass</h2>";
s += "<p>Mit Doppelklick und Shift+Doppelklick kann man in der Karte " s += "<p>Mit Doppelklick und Shift+Doppelklick kann man in der Karte ";
s += "auch zoomen.</p>" s += "auch zoomen.</p>";
s += "<h3>AGPL 3</h3>" s += "<h3>AGPL 3</h3>";
s += "<p>Copyright (C) Milan Pässler</p>" s += "<p>Copyright (C) Milan Pässler</p>";
s += "<p>Copyright (C) Nils Schneider</p>" s += "<p>Copyright (C) Nils Schneider</p>";
s += "<p>This program is free software: you can redistribute it and/or " s += "<p>This program is free software: you can redistribute it and/or ";
s += "modify it under the terms of the GNU Affero General Public " s += "modify it under the terms of the GNU Affero General Public ";
s += "License as published by the Free Software Foundation, either " s += "License as published by the Free Software Foundation, either ";
s += "version 3 of the License, or (at your option) any later version.</p>" s += "version 3 of the License, or (at your option) any later version.</p>";
s += "<p>This program is distributed in the hope that it will be useful, " s += "<p>This program is distributed in the hope that it will be useful, ";
s += "but WITHOUT ANY WARRANTY; without even the implied warranty of " s += "but WITHOUT ANY WARRANTY; without even the implied warranty of ";
s += "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the " s += "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ";
s += "GNU Affero General Public License for more details.</p>" s += "GNU Affero General Public License for more details.</p>";
s += "<p>You should have received a copy of the GNU Affero General " s += "<p>You should have received a copy of the GNU Affero General ";
s += "Public License along with this program. If not, see " s += "Public License along with this program. If not, see ";
s += "<a href=\"https://www.gnu.org/licenses/\">" s += "<a href=\"https://www.gnu.org/licenses/\">";
s += "https://www.gnu.org/licenses/</a>.</p>" s += "https://www.gnu.org/licenses/</a>.</p>";
s += "<p>The source code is available at " s += "<p>The source code is available at ";
s += "<a href=\"https://github.com/plumpudding/hopglass\">" s += "<a href=\"https://github.com/plumpudding/hopglass\">";
s += "https://github.com/plumpudding/hopglass</a>." s += "https://github.com/plumpudding/hopglass</a>.";
el.innerHTML = s el.innerHTML = s;
} };
} };
}) });

View file

@ -1,20 +1,21 @@
define([], function () { define([], function () {
return function (tag) { return function (tag) {
if (!tag) if (!tag) {
tag = "div" tag = "div";
}
var self = this var self = this;
var container = document.createElement(tag) var container = document.createElement(tag);
self.add = function (d) { self.add = function (d) {
d.render(container) d.render(container);
} };
self.render = function (el) { self.render = function (el) {
el.appendChild(container) el.appendChild(container);
} };
return self return self;
} };
}) });

View file

@ -1,80 +1,91 @@
define(["filters/nodefilter"], function (NodeFilter) { define(["filters/nodefilter"], function (NodeFilter) {
return function () { return function () {
var targets = [] var targets = [];
var filterObservers = [] var filterObservers = [];
var filters = [] var filters = [];
var filteredData var filteredData;
var data var data;
function remove(d) { function remove(d) {
targets = targets.filter( function (e) { return d !== e } ) targets = targets.filter(function (e) {
return d !== e;
});
} }
function add(d) { function add(d) {
targets.push(d) targets.push(d);
if (filteredData !== undefined) if (filteredData !== undefined) {
d.setData(filteredData) d.setData(filteredData);
}
} }
function setData(d) { function setData(d) {
data = d data = d;
refresh() refresh();
} }
function refresh() { function refresh() {
if (data === undefined) if (data === undefined) {
return return;
}
var filter = filters.reduce(function (a, f) { var filter = filters.reduce(function (a, f) {
return function (d) { return function (d) {
return a(d) && f.run(d) return a(d) && f.run(d);
} };
}, function () { return true }) }, function () {
return true;
});
filteredData = new NodeFilter(filter)(data) filteredData = new NodeFilter(filter)(data);
targets.forEach(function (t) { targets.forEach(function (t) {
t.setData(filteredData) t.setData(filteredData);
}) });
} }
function notifyObservers() { function notifyObservers() {
filterObservers.forEach(function (d) { filterObservers.forEach(function (d) {
d.filtersChanged(filters) d.filtersChanged(filters);
}) });
} }
function addFilter(d) { function addFilter(d) {
filters.push(d) filters.push(d);
notifyObservers() notifyObservers();
d.setRefresh(refresh) d.setRefresh(refresh);
refresh() refresh();
} }
function removeFilter(d) { function removeFilter(d) {
filters = filters.filter( function (e) { return d !== e } ) filters = filters.filter(function (e) {
notifyObservers() return d !== e;
refresh() });
notifyObservers();
refresh();
} }
function watchFilters(d) { function watchFilters(d) {
filterObservers.push(d) filterObservers.push(d);
d.filtersChanged(filters) d.filtersChanged(filters);
return function () { return function () {
filterObservers = filterObservers.filter( function (e) { return d !== e }) filterObservers = filterObservers.filter(function (e) {
} return d !== e;
});
};
} }
return { add: add, return {
add: add,
remove: remove, remove: remove,
setData: setData, setData: setData,
addFilter: addFilter, addFilter: addFilter,
removeFilter: removeFilter, removeFilter: removeFilter,
watchFilters: watchFilters, watchFilters: watchFilters,
refresh: refresh refresh: refresh
} };
} };
}) });

View file

@ -1,40 +1,43 @@
define([], function () { define([], function () {
return function (distributor) { return function (distributor) {
var container = document.createElement("ul") var container = document.createElement("ul");
container.classList.add("filters") container.classList.add("filters");
var div = document.createElement("div") var div = document.createElement("div");
function render(el) { function render(el) {
el.appendChild(div) el.appendChild(div);
} }
function filtersChanged(filters) { function filtersChanged(filters) {
while (container.firstChild) while (container.firstChild) {
container.removeChild(container.firstChild) container.removeChild(container.firstChild);
}
filters.forEach(function (d) { filters.forEach(function (d) {
var li = document.createElement("li") var li = document.createElement("li");
var div = document.createElement("div") var div = document.createElement("div");
container.appendChild(li) container.appendChild(li);
li.appendChild(div) li.appendChild(div);
d.render(div) d.render(div);
var button = document.createElement("button") var button = document.createElement("button");
button.textContent = "" button.textContent = "";
button.onclick = function () { button.onclick = function () {
distributor.removeFilter(d) distributor.removeFilter(d);
} };
li.appendChild(button) li.appendChild(button);
}) });
if (container.parentNode === div && filters.length === 0) if (container.parentNode === div && filters.length === 0) {
div.removeChild(container) div.removeChild(container);
else if (filters.length > 0) } else if (filters.length > 0) {
div.appendChild(container) div.appendChild(container);
}
} }
return { render: render, return {
render: render,
filtersChanged: filtersChanged filtersChanged: filtersChanged
} };
} };
}) });

View file

@ -1,52 +1,56 @@
define([], function () { define([], function () {
return function (name, key, value, f) { return function (name, key, value, f) {
var negate = false var negate = false;
var refresh var refresh;
var label = document.createElement("label") var label = document.createElement("label");
var strong = document.createElement("strong") var strong = document.createElement("strong");
label.textContent = name + " " label.textContent = name + " ";
label.appendChild(strong) label.appendChild(strong);
function run(d) { function run(d) {
var o = dictGet(d, key.slice(0)) var o = dictGet(d, key.slice(0));
if (f) if (f) {
o = f(o) o = f(o);
}
return o === value ? !negate : negate return o === value ? !negate : negate;
} }
function setRefresh(f) { function setRefresh(f) {
refresh = f refresh = f;
} }
function draw(el) { function draw(el) {
if (negate) if (negate) {
el.parentNode.classList.add("not") el.parentNode.classList.add("not");
else } else {
el.parentNode.classList.remove("not") el.parentNode.classList.remove("not");
}
strong.textContent = (negate ? "¬" : "" ) + value strong.textContent = (negate ? "¬" : "" ) + value;
} }
function render(el) { function render(el) {
el.appendChild(label) el.appendChild(label);
draw(el) draw(el);
label.onclick = function () { label.onclick = function () {
negate = !negate negate = !negate;
draw(el) draw(el);
if (refresh) if (refresh) {
refresh() refresh();
} }
};
} }
return { run: run, return {
run: run,
setRefresh: setRefresh, setRefresh: setRefresh,
render: render render: render
} };
} };
}) });

View file

@ -1,33 +1,36 @@
define([], function () { define([], function () {
return function (filter) { return function (filter) {
return function (data) { return function (data) {
var n = Object.create(data) var n = Object.create(data);
n.nodes = {} n.nodes = {};
for (var key in data.nodes) for (var key in data.nodes) {
n.nodes[key] = data.nodes[key].filter(filter) n.nodes[key] = data.nodes[key].filter(filter);
}
var filteredIds = new Set() var filteredIds = new Set();
n.graph = {} n.graph = {};
n.graph.nodes = data.graph.nodes.filter(function (d) { n.graph.nodes = data.graph.nodes.filter(function (d) {
var r var r;
if (d.node) if (d.node) {
r = filter(d.node) r = filter(d.node);
else } else {
r = filter({}) r = filter({});
}
if (r) if (r) {
filteredIds.add(d.id) filteredIds.add(d.id);
}
return r return r;
}) });
n.graph.links = data.graph.links.filter(function (d) { n.graph.links = data.graph.links.filter(function (d) {
return filteredIds.has(d.source.id) && filteredIds.has(d.target.id) return filteredIds.has(d.source.id) && filteredIds.has(d.target.id);
}) });
return n return n;
} };
} };
}) });

File diff suppressed because it is too large Load diff

View file

@ -6,120 +6,122 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
Nodelist, SimpleNodelist, Infobox, Proportions, ForceGraph, Nodelist, SimpleNodelist, Infobox, Proportions, ForceGraph,
Title, About, DataDistributor, FilterGUI) { Title, About, DataDistributor, FilterGUI) {
return function (config, router) { return function (config, router) {
var self = this var self = this;
var content var content;
var contentDiv var contentDiv;
var linkScale = chroma.scale(chroma.interpolate.bezier(["#04C714", "#FF5500", "#F02311"])).domain([1, 5]) var linkScale = chroma.scale(chroma.interpolate.bezier(["#04C714", "#FF5500", "#F02311"])).domain([1, 5]);
var sidebar var sidebar;
var buttons = document.createElement("div") var buttons = document.createElement("div");
buttons.classList.add("buttons") buttons.classList.add("buttons");
var fanout = new DataDistributor() var fanout = new DataDistributor();
var fanoutUnfiltered = new DataDistributor() var fanoutUnfiltered = new DataDistributor();
fanoutUnfiltered.add(fanout) fanoutUnfiltered.add(fanout);
function removeContent() { function removeContent() {
if (!content) if (!content) {
return return;
}
router.removeTarget(content) router.removeTarget(content);
fanout.remove(content) fanout.remove(content);
content.destroy() content.destroy();
content = null content = null;
} }
function addContent(K) { function addContent(K) {
removeContent() removeContent();
content = new K(config, linkScale, sidebar.getWidth, router, buttons) content = new K(config, linkScale, sidebar.getWidth, router, buttons);
content.render(contentDiv) content.render(contentDiv);
fanout.add(content) fanout.add(content);
router.addTarget(content) router.addTarget(content);
} }
function mkView(K) { function mkView(K) {
return function () { return function () {
addContent(K) addContent(K);
} };
} }
var loader = document.getElementsByClassName("loader")[0] var loader = document.getElementsByClassName("loader")[0];
loader.classList.add("hide") loader.classList.add("hide");
contentDiv = document.createElement("div") contentDiv = document.createElement("div");
contentDiv.classList.add("content") contentDiv.classList.add("content");
document.body.appendChild(contentDiv) document.body.appendChild(contentDiv);
sidebar = new Sidebar(document.body) sidebar = new Sidebar(document.body);
contentDiv.appendChild(buttons) contentDiv.appendChild(buttons);
var buttonToggle = document.createElement("button") var buttonToggle = document.createElement("button");
buttonToggle.textContent = "\uF133" buttonToggle.textContent = "\uF133";
buttonToggle.onclick = function () { buttonToggle.onclick = function () {
if (content.constructor === Map) if (content.constructor === Map) {
router.view("g") router.view("g");
else } else {
router.view("m") router.view("m");
} }
};
buttons.appendChild(buttonToggle) buttons.appendChild(buttonToggle);
var title = new Title(config) var title = new Title(config);
var header = new Container("header") var header = new Container("header");
var infobox = new Infobox(config, sidebar, router) var infobox = new Infobox(config, sidebar, router);
var tabs = new Tabs() var tabs = new Tabs();
var overview = new Container() var overview = new Container();
var meshstats = new Meshstats(config) var meshstats = new Meshstats(config);
var legend = new Legend() var legend = new Legend();
var newnodeslist = new SimpleNodelist("new", "firstseen", router, "Neue Knoten") var newnodeslist = new SimpleNodelist("new", "firstseen", router, "Neue Knoten");
var lostnodeslist = new SimpleNodelist("lost", "lastseen", router, "Verschwundene Knoten") var lostnodeslist = new SimpleNodelist("lost", "lastseen", router, "Verschwundene Knoten");
var nodelist = new Nodelist(router) var nodelist = new Nodelist(router);
var linklist = new Linklist(linkScale, router) var linklist = new Linklist(linkScale, router);
var statistics = new Proportions(config, fanout) var statistics = new Proportions(config, fanout);
var about = new About() var about = new About();
fanoutUnfiltered.add(meshstats) fanoutUnfiltered.add(meshstats);
fanoutUnfiltered.add(newnodeslist) fanoutUnfiltered.add(newnodeslist);
fanoutUnfiltered.add(lostnodeslist) fanoutUnfiltered.add(lostnodeslist);
fanout.add(nodelist) fanout.add(nodelist);
fanout.add(linklist) fanout.add(linklist);
fanout.add(statistics) fanout.add(statistics);
sidebar.add(header) sidebar.add(header);
header.add(meshstats) header.add(meshstats);
header.add(legend) header.add(legend);
overview.add(newnodeslist) overview.add(newnodeslist);
overview.add(lostnodeslist) overview.add(lostnodeslist);
var filterGUI = new FilterGUI(fanout) var filterGUI = new FilterGUI(fanout);
fanout.watchFilters(filterGUI) fanout.watchFilters(filterGUI);
header.add(filterGUI) header.add(filterGUI);
sidebar.add(tabs) sidebar.add(tabs);
tabs.add("Aktuelles", overview) tabs.add("Aktuelles", overview);
tabs.add("Knoten", nodelist) tabs.add("Knoten", nodelist);
tabs.add("Verbindungen", linklist) tabs.add("Verbindungen", linklist);
tabs.add("Statistiken", statistics) tabs.add("Statistiken", statistics);
tabs.add("Über", about) tabs.add("Über", about);
router.addTarget(title) router.addTarget(title);
router.addTarget(infobox) router.addTarget(infobox);
router.addView("m", mkView(Map)) router.addView("m", mkView(Map));
router.addView("g", mkView(ForceGraph)) router.addView("g", mkView(ForceGraph));
router.view("m") router.view("m");
self.setData = fanoutUnfiltered.setData self.setData = fanoutUnfiltered.setData;
return self return self;
} };
}) });

View file

@ -1,48 +1,49 @@
define(function () { define(function () {
function showStatImg(o, d) { function showStatImg(o, d) {
var subst = {} var subst = {};
subst["{SOURCE}"] = d.source.node_id subst["{SOURCE}"] = d.source.node_id;
subst["{SOURCE_NAME}"] = d.source.node.nodeinfo.hostname ? d.source.node.nodeinfo.hostname : "unknown" subst["{SOURCE_NAME}"] = d.source.node.nodeinfo.hostname ? d.source.node.nodeinfo.hostname : "unknown";
subst["{TARGET}"] = d.target.node_id subst["{TARGET}"] = d.target.node_id;
subst["{TARGET_NAME}"] = d.target.node.nodeinfo.hostname ? d.target.node.nodeinfo.hostname : "unknown" subst["{TARGET_NAME}"] = d.target.node.nodeinfo.hostname ? d.target.node.nodeinfo.hostname : "unknown";
return showStat(o, subst) return showStat(o, subst);
} }
return function (config, el, router, d) { return function (config, el, router, d) {
var unknown = !(d.source.node) var unknown = !(d.source.node);
var h2 = document.createElement("h2") var h2 = document.createElement("h2");
var a1 = document.createElement("a") var a1 = document.createElement("a");
if (!unknown) { if (!unknown) {
a1.href = "#" a1.href = "#";
a1.onclick = router.node(d.source.node) a1.onclick = router.node(d.source.node);
} }
a1.textContent = unknown ? d.source.id : d.source.node.nodeinfo.hostname a1.textContent = unknown ? d.source.id : d.source.node.nodeinfo.hostname;
h2.appendChild(a1) h2.appendChild(a1);
h2.appendChild(document.createTextNode(" → ")) h2.appendChild(document.createTextNode(" → "));
var a2 = document.createElement("a") var a2 = document.createElement("a");
a2.href = "#" a2.href = "#";
a2.onclick = router.node(d.target.node) a2.onclick = router.node(d.target.node);
a2.textContent = d.target.node.nodeinfo.hostname a2.textContent = d.target.node.nodeinfo.hostname;
h2.appendChild(a2) h2.appendChild(a2);
el.appendChild(h2) el.appendChild(h2);
var attributes = document.createElement("table") var attributes = document.createElement("table");
attributes.classList.add("attributes") attributes.classList.add("attributes");
attributeEntry(attributes, "TQ", showTq(d)) attributeEntry(attributes, "TQ", showTq(d));
attributeEntry(attributes, "Entfernung", showDistance(d)) attributeEntry(attributes, "Entfernung", showDistance(d));
attributeEntry(attributes, "Typ", d.type) attributeEntry(attributes, "Typ", d.type);
var hw1 = unknown ? null : dictGet(d.source.node.nodeinfo, ["hardware", "model"]) var hw1 = unknown ? null : dictGet(d.source.node.nodeinfo, ["hardware", "model"]);
var hw2 = dictGet(d.target.node.nodeinfo, ["hardware", "model"]) var hw2 = dictGet(d.target.node.nodeinfo, ["hardware", "model"]);
attributeEntry(attributes, "Hardware", (hw1 != null ? hw1 : "unbekannt") + " " + (hw2 != null ? hw2 : "unbekannt")) attributeEntry(attributes, "Hardware", (hw1 != null ? hw1 : "unbekannt") + " " + (hw2 != null ? hw2 : "unbekannt"));
el.appendChild(attributes) el.appendChild(attributes);
if (config.linkInfos) if (config.linkInfos) {
config.linkInfos.forEach(function (linkInfo) { config.linkInfos.forEach(function (linkInfo) {
var h4 = document.createElement("h4") var h4 = document.createElement("h4");
h4.textContent = linkInfo.name h4.textContent = linkInfo.name;
el.appendChild(h4) el.appendChild(h4);
el.appendChild(showStatImg(linkInfo, d)) el.appendChild(showStatImg(linkInfo, d));
}) });
} }
}) };
});

View file

@ -1,100 +1,103 @@
define(function () { define(function () {
return function (config, el, router, d) { return function (config, el, router, d) {
var sidebarTitle = document.createElement("h2") var sidebarTitle = document.createElement("h2");
sidebarTitle.textContent = "Location: " + d.toString() sidebarTitle.textContent = "Location: " + d.toString();
el.appendChild(sidebarTitle) el.appendChild(sidebarTitle);
getJSON("https://nominatim.openstreetmap.org/reverse?format=json&lat=" + d.lat + "&lon=" + d.lng + "&zoom=18&addressdetails=0") getJSON("https://nominatim.openstreetmap.org/reverse?format=json&lat=" + d.lat + "&lon=" + d.lng + "&zoom=18&addressdetails=0")
.then(function (result) { .then(function (result) {
if(result.display_name) if (result.display_name) {
sidebarTitle.textContent = result.display_name sidebarTitle.textContent = result.display_name;
}) }
});
var editLat = document.createElement("input") var editLat = document.createElement("input");
editLat.type = "text" editLat.type = "text";
editLat.value = d.lat.toFixed(9) editLat.value = d.lat.toFixed(9);
el.appendChild(createBox("lat", "Breitengrad", editLat)) el.appendChild(createBox("lat", "Breitengrad", editLat));
var editLng = document.createElement("input") var editLng = document.createElement("input");
editLng.type = "text" editLng.type = "text";
editLng.value = d.lng.toFixed(9) editLng.value = d.lng.toFixed(9);
el.appendChild(createBox("lng", "Längengrad", editLng)) el.appendChild(createBox("lng", "Längengrad", editLng));
var editUci = document.createElement("textarea") var editUci = document.createElement("textarea");
editUci.value = editUci.value =
"uci set gluon-node-info.@location[0]='location'; " + "uci set gluon-node-info.@location[0]='location'; " +
"uci set gluon-node-info.@location[0].share_location='1';" + "uci set gluon-node-info.@location[0].share_location='1';" +
"uci set gluon-node-info.@location[0].latitude='" + d.lat.toFixed(9) + "';" + "uci set gluon-node-info.@location[0].latitude='" + d.lat.toFixed(9) + "';" +
"uci set gluon-node-info.@location[0].longitude='" + d.lng.toFixed(9) + "';" + "uci set gluon-node-info.@location[0].longitude='" + d.lng.toFixed(9) + "';" +
"uci commit gluon-node-info" "uci commit gluon-node-info";
el.appendChild(createBox("uci", "Befehl", editUci, false)) el.appendChild(createBox("uci", "Befehl", editUci, false));
var linkPlain = document.createElement("a") var linkPlain = document.createElement("a");
linkPlain.textContent = "plain" linkPlain.textContent = "plain";
linkPlain.onclick = function () { linkPlain.onclick = function () {
switch2plain() switch2plain();
return false return false;
} };
linkPlain.href = "#" linkPlain.href = "#";
var linkUci = document.createElement("a") var linkUci = document.createElement("a");
linkUci.textContent = "uci" linkUci.textContent = "uci";
linkUci.onclick = function () { linkUci.onclick = function () {
switch2uci() switch2uci();
return false return false;
} };
linkUci.href = "#" linkUci.href = "#";
var hintText = document.createElement("p") var hintText = document.createElement("p");
hintText.appendChild(document.createTextNode("Du kannst zwischen ")) hintText.appendChild(document.createTextNode("Du kannst zwischen "));
hintText.appendChild(linkPlain) hintText.appendChild(linkPlain);
hintText.appendChild(document.createTextNode(" und ")) hintText.appendChild(document.createTextNode(" und "));
hintText.appendChild(linkUci) hintText.appendChild(linkUci);
hintText.appendChild(document.createTextNode(" wechseln.")) hintText.appendChild(document.createTextNode(" wechseln."));
el.appendChild(hintText) el.appendChild(hintText);
function createBox(name, title, inputElem, isVisible) { function createBox(name, title, inputElem, isVisible) {
var visible = typeof isVisible !== "undefined" ? isVisible : true var visible = typeof isVisible !== "undefined" ? isVisible : true;
var box = document.createElement("div") var box = document.createElement("div");
var heading = document.createElement("h3") var heading = document.createElement("h3");
heading.textContent = title heading.textContent = title;
box.appendChild(heading) box.appendChild(heading);
var btn = document.createElement("button") var btn = document.createElement("button");
btn.className = "ion-ios-copy" btn.className = "ion-ios-copy";
btn.title = "Kopieren" btn.title = "Kopieren";
btn.onclick = function() { copy2clip(inputElem.id) } btn.onclick = function () {
inputElem.id = "location-" + name copy2clip(inputElem.id);
inputElem.readOnly = true };
var line = document.createElement("p") inputElem.id = "location-" + name;
line.appendChild(inputElem) inputElem.readOnly = true;
line.appendChild(btn) var line = document.createElement("p");
box.appendChild(line) line.appendChild(inputElem);
box.id = "box-" + name line.appendChild(btn);
box.style.display = visible ? "block" : "none" box.appendChild(line);
return box box.id = "box-" + name;
box.style.display = visible ? "block" : "none";
return box;
} }
function copy2clip(id) { function copy2clip(id) {
var copyField = document.querySelector("#" + id) var copyField = document.querySelector("#" + id);
copyField.select() copyField.select();
try { try {
document.execCommand("copy") document.execCommand("copy");
} catch (err) { } catch (err) {
console.log(err) console.log(err);
} }
} }
function switch2plain() { function switch2plain() {
document.getElementById("box-uci").style.display = "none" document.getElementById("box-uci").style.display = "none";
document.getElementById("box-lat").style.display = "block" document.getElementById("box-lat").style.display = "block";
document.getElementById("box-lng").style.display = "block" document.getElementById("box-lng").style.display = "block";
} }
function switch2uci() { function switch2uci() {
document.getElementById("box-uci").style.display = "block" document.getElementById("box-uci").style.display = "block";
document.getElementById("box-lat").style.display = "none" document.getElementById("box-lat").style.display = "none";
document.getElementById("box-lng").style.display = "none" document.getElementById("box-lng").style.display = "none";
} }
} };
}) });

View file

@ -1,51 +1,51 @@
define(["infobox/link", "infobox/node", "infobox/location"], function (Link, Node, Location) { define(["infobox/link", "infobox/node", "infobox/location"], function (Link, Node, Location) {
return function (config, sidebar, router) { return function (config, sidebar, router) {
var self = this var self = this;
var el var el;
function destroy() { function destroy() {
if (el && el.parentNode) { if (el && el.parentNode) {
el.parentNode.removeChild(el) el.parentNode.removeChild(el);
el = undefined el = undefined;
sidebar.reveal() sidebar.reveal();
} }
} }
function create() { function create() {
destroy() destroy();
sidebar.ensureVisible() sidebar.ensureVisible();
sidebar.hide() sidebar.hide();
el = document.createElement("div") el = document.createElement("div");
sidebar.container.insertBefore(el, sidebar.container.firstChild) sidebar.container.insertBefore(el, sidebar.container.firstChild);
el.scrollIntoView(false) el.scrollIntoView(false);
el.classList.add("infobox") el.classList.add("infobox");
el.destroy = destroy el.destroy = destroy;
var closeButton = document.createElement("button") var closeButton = document.createElement("button");
closeButton.classList.add("close") closeButton.classList.add("close");
closeButton.onclick = router.reset closeButton.onclick = router.reset;
el.appendChild(closeButton) el.appendChild(closeButton);
} }
self.resetView = destroy self.resetView = destroy;
self.gotoNode = function (d) { self.gotoNode = function (d) {
create() create();
new Node(config, el, router, d) new Node(config, el, router, d);
} };
self.gotoLink = function (d) { self.gotoLink = function (d) {
create() create();
new Link(config, el, router, d) new Link(config, el, router, d);
} };
self.gotoLocation = function (d) { self.gotoLocation = function (d) {
create() create();
new Location(config, el, router, d) new Location(config, el, router, d);
} };
return self return self;
} };
}) });

View file

@ -2,83 +2,90 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
function (moment, numeral, Tablesort) { function (moment, numeral, Tablesort) {
function showGeoURI(d) { function showGeoURI(d) {
function showLatitude(d) { function showLatitude(d) {
var suffix = Math.sign(d) > -1 ? "'N" : "'S" var suffix = Math.sign(d) > -1 ? "'N" : "'S";
d = Math.abs(d) d = Math.abs(d);
var a = Math.floor(d) var a = Math.floor(d);
var min = (d * 60) % 60 var min = (d * 60) % 60;
a = (a < 10 ? "0" : "") + a a = (a < 10 ? "0" : "") + a;
return a + "° " + numeral(min).format("0.000") + suffix return a + "° " + numeral(min).format("0.000") + suffix;
} }
function showLongitude(d) { function showLongitude(d) {
var suffix = Math.sign(d) > -1 ? "'E" : "'W" var suffix = Math.sign(d) > -1 ? "'E" : "'W";
d = Math.abs(d) d = Math.abs(d);
var a = Math.floor(d) var a = Math.floor(d);
var min = (d * 60) % 60 var min = (d * 60) % 60;
a = (a < 100 ? "0" + (a < 10 ? "0" : "") : "") + a a = (a < 100 ? "0" + (a < 10 ? "0" : "") : "") + a;
return a + "° " + numeral(min).format("0.000") + suffix return a + "° " + numeral(min).format("0.000") + suffix;
} }
if (!has_location(d)) if (!has_location(d)) {
return undefined return undefined;
}
return function (el) { return function (el) {
var latitude = d.nodeinfo.location.latitude var latitude = d.nodeinfo.location.latitude;
var longitude = d.nodeinfo.location.longitude var longitude = d.nodeinfo.location.longitude;
var a = document.createElement("a") var a = document.createElement("a");
a.textContent = showLatitude(latitude) + " " + a.textContent = showLatitude(latitude) + " " +
showLongitude(longitude) showLongitude(longitude);
a.href = "geo:" + latitude + "," + longitude a.href = "geo:" + latitude + "," + longitude;
el.appendChild(a) el.appendChild(a);
} };
} }
function showStatus(d) { function showStatus(d) {
return function (el) { return function (el) {
el.classList.add(d.flags.unseen ? "unseen" : (d.flags.online ? "online" : "offline")) el.classList.add(d.flags.unseen ? "unseen" : (d.flags.online ? "online" : "offline"));
if (d.flags.online) if (d.flags.online) {
el.textContent = "online, letzte Nachricht " + d.lastseen.fromNow() + " (" + d.lastseen.format("DD.MM.YYYY, H:mm:ss") + ")" el.textContent = "online, letzte Nachricht " + d.lastseen.fromNow() + " (" + d.lastseen.format("DD.MM.YYYY, H:mm:ss") + ")";
else } else {
el.textContent = "offline, letzte Nachricht " + d.lastseen.fromNow() + " (" + d.lastseen.format("DD.MM.YYYY, H:mm:ss") + ")" el.textContent = "offline, letzte Nachricht " + d.lastseen.fromNow() + " (" + d.lastseen.format("DD.MM.YYYY, H:mm:ss") + ")";
} }
};
} }
function showFirmware(d) { function showFirmware(d) {
var release = dictGet(d.nodeinfo, ["software", "firmware", "release"]) var release = dictGet(d.nodeinfo, ["software", "firmware", "release"]);
var base = dictGet(d.nodeinfo, ["software", "firmware", "base"]) var base = dictGet(d.nodeinfo, ["software", "firmware", "base"]);
if (release === null || base === null) if (release === null || base === null) {
return undefined return undefined;
}
return release + " / " + base return release + " / " + base;
} }
function showSite(d, config) { function showSite(d, config) {
var site = dictGet(d.nodeinfo, ["system", "site_code"]) var site = dictGet(d.nodeinfo, ["system", "site_code"]);
var rt = site var rt = site;
if (config.siteNames) if (config.siteNames) {
config.siteNames.forEach(function (t) { config.siteNames.forEach(function (t) {
if(site === t.site) if (site === t.site) {
rt = t.name rt = t.name;
}) }
return rt });
}
return rt;
} }
function showUptime(d) { function showUptime(d) {
if (!("uptime" in d.statistics)) if (!("uptime" in d.statistics)) {
return undefined return undefined;
}
return moment.duration(d.statistics.uptime, "seconds").humanize() return moment.duration(d.statistics.uptime, "seconds").humanize();
} }
function showFirstseen(d) { function showFirstseen(d) {
if (!("firstseen" in d)) if (!("firstseen" in d)) {
return undefined return undefined;
}
return d.firstseen.fromNow(true) return d.firstseen.fromNow(true);
} }
function wifiChannelAlias(ch) { function wifiChannelAlias(ch) {
@ -115,467 +122,510 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
"132": "5660 MHz (DFS) !!", "132": "5660 MHz (DFS) !!",
"136": "5680 MHz (DFS) !!", "136": "5680 MHz (DFS) !!",
"140": "5700 MHz (DFS) !!" "140": "5700 MHz (DFS) !!"
};
if (!(ch in chlist)) {
return "";
} else {
return chlist[ch];
} }
if (!(ch in chlist))
return ""
else
return chlist[ch]
} }
function showWifiChannel(ch) { function showWifiChannel(ch) {
if (!ch) if (!ch) {
return undefined return undefined;
}
return ch + " (" + wifiChannelAlias(ch) + ")" return ch + " (" + wifiChannelAlias(ch) + ")";
} }
function showClients(d) { function showClients(d) {
if (!d.flags.online) if (!d.flags.online) {
return undefined return undefined;
var meshclients = getMeshClients(d)
resetMeshClients(d)
var before = " ("
var after = " in der lokalen Wolke)"
return function (el) {
el.appendChild(document.createTextNode(d.statistics.clients > 0 ? d.statistics.clients : "keine"))
el.appendChild(document.createTextNode(before))
el.appendChild(document.createTextNode(meshclients > 0 ? meshclients : "keine"))
el.appendChild(document.createTextNode(after))
el.appendChild(document.createElement("br"))
var span = document.createElement("span")
span.classList.add("clients")
span.textContent = " ".repeat(d.statistics.clients)
el.appendChild(span)
var spanmesh = document.createElement("span")
spanmesh.classList.add("clientsMesh")
spanmesh.textContent = " ".repeat(meshclients - d.statistics.clients)
el.appendChild(spanmesh)
} }
var meshclients = getMeshClients(d);
resetMeshClients(d);
var before = " (";
var after = " in der lokalen Wolke)";
return function (el) {
el.appendChild(document.createTextNode(d.statistics.clients > 0 ? d.statistics.clients : "keine"));
el.appendChild(document.createTextNode(before));
el.appendChild(document.createTextNode(meshclients > 0 ? meshclients : "keine"));
el.appendChild(document.createTextNode(after));
el.appendChild(document.createElement("br"));
var span = document.createElement("span");
span.classList.add("clients");
span.textContent = " ".repeat(d.statistics.clients);
el.appendChild(span);
var spanmesh = document.createElement("span");
spanmesh.classList.add("clientsMesh");
spanmesh.textContent = " ".repeat(meshclients - d.statistics.clients);
el.appendChild(spanmesh);
};
} }
function getMeshClients(node) { function getMeshClients(node) {
var meshclients = 0 var meshclients = 0;
if (node.statistics && !isNaN(node.statistics.clients)) if (node.statistics && !isNaN(node.statistics.clients)) {
meshclients = node.statistics.clients meshclients = node.statistics.clients;
}
if (!node) if (!node) {
return 0 return 0;
}
if (node.parsed) if (node.parsed) {
return 0 return 0;
}
node.parsed = 1 node.parsed = 1;
node.neighbours.forEach(function (neighbour) { node.neighbours.forEach(function (neighbour) {
if (!neighbour.link.isVPN && neighbour.node) if (!neighbour.link.isVPN && neighbour.node) {
meshclients += getMeshClients(neighbour.node) meshclients += getMeshClients(neighbour.node);
}) }
});
return meshclients return meshclients;
} }
function resetMeshClients(node) { function resetMeshClients(node) {
if (!node.parsed) if (!node.parsed) {
return return;
}
node.parsed = 0 node.parsed = 0;
node.neighbours.forEach(function (neighbour) { node.neighbours.forEach(function (neighbour) {
if (!neighbour.link.isVPN && neighbour.node) if (!neighbour.link.isVPN && neighbour.node) {
resetMeshClients(neighbour.node) resetMeshClients(neighbour.node);
}) }
});
return
} }
function showMeshClients(d) { function showMeshClients(d) {
if (!d.flags.online) if (!d.flags.online) {
return undefined return undefined;
var meshclients = getMeshClients(d)
resetMeshClients(d)
return function (el) {
el.appendChild(document.createTextNode(meshclients > 0 ? meshclients : "keine"))
el.appendChild(document.createElement("br"))
} }
var meshclients = getMeshClients(d);
resetMeshClients(d);
return function (el) {
el.appendChild(document.createTextNode(meshclients > 0 ? meshclients : "keine"));
el.appendChild(document.createElement("br"));
};
} }
function showIPs(d) { function showIPs(d) {
var ips = dictGet(d.nodeinfo, ["network", "addresses"]) var ips = dictGet(d.nodeinfo, ["network", "addresses"]);
if (ips === null) if (ips === null) {
return undefined return undefined;
}
ips.sort() ips.sort();
return function (el) { return function (el) {
ips.forEach(function (ip, i) { ips.forEach(function (ip, i) {
var link = !ip.startsWith("fe80:") var link = !ip.startsWith("fe80:");
if (i > 0) if (i > 0) {
el.appendChild(document.createElement("br")) el.appendChild(document.createElement("br"));
}
if (link) { if (link) {
var a = document.createElement("a") var a = document.createElement("a");
if (ip.includes(".")) if (ip.includes(".")) {
a.href = "http://" + ip + "/" a.href = "http://" + ip + "/";
else } else {
a.href = "http://[" + ip + "]/" a.href = "http://[" + ip + "]/";
a.textContent = ip
el.appendChild(a)
} else
el.appendChild(document.createTextNode(ip))
})
} }
a.textContent = ip;
el.appendChild(a);
} else {
el.appendChild(document.createTextNode(ip));
}
});
};
} }
function showBar(className, v) { function showBar(className, v) {
var span = document.createElement("span") var span = document.createElement("span");
span.classList.add("bar") span.classList.add("bar");
span.classList.add(className) span.classList.add(className);
var bar = document.createElement("span") var bar = document.createElement("span");
bar.style.width = (v * 100) + "%" bar.style.width = (v * 100) + "%";
span.appendChild(bar) span.appendChild(bar);
var label = document.createElement("label") var label = document.createElement("label");
label.textContent = (Math.round(v * 100)) + " %" label.textContent = (Math.round(v * 100)) + " %";
span.appendChild(label) span.appendChild(label);
return span return span;
} }
function showLoadBar(className, v) { function showLoadBar(className, v) {
var span = document.createElement("span") var span = document.createElement("span");
span.classList.add("bar") span.classList.add("bar");
span.classList.add(className) span.classList.add(className);
var bar = document.createElement("span") var bar = document.createElement("span");
if (v >= 1) { if (v >= 1) {
bar.style.width = ((v * 100) % 100) + "%" bar.style.width = ((v * 100) % 100) + "%";
bar.style.background = "rgba(255, 50, 50, 0.9)" bar.style.background = "rgba(255, 50, 50, 0.9)";
span.style.background = "rgba(255, 50, 50, 0.6)" span.style.background = "rgba(255, 50, 50, 0.6)";
span.appendChild(bar) span.appendChild(bar);
} }
else else {
{ bar.style.width = (v * 100) + "%";
bar.style.width = (v * 100) + "%" span.appendChild(bar);
span.appendChild(bar)
} }
var label = document.createElement("label") var label = document.createElement("label");
label.textContent = +(Math.round(v + "e+2") + "e-2") label.textContent = +(Math.round(v + "e+2") + "e-2");
span.appendChild(label) span.appendChild(label);
return span return span;
} }
function showLoad(d) { function showLoad(d) {
if (!("loadavg" in d.statistics)) if (!("loadavg" in d.statistics)) {
return undefined return undefined;
}
return function (el) { return function (el) {
el.appendChild(showLoadBar("load-avg", d.statistics.loadavg)) el.appendChild(showLoadBar("load-avg", d.statistics.loadavg));
} };
} }
function showRAM(d) { function showRAM(d) {
if (!("memory_usage" in d.statistics)) if (!("memory_usage" in d.statistics)) {
return undefined return undefined;
}
return function (el) { return function (el) {
el.appendChild(showBar("memory-usage", d.statistics.memory_usage)) el.appendChild(showBar("memory-usage", d.statistics.memory_usage));
} };
} }
function showAirtime(band, val) { function showAirtime(band, val) {
if (!val) if (!val) {
return undefined return undefined;
}
return function (el) { return function (el) {
el.appendChild(showBar("airtime" + band.toString(), val)) el.appendChild(showBar("airtime" + band.toString(), val));
} };
} }
function createLink(target, router) { function createLink(target, router) {
if (!target) return document.createTextNode("unknown") if (!target) {
var unknown = !(target.node) return document.createTextNode("unknown");
var text = unknown ? (target.id ? target.id : target) : target.node.nodeinfo.hostname
if (!unknown) {
var link = document.createElement("a")
link.classList.add("hostname-link")
link.href = "#"
link.onclick = router.node(target.node)
link.textContent = text
return link
} }
return document.createTextNode(text) var unknown = !(target.node);
var text = unknown ? (target.id ? target.id : target) : target.node.nodeinfo.hostname;
if (!unknown) {
var link = document.createElement("a");
link.classList.add("hostname-link");
link.href = "#";
link.onclick = router.node(target.node);
link.textContent = text;
return link;
}
return document.createTextNode(text);
} }
function showGateway(d, router) { function showGateway(d, router) {
var nh var nh;
if (dictGet(d.statistics, ["nexthop"])) if (dictGet(d.statistics, ["nexthop"])) {
nh = dictGet(d.statistics, ["nexthop"]) nh = dictGet(d.statistics, ["nexthop"]);
if (dictGet(d.statistics, ["gateway_nexthop"])) }
nh = dictGet(d.statistics, ["gateway_nexthop"]) if (dictGet(d.statistics, ["gateway_nexthop"])) {
var gw = dictGet(d.statistics, ["gateway"]) nh = dictGet(d.statistics, ["gateway_nexthop"]);
}
var gw = dictGet(d.statistics, ["gateway"]);
if (!gw) return null if (!gw) {
return null;
}
return function (el) { return function (el) {
var num = 0 var num = 0;
while (gw && nh && gw.id !== nh.id && num < 10) { while (gw && nh && gw.id !== nh.id && num < 10) {
if (num !== 0) el.appendChild(document.createTextNode(" -> ")) if (num !== 0) {
el.appendChild(createLink(nh, router)) el.appendChild(document.createTextNode(" -> "));
num++ }
if (!nh.node || !nh.node.statistics) break el.appendChild(createLink(nh, router));
if (!dictGet(nh.node.statistics, ["gateway"]) || !dictGet(nh.node.statistics, ["gateway"]).id) break num++;
if (dictGet(nh.node.statistics, ["gateway"]).id !== gw.id) break if (!nh.node || !nh.node.statistics) {
if (dictGet(nh.node.statistics, ["gateway_nexthop"])) break;
nh = dictGet(nh.node.statistics, ["gateway_nexthop"]) }
else if (dictGet(nh.node.statistics, ["nexthop"])) if (!dictGet(nh.node.statistics, ["gateway"]) || !dictGet(nh.node.statistics, ["gateway"]).id) {
nh = dictGet(nh.node.statistics, ["nexthop"]) break;
else }
break if (dictGet(nh.node.statistics, ["gateway"]).id !== gw.id) {
break;
}
if (dictGet(nh.node.statistics, ["gateway_nexthop"])) {
nh = dictGet(nh.node.statistics, ["gateway_nexthop"]);
} else if (dictGet(nh.node.statistics, ["nexthop"])) {
nh = dictGet(nh.node.statistics, ["nexthop"]);
} else {
break;
}
} }
if (gw && nh && gw.id !== nh.id) { if (gw && nh && gw.id !== nh.id) {
if (num !== 0) el.appendChild(document.createTextNode(" -> ")) if (num !== 0) {
num++ el.appendChild(document.createTextNode(" -> "));
el.appendChild(document.createTextNode("..."))
} }
if (num !== 0) el.appendChild(document.createTextNode(" -> ")) num++;
el.appendChild(createLink(gw, router)) el.appendChild(document.createTextNode("..."));
} }
if (num !== 0) {
el.appendChild(document.createTextNode(" -> "));
}
el.appendChild(createLink(gw, router));
};
} }
function showPages(d) { function showPages(d) {
var webpages = dictGet(d.nodeinfo, ["pages"]) var webpages = dictGet(d.nodeinfo, ["pages"]);
if (webpages === null) if (webpages === null) {
return undefined return undefined;
}
webpages.sort() webpages.sort();
return function (el) { return function (el) {
webpages.forEach(function (webpage, i) { webpages.forEach(function (webpage, i) {
if (i > 0) if (i > 0) {
el.appendChild(document.createElement("br")) el.appendChild(document.createElement("br"));
}
var a = document.createElement("span") var a = document.createElement("span");
var link = document.createElement("a") var link = document.createElement("a");
link.href = webpage link.href = webpage;
if (webpage.search(/^https:\/\//i) !== -1) { if (webpage.search(/^https:\/\//i) !== -1) {
var lock = document.createElement("span") var lock = document.createElement("span");
lock.className = "ion-android-lock" lock.className = "ion-android-lock";
a.appendChild(lock) a.appendChild(lock);
var t1 = document.createTextNode(" ") var t1 = document.createTextNode(" ");
a.appendChild(t1) a.appendChild(t1);
link.textContent = webpage.replace(/^https:\/\//i, "") link.textContent = webpage.replace(/^https:\/\//i, "");
} }
else else {
link.textContent = webpage.replace(/^http:\/\//i, "") link.textContent = webpage.replace(/^http:\/\//i, "");
a.appendChild(link)
el.appendChild(a)
})
} }
a.appendChild(link);
el.appendChild(a);
});
};
} }
function showAutoupdate(d) { function showAutoupdate(d) {
var au = dictGet(d.nodeinfo, ["software", "autoupdater"]) var au = dictGet(d.nodeinfo, ["software", "autoupdater"]);
if (!au) if (!au) {
return undefined return undefined;
}
return au.enabled ? "aktiviert (" + au.branch + ")" : "deaktiviert" return au.enabled ? "aktiviert (" + au.branch + ")" : "deaktiviert";
} }
function showNodeImg(o, model) { function showNodeImg(o, model) {
if (!model) if (!model) {
return document.createTextNode("Knotenname") return document.createTextNode("Knotenname");
var content, caption
var modelhash = model.split("").reduce(function(a, b) {
a = ((a << 5) - a) + b.charCodeAt(0)
return a & a
}, 0)
content = document.createElement("img")
content.id = "routerpicture"
content.classList.add("nodeImg")
content.src = o.thumbnail.replace("{MODELHASH}", modelhash)
content.onerror = function() {
document.getElementById("routerpicdiv").outerHTML = "Knotenname"
} }
var content, caption;
var modelhash = model.split("").reduce(function (a, b) {
a = ((a << 5) - a) + b.charCodeAt(0);
return a & a;
}, 0);
content = document.createElement("img");
content.id = "routerpicture";
content.classList.add("nodeImg");
content.src = o.thumbnail.replace("{MODELHASH}", modelhash);
content.onerror = function () {
document.getElementById("routerpicdiv").outerHTML = "Knotenname";
};
if (o.caption) { if (o.caption) {
caption = o.caption.replace("{MODELHASH}", modelhash) caption = o.caption.replace("{MODELHASH}", modelhash);
if (!content) if (!content) {
content = document.createTextNode(caption) content = document.createTextNode(caption);
}
} }
var p = document.createElement("p") var p = document.createElement("p");
p.appendChild(content) p.appendChild(content);
return content return content;
} }
function showStatImg(o, d) { function showStatImg(o, d) {
var subst = {} var subst = {};
subst["{NODE_ID}"] = d.nodeinfo.node_id ? d.nodeinfo.node_id : "unknown" subst["{NODE_ID}"] = d.nodeinfo.node_id ? d.nodeinfo.node_id : "unknown";
subst["{NODE_NAME}"] = d.nodeinfo.hostname ? d.nodeinfo.hostname : "unknown" subst["{NODE_NAME}"] = d.nodeinfo.hostname ? d.nodeinfo.hostname : "unknown";
return showStat(o, subst) return showStat(o, subst);
} }
return function (config, el, router, d) { return function (config, el, router, d) {
var attributes = document.createElement("table") var attributes = document.createElement("table");
attributes.classList.add("attributes") attributes.classList.add("attributes");
if (config.hwImg) { if (config.hwImg) {
var top = document.createElement("div") var top = document.createElement("div");
top.id = "routerpicdiv" top.id = "routerpicdiv";
try { try {
config.hwImg.forEach(function (hwImg) { config.hwImg.forEach(function (hwImg) {
try { try {
top.appendChild(showNodeImg(hwImg, dictGet(d, ["nodeinfo", "hardware", "model"]))) top.appendChild(showNodeImg(hwImg, dictGet(d, ["nodeinfo", "hardware", "model"])));
} catch (err) { } catch (err) {
console.log(err.message) console.log(err.message);
} }
}) });
} catch (err) { } catch (err) {
console.log(err.message) console.log(err.message);
} }
attributeEntry(attributes, top, d.nodeinfo.hostname) attributeEntry(attributes, top, d.nodeinfo.hostname);
} else { } else {
var h2 = document.createElement("h2") var h2 = document.createElement("h2");
h2.textContent = d.nodeinfo.hostname h2.textContent = d.nodeinfo.hostname;
el.appendChild(h2) el.appendChild(h2);
} }
attributeEntry(attributes, "Status", showStatus(d)) attributeEntry(attributes, "Status", showStatus(d));
attributeEntry(attributes, "Gateway", d.flags.gateway ? "ja" : null) attributeEntry(attributes, "Gateway", d.flags.gateway ? "ja" : null);
attributeEntry(attributes, "Koordinaten", showGeoURI(d)) attributeEntry(attributes, "Koordinaten", showGeoURI(d));
if (config.showContact) if (config.showContact) {
attributeEntry(attributes, "Kontakt", dictGet(d.nodeinfo, ["owner", "contact"])) attributeEntry(attributes, "Kontakt", dictGet(d.nodeinfo, ["owner", "contact"]));
}
attributeEntry(attributes, "Hardware", dictGet(d.nodeinfo, ["hardware", "model"])) attributeEntry(attributes, "Hardware", dictGet(d.nodeinfo, ["hardware", "model"]));
attributeEntry(attributes, "Primäre MAC", dictGet(d.nodeinfo, ["network", "mac"])) attributeEntry(attributes, "Primäre MAC", dictGet(d.nodeinfo, ["network", "mac"]));
attributeEntry(attributes, "Node ID", dictGet(d.nodeinfo, ["node_id"])) attributeEntry(attributes, "Node ID", dictGet(d.nodeinfo, ["node_id"]));
attributeEntry(attributes, "Firmware", showFirmware(d)) attributeEntry(attributes, "Firmware", showFirmware(d));
attributeEntry(attributes, "Site", showSite(d, config)) attributeEntry(attributes, "Site", showSite(d, config));
attributeEntry(attributes, "Uptime", showUptime(d)) attributeEntry(attributes, "Uptime", showUptime(d));
attributeEntry(attributes, "Teil des Netzes", showFirstseen(d)) attributeEntry(attributes, "Teil des Netzes", showFirstseen(d));
attributeEntry(attributes, "Kanal 2.4 GHz", showWifiChannel(dictGet(d.nodeinfo, ["wireless", "chan2"]))) attributeEntry(attributes, "Kanal 2.4 GHz", showWifiChannel(dictGet(d.nodeinfo, ["wireless", "chan2"])));
attributeEntry(attributes, "Kanal 5 GHz", showWifiChannel(dictGet(d.nodeinfo, ["wireless", "chan5"]))) attributeEntry(attributes, "Kanal 5 GHz", showWifiChannel(dictGet(d.nodeinfo, ["wireless", "chan5"])));
attributeEntry(attributes, "Airtime 2.4 GHz", showAirtime(2, dictGet(d.statistics, ["wireless", "airtime2"]))) attributeEntry(attributes, "Airtime 2.4 GHz", showAirtime(2, dictGet(d.statistics, ["wireless", "airtime2"])));
attributeEntry(attributes, "Airtime 5 GHz", showAirtime(5, dictGet(d.statistics, ["wireless", "airtime5"]))) attributeEntry(attributes, "Airtime 5 GHz", showAirtime(5, dictGet(d.statistics, ["wireless", "airtime5"])));
attributeEntry(attributes, "Systemlast", showLoad(d)) attributeEntry(attributes, "Systemlast", showLoad(d));
attributeEntry(attributes, "Arbeitsspeicher", showRAM(d)) attributeEntry(attributes, "Arbeitsspeicher", showRAM(d));
attributeEntry(attributes, "IP Adressen", showIPs(d)) attributeEntry(attributes, "IP Adressen", showIPs(d));
attributeEntry(attributes, "Webseite", showPages(d)) attributeEntry(attributes, "Webseite", showPages(d));
attributeEntry(attributes, "Gewähltes Gateway", showGateway(d, router)) attributeEntry(attributes, "Gewähltes Gateway", showGateway(d, router));
attributeEntry(attributes, "Autom. Updates", showAutoupdate(d)) attributeEntry(attributes, "Autom. Updates", showAutoupdate(d));
attributeEntry(attributes, "Clients", showClients(d), showMeshClients(d)) attributeEntry(attributes, "Clients", showClients(d), showMeshClients(d));
el.appendChild(attributes) el.appendChild(attributes);
if (config.nodeInfos) if (config.nodeInfos) {
config.nodeInfos.forEach(function (nodeInfo) { config.nodeInfos.forEach(function (nodeInfo) {
var h4 = document.createElement("h4") var h4 = document.createElement("h4");
h4.textContent = nodeInfo.name h4.textContent = nodeInfo.name;
el.appendChild(h4) el.appendChild(h4);
el.appendChild(showStatImg(nodeInfo, d)) el.appendChild(showStatImg(nodeInfo, d));
}) });
}
if (d.neighbours.length > 0) { if (d.neighbours.length > 0) {
var h3 = document.createElement("h3") var h3 = document.createElement("h3");
h3.textContent = "Links (" + d.neighbours.length + ")" h3.textContent = "Links (" + d.neighbours.length + ")";
el.appendChild(h3) el.appendChild(h3);
var table = document.createElement("table") var table = document.createElement("table");
var thead = document.createElement("thead") var thead = document.createElement("thead");
var tr = document.createElement("tr") var tr = document.createElement("tr");
var th1 = document.createElement("th") var th1 = document.createElement("th");
th1.textContent = " " th1.textContent = " ";
tr.appendChild(th1) tr.appendChild(th1);
var th2 = document.createElement("th") var th2 = document.createElement("th");
th2.textContent = "Knoten" th2.textContent = "Knoten";
th2.classList.add("sort-default") th2.classList.add("sort-default");
tr.appendChild(th2) tr.appendChild(th2);
var th3 = document.createElement("th") var th3 = document.createElement("th");
th3.textContent = "TQ" th3.textContent = "TQ";
tr.appendChild(th3) tr.appendChild(th3);
var th4 = document.createElement("th") var th4 = document.createElement("th");
th4.textContent = "Typ" th4.textContent = "Typ";
tr.appendChild(th4) tr.appendChild(th4);
var th5 = document.createElement("th") var th5 = document.createElement("th");
th5.textContent = "Entfernung" th5.textContent = "Entfernung";
tr.appendChild(th5) tr.appendChild(th5);
thead.appendChild(tr) thead.appendChild(tr);
table.appendChild(thead) table.appendChild(thead);
var tbody = document.createElement("tbody") var tbody = document.createElement("tbody");
d.neighbours.forEach(function (d) { d.neighbours.forEach(function (d) {
var unknown = !(d.node) var unknown = !(d.node);
var tr = document.createElement("tr") var tr = document.createElement("tr");
var td1 = document.createElement("td") var td1 = document.createElement("td");
td1.appendChild(document.createTextNode(d.incoming ? " ← " : " → ")) td1.appendChild(document.createTextNode(d.incoming ? " ← " : " → "));
tr.appendChild(td1) tr.appendChild(td1);
var td2 = document.createElement("td") var td2 = document.createElement("td");
td2.appendChild(createLink(d, router)) td2.appendChild(createLink(d, router));
if (!unknown && has_location(d.node)) { if (!unknown && has_location(d.node)) {
var span = document.createElement("span") var span = document.createElement("span");
span.classList.add("icon") span.classList.add("icon");
span.classList.add("ion-location") span.classList.add("ion-location");
td2.appendChild(span) td2.appendChild(span);
} }
tr.appendChild(td2) tr.appendChild(td2);
var td3 = document.createElement("td") var td3 = document.createElement("td");
var a2 = document.createElement("a") var a2 = document.createElement("a");
a2.href = "#" a2.href = "#";
a2.textContent = showTq(d.link) a2.textContent = showTq(d.link);
a2.onclick = router.link(d.link) a2.onclick = router.link(d.link);
td3.appendChild(a2) td3.appendChild(a2);
tr.appendChild(td3) tr.appendChild(td3);
var td4 = document.createElement("td") var td4 = document.createElement("td");
var a3 = document.createElement("a") var a3 = document.createElement("a");
a3.href = "#" a3.href = "#";
a3.textContent = d.link.type a3.textContent = d.link.type;
a3.onclick = router.link(d.link) a3.onclick = router.link(d.link);
td4.appendChild(a3) td4.appendChild(a3);
tr.appendChild(td4) tr.appendChild(td4);
var td5 = document.createElement("td") var td5 = document.createElement("td");
var a4 = document.createElement("a") var a4 = document.createElement("a");
a4.href = "#" a4.href = "#";
a4.textContent = showDistance(d.link) a4.textContent = showDistance(d.link);
a4.onclick = router.link(d.link) a4.onclick = router.link(d.link);
td5.appendChild(a4) td5.appendChild(a4);
td5.setAttribute("data-sort", d.link.distance !== undefined ? -d.link.distance : 1) td5.setAttribute("data-sort", d.link.distance !== undefined ? -d.link.distance : 1);
tr.appendChild(td5) tr.appendChild(td5);
tbody.appendChild(tr) tbody.appendChild(tr);
}) });
table.appendChild(tbody) table.appendChild(tbody);
table.className = "node-links" table.className = "node-links";
new Tablesort(table) new Tablesort(table);
el.appendChild(table) el.appendChild(table);
} }
} };
}) });

View file

@ -1,41 +1,41 @@
define(function () { define(function () {
return function () { return function () {
var self = this var self = this;
self.render = function (el) { self.render = function (el) {
var p = document.createElement("p") var p = document.createElement("p");
p.setAttribute("class", "legend") p.setAttribute("class", "legend");
el.appendChild(p) el.appendChild(p);
var spanNew = document.createElement("span") var spanNew = document.createElement("span");
spanNew.setAttribute("class", "legend-new") spanNew.setAttribute("class", "legend-new");
var symbolNew = document.createElement("span") var symbolNew = document.createElement("span");
symbolNew.setAttribute("class", "symbol") symbolNew.setAttribute("class", "symbol");
var textNew = document.createTextNode(" Neuer Knoten") var textNew = document.createTextNode(" Neuer Knoten");
spanNew.appendChild(symbolNew) spanNew.appendChild(symbolNew);
spanNew.appendChild(textNew) spanNew.appendChild(textNew);
p.appendChild(spanNew) p.appendChild(spanNew);
var spanOnline = document.createElement("span") var spanOnline = document.createElement("span");
spanOnline.setAttribute("class", "legend-online") spanOnline.setAttribute("class", "legend-online");
var symbolOnline = document.createElement("span") var symbolOnline = document.createElement("span");
symbolOnline.setAttribute("class", "symbol") symbolOnline.setAttribute("class", "symbol");
var textOnline = document.createTextNode(" Knoten ist online") var textOnline = document.createTextNode(" Knoten ist online");
spanOnline.appendChild(symbolOnline) spanOnline.appendChild(symbolOnline);
spanOnline.appendChild(textOnline) spanOnline.appendChild(textOnline);
p.appendChild(spanOnline) p.appendChild(spanOnline);
var spanOffline = document.createElement("span") var spanOffline = document.createElement("span");
spanOffline.setAttribute("class", "legend-offline") spanOffline.setAttribute("class", "legend-offline");
var symbolOffline = document.createElement("span") var symbolOffline = document.createElement("span");
symbolOffline.setAttribute("class", "symbol") symbolOffline.setAttribute("class", "symbol");
var textOffline = document.createTextNode(" Knoten ist offline") var textOffline = document.createTextNode(" Knoten ist offline");
spanOffline.appendChild(symbolOffline) spanOffline.appendChild(symbolOffline);
spanOffline.appendChild(textOffline) spanOffline.appendChild(textOffline);
p.appendChild(spanOffline) p.appendChild(spanOffline);
} };
return self return self;
} };
}) });

View file

@ -1,60 +1,66 @@
define(["sorttable", "virtual-dom"], function (SortTable, V) { define(["sorttable", "virtual-dom"], function (SortTable, V) {
function linkName(d) { function linkName(d) {
return (d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + " " + d.target.node.nodeinfo.hostname return (d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + " " + d.target.node.nodeinfo.hostname;
} }
var headings = [{ name: "Knoten", var headings = [{
name: "Knoten",
sort: function (a, b) { sort: function (a, b) {
return linkName(a).localeCompare(linkName(b)) return linkName(a).localeCompare(linkName(b));
}, },
reverse: false reverse: false
}, },
{ name: "TQ", {
sort: function (a, b) { return a.tq - b.tq}, name: "TQ",
sort: function (a, b) {
return a.tq - b.tq;
},
reverse: true reverse: true
}, },
{ name: "Typ", {
name: "Typ",
sort: function (a, b) { sort: function (a, b) {
return a.type.localeCompare(b.type) return a.type.localeCompare(b.type);
}, },
reverse: false reverse: false
}, },
{ name: "Entfernung", {
name: "Entfernung",
sort: function (a, b) { sort: function (a, b) {
return (a.distance === undefined ? -1 : a.distance) - return (a.distance === undefined ? -1 : a.distance) -
(b.distance === undefined ? -1 : b.distance) (b.distance === undefined ? -1 : b.distance);
}, },
reverse: true reverse: true
}] }];
return function (linkScale, router) { return function (linkScale, router) {
var table = new SortTable(headings, 2, renderRow) var table = new SortTable(headings, 2, renderRow);
function renderRow(d) { function renderRow(d) {
var td1Content = [V.h("a", {href: "#", onclick: router.link(d)}, linkName(d))] var td1Content = [V.h("a", {href: "#", onclick: router.link(d)}, linkName(d))];
var td1 = V.h("td", td1Content) var td1 = V.h("td", td1Content);
var td2 = V.h("td", {style: {color: linkScale(d.tq).hex()}}, showTq(d)) var td2 = V.h("td", {style: {color: linkScale(d.tq).hex()}}, showTq(d));
var td3 = V.h("td", d.type) var td3 = V.h("td", d.type);
var td4 = V.h("td", showDistance(d)) var td4 = V.h("td", showDistance(d));
return V.h("tr", [td1, td2, td3, td4]) return V.h("tr", [td1, td2, td3, td4]);
} }
this.render = function (d) { this.render = function (d) {
var el = document.createElement("div") var el = document.createElement("div");
el.last = V.h("div") el.last = V.h("div");
d.appendChild(el) 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);
el.appendChild(table.el) el.appendChild(table.el);
} };
this.setData = function (d) { this.setData = function (d) {
table.setData(d.graph.links) table.setData(d.graph.links);
} };
} };
}) });

View file

@ -30,30 +30,30 @@ define(["leaflet"], function (L) {
}, },
initialize: function (latlng) { initialize: function (latlng) {
this.accuracyCircle = L.circle(latlng, 0, this.accuracyCircle) this.accuracyCircle = L.circle(latlng, 0, this.accuracyCircle);
this.outerCircle = L.circleMarker(latlng, this.outerCircle) this.outerCircle = L.circleMarker(latlng, this.outerCircle);
L.CircleMarker.prototype.initialize.call(this, latlng, this.innerCircle) L.CircleMarker.prototype.initialize.call(this, latlng, this.innerCircle);
this.on("remove", function () { this.on("remove", function () {
this._map.removeLayer(this.accuracyCircle) this._map.removeLayer(this.accuracyCircle);
this._map.removeLayer(this.outerCircle) this._map.removeLayer(this.outerCircle);
}) });
}, },
setLatLng: function (latlng) { setLatLng: function (latlng) {
this.accuracyCircle.setLatLng(latlng) this.accuracyCircle.setLatLng(latlng);
this.outerCircle.setLatLng(latlng) this.outerCircle.setLatLng(latlng);
L.CircleMarker.prototype.setLatLng.call(this, latlng) L.CircleMarker.prototype.setLatLng.call(this, latlng);
}, },
setAccuracy: function (accuracy) { setAccuracy: function (accuracy) {
this.accuracyCircle.setRadius(accuracy) this.accuracyCircle.setRadius(accuracy);
}, },
onAdd: function (map) { onAdd: function (map) {
this.accuracyCircle.addTo(map).bringToBack() this.accuracyCircle.addTo(map).bringToBack();
this.outerCircle.addTo(map) this.outerCircle.addTo(map);
L.CircleMarker.prototype.onAdd.call(this, map) L.CircleMarker.prototype.onAdd.call(this, map);
} }
}) });
}) });

View file

@ -2,181 +2,189 @@ define(["moment", "router", "leaflet", "gui", "numeral"],
function (moment, Router, L, GUI, numeral) { function (moment, Router, L, GUI, numeral) {
return function (config) { return function (config) {
function handleData(data) { function handleData(data) {
var dataNodes = {} var dataNodes = {};
dataNodes.nodes = [] dataNodes.nodes = [];
dataNodes.nodeIds = [] dataNodes.nodeIds = [];
var dataGraph = {} var dataGraph = {};
dataGraph.batadv = {} dataGraph.batadv = {};
dataGraph.batadv.nodes = [] dataGraph.batadv.nodes = [];
dataGraph.batadv.links = [] dataGraph.batadv.links = [];
function rearrangeLinks(d) { function rearrangeLinks(d) {
d.source += dataGraph.batadv.nodes.length d.source += dataGraph.batadv.nodes.length;
d.target += dataGraph.batadv.nodes.length d.target += dataGraph.batadv.nodes.length;
} }
for (var i = 0; i < data.length; ++i) { for (var i = 0; i < data.length; ++i) {
var vererr var vererr;
if(i % 2) if (i % 2) {
if (data[i].version !== 1) { if (data[i].version !== 1) {
vererr = "Unsupported graph version: " + data[i].version vererr = "Unsupported graph version: " + data[i].version;
console.log(vererr) //silent fail console.log(vererr); //silent fail
} else { } else {
data[i].batadv.links.forEach(rearrangeLinks) data[i].batadv.links.forEach(rearrangeLinks);
dataGraph.batadv.nodes = dataGraph.batadv.nodes.concat(data[i].batadv.nodes) dataGraph.batadv.nodes = dataGraph.batadv.nodes.concat(data[i].batadv.nodes);
dataGraph.batadv.links = dataGraph.batadv.links.concat(data[i].batadv.links) dataGraph.batadv.links = dataGraph.batadv.links.concat(data[i].batadv.links);
dataGraph.timestamp = data[i].timestamp dataGraph.timestamp = data[i].timestamp;
} }
else } else if (data[i].version !== 2) {
if (data[i].version !== 2) { vererr = "Unsupported nodes version: " + data[i].version;
vererr = "Unsupported nodes version: " + data[i].version console.log(vererr); //silent fail
console.log(vererr) //silent fail
} else { } else {
data[i].nodes.forEach(fillData) data[i].nodes.forEach(fillData);
dataNodes.timestamp = data[i].timestamp dataNodes.timestamp = data[i].timestamp;
} }
} }
function fillData(node) { function fillData(node) {
var position = dataNodes.nodeIds.indexOf(node.nodeinfo.node_id) var position = dataNodes.nodeIds.indexOf(node.nodeinfo.node_id);
if (position === -1) { if (position === -1) {
dataNodes.nodes.push(node) dataNodes.nodes.push(node);
dataNodes.nodeIds.push(node.nodeinfo.node_id) dataNodes.nodeIds.push(node.nodeinfo.node_id);
}
else if (node.flags.online === true) {
dataNodes.nodes[position] = node;
} }
else
if(node.flags.online === true)
dataNodes.nodes[position] = node
} }
var nodes = dataNodes.nodes.filter(function (d) { var nodes = dataNodes.nodes.filter(function (d) {
return "firstseen" in d && "lastseen" in d return "firstseen" in d && "lastseen" in d;
}) });
nodes.forEach(function (node) { nodes.forEach(function (node) {
node.firstseen = moment.utc(node.firstseen).local() node.firstseen = moment.utc(node.firstseen).local();
node.lastseen = moment.utc(node.lastseen).local() node.lastseen = moment.utc(node.lastseen).local();
}) });
var now = moment() var now = moment();
var age = moment(now).subtract(config.maxAge, "days") var age = moment(now).subtract(config.maxAge, "days");
var newnodes = limit("firstseen", age, sortByKey("firstseen", nodes).filter(online)) var newnodes = limit("firstseen", age, sortByKey("firstseen", nodes).filter(online));
var lostnodes = limit("lastseen", age, sortByKey("lastseen", nodes).filter(offline)) var lostnodes = limit("lastseen", age, sortByKey("lastseen", nodes).filter(offline));
var graphnodes = {} var graphnodes = {};
dataNodes.nodes.forEach(function (d) { dataNodes.nodes.forEach(function (d) {
graphnodes[d.nodeinfo.node_id] = d graphnodes[d.nodeinfo.node_id] = d;
}) });
var graph = dataGraph.batadv var graph = dataGraph.batadv;
graph.nodes.forEach(function (d) { graph.nodes.forEach(function (d) {
if (d.node_id in graphnodes) { if (d.node_id in graphnodes) {
d.node = graphnodes[d.node_id] d.node = graphnodes[d.node_id];
if (d.unseen) { if (d.unseen) {
d.node.flags.online = true d.node.flags.online = true;
d.node.flags.unseen = true d.node.flags.unseen = true;
} }
} }
}) });
graph.links.forEach(function (d) { graph.links.forEach(function (d) {
d.source = graph.nodes[d.source] d.source = graph.nodes[d.source];
if (graph.nodes[d.target].node) if (graph.nodes[d.target].node) {
d.target = graph.nodes[d.target] d.target = graph.nodes[d.target];
else } else {
d.target = undefined d.target = undefined;
}) }
});
var links = graph.links.filter(function (d) { var links = graph.links.filter(function (d) {
return d.target !== undefined return d.target !== undefined;
}) });
links.forEach(function (d) { links.forEach(function (d) {
var unknown = (d.source.node === undefined) var unknown = (d.source.node === undefined);
var ids var ids;
if (unknown) if (unknown) {
ids = [d.source.id.replace(/:/g, ""), d.target.node.nodeinfo.node_id] ids = [d.source.id.replace(/:/g, ""), d.target.node.nodeinfo.node_id];
else } else {
ids = [d.source.node.nodeinfo.node_id, d.target.node.nodeinfo.node_id] ids = [d.source.node.nodeinfo.node_id, d.target.node.nodeinfo.node_id];
d.id = ids.join("-") }
d.id = ids.join("-");
if (unknown || if (unknown || !d.source.node.nodeinfo.location || !d.target.node.nodeinfo.location ||
!d.source.node.nodeinfo.location ||
!d.target.node.nodeinfo.location ||
isNaN(d.source.node.nodeinfo.location.latitude) || isNaN(d.source.node.nodeinfo.location.latitude) ||
isNaN(d.source.node.nodeinfo.location.longitude) || isNaN(d.source.node.nodeinfo.location.longitude) ||
isNaN(d.target.node.nodeinfo.location.latitude) || isNaN(d.target.node.nodeinfo.location.latitude) ||
isNaN(d.target.node.nodeinfo.location.longitude)) isNaN(d.target.node.nodeinfo.location.longitude)) {
return return;
}
d.latlngs = [] d.latlngs = [];
d.latlngs.push(L.latLng(d.source.node.nodeinfo.location.latitude, d.source.node.nodeinfo.location.longitude)) d.latlngs.push(L.latLng(d.source.node.nodeinfo.location.latitude, d.source.node.nodeinfo.location.longitude));
d.latlngs.push(L.latLng(d.target.node.nodeinfo.location.latitude, d.target.node.nodeinfo.location.longitude)) d.latlngs.push(L.latLng(d.target.node.nodeinfo.location.latitude, d.target.node.nodeinfo.location.longitude));
d.distance = d.latlngs[0].distanceTo(d.latlngs[1]) d.distance = d.latlngs[0].distanceTo(d.latlngs[1]);
}) });
nodes.forEach(function (d) { nodes.forEach(function (d) {
d.neighbours = [] d.neighbours = [];
if (d.statistics) { if (d.statistics) {
/*eslint camelcase:0*/ /*eslint camelcase:0*/
if ("gateway" in d.statistics && d.statistics.gateway in graphnodes) if ("gateway" in d.statistics && d.statistics.gateway in graphnodes) {
d.statistics.gateway = {"node": graphnodes[d.statistics.gateway], "id": d.statistics.gateway} d.statistics.gateway = {"node": graphnodes[d.statistics.gateway], "id": d.statistics.gateway};
if ("nexthop" in d.statistics && d.statistics.nexthop in graphnodes)
d.statistics.nexthop = {"node": graphnodes[d.statistics.nexthop], "id": d.statistics.nexthop}
if ("gateway_nexthop" in d.statistics && d.statistics.gateway_nexthop in graphnodes)
d.statistics.gateway_nexthop = {"node": graphnodes[d.statistics.gateway_nexthop], "id": d.statistics.gateway_nexthop}
} }
}) if ("nexthop" in d.statistics && d.statistics.nexthop in graphnodes) {
d.statistics.nexthop = {"node": graphnodes[d.statistics.nexthop], "id": d.statistics.nexthop};
}
if ("gateway_nexthop" in d.statistics && d.statistics.gateway_nexthop in graphnodes) {
d.statistics.gateway_nexthop = {
"node": graphnodes[d.statistics.gateway_nexthop],
"id": d.statistics.gateway_nexthop
};
}
}
});
links.forEach(function (d) { links.forEach(function (d) {
if (d.type === "tunnel" || d.vpn) { if (d.type === "tunnel" || d.vpn) {
d.type = "VPN" d.type = "VPN";
d.isVPN = true d.isVPN = true;
} else if (d.type === "fastd") { } else if (d.type === "fastd") {
d.type = "fastd" d.type = "fastd";
d.isVPN = true d.isVPN = true;
} else if (d.type === "l2tp") { } else if (d.type === "l2tp") {
d.type = "L2TP" d.type = "L2TP";
d.isVPN = true d.isVPN = true;
} else if (d.type === "gre") { } else if (d.type === "gre") {
d.type = "GRE" d.type = "GRE";
d.isVPN = true d.isVPN = true;
} else if (d.type === "wireless") { } else if (d.type === "wireless") {
d.type = "Wifi" d.type = "Wifi";
d.isVPN = false d.isVPN = false;
} else if (d.type === "other") { } else if (d.type === "other") {
d.type = "Kabel" d.type = "Kabel";
d.isVPN = false d.isVPN = false;
} else { } else {
d.type = "N/A" d.type = "N/A";
d.isVPN = false d.isVPN = false;
} }
if (d.isVPN && d.target.node) if (d.isVPN && d.target.node) {
d.target.node.flags.uplink = true d.target.node.flags.uplink = true;
}
var unknown = (d.source.node === undefined) var unknown = (d.source.node === undefined);
if (unknown) { if (unknown) {
d.target.node.neighbours.push({ id: d.source.id, link: d, incoming: true }) d.target.node.neighbours.push({id: d.source.id, link: d, incoming: true});
return return;
} }
d.source.node.neighbours.push({ node: d.target.node, link: d, incoming: false }) d.source.node.neighbours.push({node: d.target.node, link: d, incoming: false});
d.target.node.neighbours.push({ node: d.source.node, link: d, incoming: true }) d.target.node.neighbours.push({node: d.source.node, link: d, incoming: true});
if (!d.isVPN) if (!d.isVPN) {
d.source.node.meshlinks = d.source.node.meshlinks ? d.source.node.meshlinks + 1 : 1 d.source.node.meshlinks = d.source.node.meshlinks ? d.source.node.meshlinks + 1 : 1;
}) }
});
links.sort(function (a, b) { links.sort(function (a, b) {
return b.tq - a.tq return b.tq - a.tq;
}) });
return { now: now, return {
now: now,
timestamp: moment.utc(dataNodes.timestamp).local(), timestamp: moment.utc(dataNodes.timestamp).local(),
nodes: { nodes: {
all: nodes, all: nodes,
@ -187,46 +195,47 @@ function (moment, Router, L, GUI, numeral) {
links: links, links: links,
nodes: graph.nodes nodes: graph.nodes
} }
} };
} }
numeral.language("de") numeral.language("de");
moment.locale("de") moment.locale("de");
var router = new Router() var router = new Router();
var urls = [] var urls = [];
if (typeof config.dataPath === "string" || config.dataPath instanceof String) if (typeof config.dataPath === "string" || config.dataPath instanceof String) {
config.dataPath = [config.dataPath] config.dataPath = [config.dataPath];
}
for (var i in config.dataPath) { for (var i in config.dataPath) {
urls.push(config.dataPath[i] + "nodes.json") urls.push(config.dataPath[i] + "nodes.json");
urls.push(config.dataPath[i] + "graph.json") urls.push(config.dataPath[i] + "graph.json");
} }
function update() { function update() {
return Promise.all(urls.map(getJSON)) return Promise.all(urls.map(getJSON))
.then(handleData) .then(handleData);
} }
update() 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 () { window.setInterval(function () {
update().then(function (d) { update().then(function (d) {
gui.setData(d) gui.setData(d);
router.setData(d) router.setData(d);
}) });
}, 60000) }, 60000);
}) })
.catch(function (e) { .catch(function (e) {
document.body.textContent = e document.body.textContent = e;
console.log(e) console.log(e);
}) });
} };
}) });

View file

@ -2,9 +2,10 @@ define(["map/clientlayer", "map/labelslayer",
"d3", "leaflet", "moment", "locationmarker", "rbush", "d3", "leaflet", "moment", "locationmarker", "rbush",
"leaflet.label", "leaflet.providers"], "leaflet.label", "leaflet.providers"],
function (ClientLayer, LabelsLayer, d3, L, moment, LocationMarker, rbush) { function (ClientLayer, LabelsLayer, d3, L, moment, LocationMarker, rbush) {
var options = { worldCopyJump: true, var options = {
worldCopyJump: true,
zoomControl: false zoomControl: false
} };
var AddLayerButton = L.Control.extend({ var AddLayerButton = L.Control.extend({
options: { options: {
@ -12,24 +13,24 @@ define(["map/clientlayer", "map/labelslayer",
}, },
initialize: function (f, options) { initialize: function (f, options) {
L.Util.setOptions(this, options) L.Util.setOptions(this, options);
this.f = f this.f = f;
}, },
onAdd: function () { onAdd: function () {
var button = L.DomUtil.create("button", "add-layer") var button = L.DomUtil.create("button", "add-layer");
button.textContent = "\uF2C7" button.textContent = "\uF2C7";
// L.DomEvent.disableClickPropagation(button) // L.DomEvent.disableClickPropagation(button)
// Click propagation isn't disabled as this causes problems with the // Click propagation isn't disabled as this causes problems with the
// location picking mode; instead propagation is stopped in onClick(). // location picking mode; instead propagation is stopped in onClick().
L.DomEvent.addListener(button, "click", this.f, this) L.DomEvent.addListener(button, "click", this.f, this);
this.button = button this.button = button;
return button return button;
} }
}) });
var LocateButton = L.Control.extend({ var LocateButton = L.Control.extend({
options: { options: {
@ -40,35 +41,35 @@ define(["map/clientlayer", "map/labelslayer",
button: undefined, button: undefined,
initialize: function (f, options) { initialize: function (f, options) {
L.Util.setOptions(this, options) L.Util.setOptions(this, options);
this.f = f this.f = f;
}, },
onAdd: function () { onAdd: function () {
var button = L.DomUtil.create("button", "locate-user") var button = L.DomUtil.create("button", "locate-user");
button.textContent = "\uF2E9" button.textContent = "\uF2E9";
L.DomEvent.disableClickPropagation(button) L.DomEvent.disableClickPropagation(button);
L.DomEvent.addListener(button, "click", this.onClick, this) L.DomEvent.addListener(button, "click", this.onClick, this);
this.button = button this.button = button;
return button return button;
}, },
update: function () { update: function () {
this.button.classList.toggle("active", this.active) this.button.classList.toggle("active", this.active);
}, },
set: function (v) { set: function (v) {
this.active = v this.active = v;
this.update() this.update();
}, },
onClick: function () { onClick: function () {
this.f(!this.active) this.f(!this.active);
} }
}) });
var CoordsPickerButton = L.Control.extend({ var CoordsPickerButton = L.Control.extend({
options: { options: {
@ -79,470 +80,547 @@ define(["map/clientlayer", "map/labelslayer",
button: undefined, button: undefined,
initialize: function (f, options) { initialize: function (f, options) {
L.Util.setOptions(this, options) L.Util.setOptions(this, options);
this.f = f this.f = f;
}, },
onAdd: function () { onAdd: function () {
var button = L.DomUtil.create("button", "coord-picker") var button = L.DomUtil.create("button", "coord-picker");
button.textContent = "\uF2A6" button.textContent = "\uF2A6";
// Click propagation isn't disabled as this causes problems with the // Click propagation isn't disabled as this causes problems with the
// location picking mode; instead propagation is stopped in onClick(). // location picking mode; instead propagation is stopped in onClick().
L.DomEvent.addListener(button, "click", this.onClick, this) L.DomEvent.addListener(button, "click", this.onClick, this);
this.button = button this.button = button;
return button return button;
}, },
update: function () { update: function () {
this.button.classList.toggle("active", this.active) this.button.classList.toggle("active", this.active);
}, },
set: function (v) { set: function (v) {
this.active = v this.active = v;
this.update() this.update();
}, },
onClick: function (e) { onClick: function (e) {
L.DomEvent.stopPropagation(e) L.DomEvent.stopPropagation(e);
this.f(!this.active) this.f(!this.active);
} }
}) });
function mkMarker(dict, iconFunc, router) { function mkMarker(dict, iconFunc, router) {
return function (d) { return function (d) {
var m = L.circleMarker([d.nodeinfo.location.latitude, d.nodeinfo.location.longitude], iconFunc(d)) var m = L.circleMarker([d.nodeinfo.location.latitude, d.nodeinfo.location.longitude], iconFunc(d));
m.resetStyle = function () { m.resetStyle = function () {
m.setStyle(iconFunc(d)) m.setStyle(iconFunc(d));
} };
m.on("click", router.node(d)) m.on("click", router.node(d));
m.bindLabel(d.nodeinfo.hostname) m.bindLabel(d.nodeinfo.hostname);
dict[d.nodeinfo.node_id] = m dict[d.nodeinfo.node_id] = m;
return m return m;
} };
} }
function addLinksToMap(dict, linkScale, graph, router) { function addLinksToMap(dict, linkScale, graph, router) {
graph = graph.filter(function (d) { graph = graph.filter(function (d) {
return "distance" in d && d.type !== "VPN" return "distance" in d && d.type !== "VPN";
}) });
var lines = graph.map(function (d) { var lines = graph.map(function (d) {
var opts = { color: d.type === "Kabel" ? "#50B0F0" : linkScale(d.tq).hex(), var opts = {
color: d.type === "Kabel" ? "#50B0F0" : linkScale(d.tq).hex(),
weight: 4, weight: 4,
opacity: 0.5, opacity: 0.5,
dashArray: "none" dashArray: "none"
} };
var line = L.polyline(d.latlngs, opts) var line = L.polyline(d.latlngs, opts);
line.resetStyle = function () { line.resetStyle = function () {
line.setStyle(opts) line.setStyle(opts);
};
line.bindLabel(d.source.node.nodeinfo.hostname + " " + d.target.node.nodeinfo.hostname + "<br><strong>" + showDistance(d) + " / " + showTq(d) + "</strong>");
line.on("click", router.link(d));
dict[d.id] = line;
return line;
});
return lines;
} }
line.bindLabel(d.source.node.nodeinfo.hostname + " " + d.target.node.nodeinfo.hostname + "<br><strong>" + showDistance(d) + " / " + showTq(d) + "</strong>") var iconOnline = {
line.on("click", router.link(d)) color: "#1566A9",
fillColor: "#1566A9",
dict[d.id] = line radius: 6,
fillOpacity: 0.5,
return line opacity: 0.5,
}) weight: 2,
className: "stroke-first"
return lines };
} var iconOffline = {
color: "#D43E2A",
var iconOnline = { color: "#1566A9", fillColor: "#1566A9", radius: 6, fillOpacity: 0.5, opacity: 0.5, weight: 2, className: "stroke-first" } fillColor: "#D43E2A",
var iconOffline = { color: "#D43E2A", fillColor: "#D43E2A", radius: 3, fillOpacity: 0.5, opacity: 0.5, weight: 1, className: "stroke-first" } radius: 3,
var iconLost = { color: "#D43E2A", fillColor: "#D43E2A", radius: 6, fillOpacity: 0.8, opacity: 0.8, weight: 1, className: "stroke-first" } fillOpacity: 0.5,
var iconAlert = { color: "#D43E2A", fillColor: "#D43E2A", radius: 6, fillOpacity: 0.8, opacity: 0.8, weight: 2, className: "stroke-first node-alert" } opacity: 0.5,
var iconNew = { color: "#1566A9", fillColor: "#93E929", radius: 6, fillOpacity: 1.0, opacity: 0.5, weight: 2 } weight: 1,
className: "stroke-first"
};
var iconLost = {
color: "#D43E2A",
fillColor: "#D43E2A",
radius: 6,
fillOpacity: 0.8,
opacity: 0.8,
weight: 1,
className: "stroke-first"
};
var iconAlert = {
color: "#D43E2A",
fillColor: "#D43E2A",
radius: 6,
fillOpacity: 0.8,
opacity: 0.8,
weight: 2,
className: "stroke-first node-alert"
};
var iconNew = {color: "#1566A9", fillColor: "#93E929", radius: 6, fillOpacity: 1.0, opacity: 0.5, weight: 2};
return function (config, linkScale, sidebar, router, buttons) { return function (config, linkScale, sidebar, router, buttons) {
var self = this var self = this;
var barycenter var barycenter;
var groupOnline, groupOffline, groupNew, groupLost, groupLines var groupOnline, groupOffline, groupNew, groupLost, groupLines;
var savedView var savedView;
var map, userLocation var map, userLocation;
var layerControl var layerControl;
var customLayers = {} var customLayers = {};
var baseLayers = {} var baseLayers = {};
var locateUserButton = new LocateButton(function (d) { var locateUserButton = new LocateButton(function (d) {
if (d) if (d) {
enableTracking() enableTracking();
else } else {
disableTracking() disableTracking();
}) }
});
var mybuttons = [] var mybuttons = [];
function addButton(button) { function addButton(button) {
var el = button.onAdd() var el = button.onAdd();
mybuttons.push(el) mybuttons.push(el);
buttons.appendChild(el) buttons.appendChild(el);
} }
function clearButtons() { function clearButtons() {
mybuttons.forEach(function (d) { mybuttons.forEach(function (d) {
buttons.removeChild(d) buttons.removeChild(d);
}) });
} }
var showCoordsPickerButton = new CoordsPickerButton(function (d) { var showCoordsPickerButton = new CoordsPickerButton(function (d) {
if (d) if (d) {
enableCoords() enableCoords();
else } else {
disableCoords() disableCoords();
}) }
});
function saveView() { function saveView() {
savedView = {center: map.getCenter(), savedView = {
zoom: map.getZoom()} center: map.getCenter(),
zoom: map.getZoom()
};
} }
function enableTracking() { function enableTracking() {
map.locate({watch: true, map.locate({
watch: true,
enableHighAccuracy: true, enableHighAccuracy: true,
setView: true setView: true
}) });
locateUserButton.set(true) locateUserButton.set(true);
} }
function disableTracking() { function disableTracking() {
map.stopLocate() map.stopLocate();
locationError() locationError();
locateUserButton.set(false) locateUserButton.set(false);
} }
function enableCoords() { function enableCoords() {
map.getContainer().classList.add("pick-coordinates") map.getContainer().classList.add("pick-coordinates");
map.on("click", showCoordinates) map.on("click", showCoordinates);
showCoordsPickerButton.set(true) showCoordsPickerButton.set(true);
} }
function disableCoords() { function disableCoords() {
map.getContainer().classList.remove("pick-coordinates") map.getContainer().classList.remove("pick-coordinates");
map.off("click", showCoordinates) map.off("click", showCoordinates);
showCoordsPickerButton.set(false) showCoordsPickerButton.set(false);
} }
function showCoordinates(e) { function showCoordinates(e) {
router.gotoLocation(e.latlng) router.gotoLocation(e.latlng);
// window.prompt("Koordinaten (Lat, Lng)", e.latlng.lat.toFixed(9) + ", " + e.latlng.lng.toFixed(9)) // window.prompt("Koordinaten (Lat, Lng)", e.latlng.lat.toFixed(9) + ", " + e.latlng.lng.toFixed(9))
disableCoords() disableCoords();
} }
function locationFound(e) { function locationFound(e) {
if (!userLocation) if (!userLocation) {
userLocation = new LocationMarker(e.latlng).addTo(map) userLocation = new LocationMarker(e.latlng).addTo(map);
}
userLocation.setLatLng(e.latlng) userLocation.setLatLng(e.latlng);
userLocation.setAccuracy(e.accuracy) userLocation.setAccuracy(e.accuracy);
} }
function locationError() { function locationError() {
if (userLocation) { if (userLocation) {
map.removeLayer(userLocation) map.removeLayer(userLocation);
userLocation = null userLocation = null;
} }
} }
function addLayer(layerName) { function addLayer(layerName) {
if (layerName in baseLayers) if (layerName in baseLayers) {
return return;
}
if (layerName in customLayers) if (layerName in customLayers) {
return return;
}
try { try {
var layer = L.tileLayer.provider(layerName) var layer = L.tileLayer.provider(layerName);
layerControl.addBaseLayer(layer, layerName) layerControl.addBaseLayer(layer, layerName);
customLayers[layerName] = layer customLayers[layerName] = layer;
if (localStorageTest()) if (localStorageTest()) {
localStorage.setItem("map/customLayers", JSON.stringify(Object.keys(customLayers))) localStorage.setItem("map/customLayers", JSON.stringify(Object.keys(customLayers)));
}
} catch (e) { } catch (e) {
return
} }
} }
function contextMenuGotoLocation(e) { function contextMenuGotoLocation(e) {
router.gotoLocation(e.latlng) router.gotoLocation(e.latlng);
} }
var el = document.createElement("div") var el = document.createElement("div");
el.classList.add("map") el.classList.add("map");
map = L.map(el, options) map = L.map(el, options);
var layers = config.mapLayers.map(function (d) { var layers = config.mapLayers.map(function (d) {
return { return {
"name": d.name, "name": d.name,
"layer": "url" in d ? L.tileLayer(d.url, d.config) : L.tileLayer.provider(d.name) "layer": "url" in d ? L.tileLayer(d.url, d.config) : L.tileLayer.provider(d.name)
} };
}) });
layers[0].layer.addTo(map) layers[0].layer.addTo(map);
layers.forEach(function (d) { layers.forEach(function (d) {
baseLayers[d.name] = d.layer baseLayers[d.name] = d.layer;
}) });
map.on("locationfound", locationFound) map.on("locationfound", locationFound);
map.on("locationerror", locationError) map.on("locationerror", locationError);
map.on("dragend", saveView) map.on("dragend", saveView);
map.on("contextmenu", contextMenuGotoLocation) map.on("contextmenu", contextMenuGotoLocation);
addButton(locateUserButton) addButton(locateUserButton);
addButton(showCoordsPickerButton) addButton(showCoordsPickerButton);
addButton(new AddLayerButton(function () { addButton(new AddLayerButton(function () {
/*eslint no-alert:0*/ /*eslint no-alert:0*/
var layerName = prompt("Leaflet Provider:") var layerName = prompt("Leaflet Provider:");
addLayer(layerName) addLayer(layerName);
})) }));
layerControl = L.control.layers(baseLayers, [], {position: "bottomright"}) layerControl = L.control.layers(baseLayers, [], {position: "bottomright"});
layerControl.addTo(map) layerControl.addTo(map);
if (localStorageTest()) { if (localStorageTest()) {
var d = JSON.parse(localStorage.getItem("map/customLayers")) var d = JSON.parse(localStorage.getItem("map/customLayers"));
if (d)
d.forEach(addLayer)
d = JSON.parse(localStorage.getItem("map/selectedLayer"))
d = d && d.name in baseLayers ? baseLayers[d.name] : d && d.name in customLayers ? customLayers[d.name] : false
if (d) { if (d) {
map.removeLayer(layers[0].layer) d.forEach(addLayer);
map.addLayer(d) }
d = JSON.parse(localStorage.getItem("map/selectedLayer"));
d = d && d.name in baseLayers ? baseLayers[d.name] : d && d.name in customLayers ? customLayers[d.name] : false;
if (d) {
map.removeLayer(layers[0].layer);
map.addLayer(d);
} }
} }
var clientLayer = new ClientLayer({minZoom: 15}) var clientLayer = new ClientLayer({minZoom: 15});
clientLayer.addTo(map) clientLayer.addTo(map);
clientLayer.setZIndex(5) clientLayer.setZIndex(5);
var labelsLayer = new LabelsLayer({}) var labelsLayer = new LabelsLayer({});
labelsLayer.addTo(map) labelsLayer.addTo(map);
labelsLayer.setZIndex(6) labelsLayer.setZIndex(6);
map.on("baselayerchange", function (e) { map.on("baselayerchange", function (e) {
map.options.maxZoom = e.layer.options.maxZoom map.options.maxZoom = e.layer.options.maxZoom;
clientLayer.options.maxZoom = map.options.maxZoom clientLayer.options.maxZoom = map.options.maxZoom;
labelsLayer.options.maxZoom = map.options.maxZoom labelsLayer.options.maxZoom = map.options.maxZoom;
if (map.getZoom() > map.options.maxZoom) map.setZoom(map.options.maxZoom) if (map.getZoom() > map.options.maxZoom) {
if (localStorageTest()) map.setZoom(map.options.maxZoom);
localStorage.setItem("map/selectedLayer", JSON.stringify({name: e.name})) }
}) if (localStorageTest()) {
localStorage.setItem("map/selectedLayer", JSON.stringify({name: e.name}));
}
});
var nodeDict = {} var nodeDict = {};
var linkDict = {} var linkDict = {};
var highlight var highlight;
function resetMarkerStyles(nodes, links) { function resetMarkerStyles(nodes, links) {
Object.keys(nodes).forEach(function (d) { Object.keys(nodes).forEach(function (d) {
nodes[d].resetStyle() nodes[d].resetStyle();
}) });
Object.keys(links).forEach(function (d) { Object.keys(links).forEach(function (d) {
links[d].resetStyle() links[d].resetStyle();
}) });
} }
function setView(bounds) { function setView(bounds) {
map.fitBounds(bounds, {paddingTopLeft: [sidebar(), 0]}) map.fitBounds(bounds, {paddingTopLeft: [sidebar(), 0]});
} }
function resetZoom() { function resetZoom() {
if (barycenter) if (barycenter) {
setView(barycenter.getBounds()) setView(barycenter.getBounds());
}
} }
function goto(m) { function goto(m) {
var bounds var bounds;
if ("getBounds" in m) if ("getBounds" in m) {
bounds = m.getBounds() bounds = m.getBounds();
else } else {
bounds = L.latLngBounds([m.getLatLng()]) bounds = L.latLngBounds([m.getLatLng()]);
}
setView(bounds) setView(bounds);
return m return m;
} }
function updateView(nopanzoom) { function updateView(nopanzoom) {
resetMarkerStyles(nodeDict, linkDict) resetMarkerStyles(nodeDict, linkDict);
var m var m;
if (highlight !== undefined) if (highlight !== undefined) {
if (highlight.type === "node") { if (highlight.type === "node") {
m = nodeDict[highlight.o.nodeinfo.node_id] m = nodeDict[highlight.o.nodeinfo.node_id];
if (m) if (m) {
m.setStyle({ color: "orange", weight: 20, fillOpacity: 1, opacity: 0.7, className: "stroke-first" }) m.setStyle({color: "orange", weight: 20, fillOpacity: 1, opacity: 0.7, className: "stroke-first"});
}
} else if (highlight.type === "link") { } else if (highlight.type === "link") {
m = linkDict[highlight.o.id] m = linkDict[highlight.o.id];
if (m) if (m) {
m.setStyle({ weight: 7, opacity: 1, dashArray: "10, 10" }) m.setStyle({weight: 7, opacity: 1, dashArray: "10, 10"});
}
}
} }
if (!nopanzoom) if (!nopanzoom) {
if (m) if (m) {
goto(m) goto(m);
else if (savedView) } else if (savedView) {
map.setView(savedView.center, savedView.zoom) map.setView(savedView.center, savedView.zoom);
else } else {
resetZoom() 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;
});
if (nodes.length === 0) if (nodes.length === 0) {
return undefined return undefined;
}
var lats = nodes.map(function (d) { return d.latitude }) var lats = nodes.map(function (d) {
var lngs = nodes.map(function (d) { return d.longitude }) return d.latitude;
});
var lngs = nodes.map(function (d) {
return d.longitude;
});
var barycenter = L.latLng(d3.median(lats), d3.median(lngs)) var barycenter = L.latLng(d3.median(lats), d3.median(lngs));
var barycenterDev = [d3.deviation(lats), d3.deviation(lngs)] var barycenterDev = [d3.deviation(lats), d3.deviation(lngs)];
if (barycenterDev[0] === undefined) if (barycenterDev[0] === undefined) {
barycenterDev[0] = 0 barycenterDev[0] = 0;
}
if (barycenterDev[1] === undefined) if (barycenterDev[1] === undefined) {
barycenterDev[1] = 0 barycenterDev[1] = 0;
}
var barycenterCircle = L.latLng(barycenter.lat + barycenterDev[0], var barycenterCircle = L.latLng(barycenter.lat + barycenterDev[0],
barycenter.lng + barycenterDev[1]) barycenter.lng + barycenterDev[1]);
var r = barycenter.distanceTo(barycenterCircle) var r = barycenter.distanceTo(barycenterCircle);
return L.circle(barycenter, r * config.mapSigmaScale) return L.circle(barycenter, r * config.mapSigmaScale);
} }
function mapRTree(d) { function mapRTree(d) {
var o = [d.nodeinfo.location.latitude, d.nodeinfo.location.longitude, var o = [d.nodeinfo.location.latitude, d.nodeinfo.location.longitude,
d.nodeinfo.location.latitude, d.nodeinfo.location.longitude] d.nodeinfo.location.latitude, d.nodeinfo.location.longitude];
o.node = d o.node = d;
return o return o;
} }
self.setData = function (data) { self.setData = function (data) {
nodeDict = {} nodeDict = {};
linkDict = {} linkDict = {};
if (groupOffline) if (groupOffline) {
groupOffline.clearLayers() groupOffline.clearLayers();
}
if (groupOnline) if (groupOnline) {
groupOnline.clearLayers() groupOnline.clearLayers();
}
if (groupNew) if (groupNew) {
groupNew.clearLayers() groupNew.clearLayers();
}
if (groupLost) if (groupLost) {
groupLost.clearLayers() groupLost.clearLayers();
}
if (groupLines) if (groupLines) {
groupLines.clearLayers() groupLines.clearLayers();
}
var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router) var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router);
groupLines = L.featureGroup(lines).addTo(map) groupLines = L.featureGroup(lines).addTo(map);
if (typeof config.fixedCenter === "undefined") if (typeof config.fixedCenter === "undefined") {
barycenter = calcBarycenter(data.nodes.all.filter(has_location)) barycenter = calcBarycenter(data.nodes.all.filter(has_location));
else } else {
barycenter = L.circle(L.latLng(new L.LatLng(config.fixedCenter.lat, config.fixedCenter.lng)), config.fixedCenter.radius * 1000) barycenter = L.circle(L.latLng(new L.LatLng(config.fixedCenter.lat, config.fixedCenter.lng)), config.fixedCenter.radius * 1000);
}
var nodesOnline = subtract(data.nodes.all.filter(online), data.nodes.new) var nodesOnline = subtract(data.nodes.all.filter(online), data.nodes.new);
var nodesOffline = subtract(data.nodes.all.filter(offline), data.nodes.lost) var nodesOffline = subtract(data.nodes.all.filter(offline), data.nodes.lost);
var markersOnline = nodesOnline.filter(has_location) var markersOnline = nodesOnline.filter(has_location)
.map(mkMarker(nodeDict, function () { return iconOnline }, router)) .map(mkMarker(nodeDict, function () {
return iconOnline;
}, router));
var markersOffline = nodesOffline.filter(has_location) var markersOffline = nodesOffline.filter(has_location)
.map(mkMarker(nodeDict, function () { return iconOffline }, router)) .map(mkMarker(nodeDict, function () {
return iconOffline;
}, router));
var markersNew = data.nodes.new.filter(has_location) var markersNew = data.nodes.new.filter(has_location)
.map(mkMarker(nodeDict, function () { return iconNew }, router)) .map(mkMarker(nodeDict, function () {
return iconNew;
}, router));
var markersLost = data.nodes.lost.filter(has_location) var markersLost = data.nodes.lost.filter(has_location)
.map(mkMarker(nodeDict, function (d) { .map(mkMarker(nodeDict, function (d) {
if (d.lastseen.isAfter(moment(data.now).subtract(3, "days"))) if (d.lastseen.isAfter(moment(data.now).subtract(3, "days"))) {
return iconAlert return iconAlert;
}
return iconLost return iconLost;
}, router)) }, router));
groupOffline = L.featureGroup(markersOffline).addTo(map) groupOffline = L.featureGroup(markersOffline).addTo(map);
groupOnline = L.featureGroup(markersOnline).addTo(map) groupOnline = L.featureGroup(markersOnline).addTo(map);
groupLost = L.featureGroup(markersLost).addTo(map) groupLost = L.featureGroup(markersLost).addTo(map);
groupNew = L.featureGroup(markersNew).addTo(map) groupNew = L.featureGroup(markersNew).addTo(map);
var rtreeOnlineAll = rbush(9) var rtreeOnlineAll = rbush(9);
rtreeOnlineAll.load(data.nodes.all.filter(online).filter(has_location).map(mapRTree)) rtreeOnlineAll.load(data.nodes.all.filter(online).filter(has_location).map(mapRTree));
clientLayer.setData(rtreeOnlineAll) clientLayer.setData(rtreeOnlineAll);
labelsLayer.setData({online: nodesOnline.filter(has_location), labelsLayer.setData({
online: nodesOnline.filter(has_location),
offline: nodesOffline.filter(has_location), offline: nodesOffline.filter(has_location),
new: data.nodes.new.filter(has_location), new: data.nodes.new.filter(has_location),
lost: data.nodes.lost.filter(has_location) lost: data.nodes.lost.filter(has_location)
}) });
updateView(true) updateView(true);
} };
self.resetView = function () { self.resetView = function () {
disableTracking() disableTracking();
highlight = undefined highlight = undefined;
updateView() updateView();
} };
self.gotoNode = function (d) { self.gotoNode = function (d) {
disableTracking() disableTracking();
highlight = {type: "node", o: d} highlight = {type: "node", o: d};
updateView() updateView();
} };
self.gotoLink = function (d) { self.gotoLink = function (d) {
disableTracking() disableTracking();
highlight = {type: "link", o: d} highlight = {type: "link", o: d};
updateView() updateView();
} };
self.gotoLocation = function () { self.gotoLocation = function () {
//ignore //ignore
} };
self.destroy = function () { self.destroy = function () {
clearButtons() clearButtons();
map.remove() map.remove();
if (el.parentNode) if (el.parentNode) {
el.parentNode.removeChild(el) el.parentNode.removeChild(el);
} }
};
self.render = function (d) { self.render = function (d) {
d.appendChild(el) d.appendChild(el);
map.invalidateSize() map.invalidateSize();
} };
return self return self;
} };
}) });

View file

@ -1,76 +1,79 @@
define(["leaflet", "jshashes"], define(["leaflet", "jshashes"],
function (L, jsHashes) { function (L, jsHashes) {
var MD5 = new jsHashes.MD5() var MD5 = new jsHashes.MD5();
return L.TileLayer.Canvas.extend({ return L.TileLayer.Canvas.extend({
setData: function (d) { setData: function (d) {
this.data = d this.data = d;
//pre-calculate start angles //pre-calculate start angles
this.data.all().forEach(function (d) { this.data.all().forEach(function (d) {
var hash = MD5.hex(d.node.nodeinfo.node_id) var hash = MD5.hex(d.node.nodeinfo.node_id);
d.startAngle = (parseInt(hash.substr(0, 2), 16) / 255) * 2 * Math.PI d.startAngle = (parseInt(hash.substr(0, 2), 16) / 255) * 2 * Math.PI;
}) });
this.redraw() this.redraw();
}, },
drawTile: function (canvas, tilePoint) { drawTile: function (canvas, tilePoint) {
function getTileBBox(s, map, tileSize, margin) { function getTileBBox(s, map, tileSize, margin) {
var tl = map.unproject([s.x - margin, s.y - margin]) var tl = map.unproject([s.x - margin, s.y - margin]);
var br = map.unproject([s.x + margin + tileSize, s.y + margin + tileSize]) var br = map.unproject([s.x + margin + tileSize, s.y + margin + tileSize]);
return [br.lat, tl.lng, tl.lat, br.lng] return [br.lat, tl.lng, tl.lat, br.lng];
} }
if (!this.data) if (!this.data) {
return return;
}
var tileSize = this.options.tileSize var tileSize = this.options.tileSize;
var s = tilePoint.multiplyBy(tileSize) var s = tilePoint.multiplyBy(tileSize);
var map = this._map var map = this._map;
var margin = 50 var margin = 50;
var bbox = getTileBBox(s, map, tileSize, margin) var bbox = getTileBBox(s, map, tileSize, margin);
var nodes = this.data.search(bbox) var nodes = this.data.search(bbox);
if (nodes.length === 0) if (nodes.length === 0) {
return return;
}
var ctx = canvas.getContext("2d") var ctx = canvas.getContext("2d");
var radius = 3 var radius = 3;
var a = 1.2 var a = 1.2;
var startDistance = 12 var startDistance = 12;
ctx.beginPath() ctx.beginPath();
nodes.forEach(function (d) { nodes.forEach(function (d) {
var p = map.project([d.node.nodeinfo.location.latitude, d.node.nodeinfo.location.longitude]) var p = map.project([d.node.nodeinfo.location.latitude, d.node.nodeinfo.location.longitude]);
var clients = d.node.statistics.clients var clients = d.node.statistics.clients;
if (clients === 0) if (clients === 0) {
return return;
}
p.x -= s.x p.x -= s.x;
p.y -= s.y p.y -= s.y;
for (var orbit = 0, i = 0; i < clients; orbit++) { for (var orbit = 0, i = 0; i < clients; orbit++) {
var distance = startDistance + orbit * 2 * radius * a var distance = startDistance + orbit * 2 * radius * a;
var n = Math.floor((Math.PI * distance) / (a * radius)) var n = Math.floor((Math.PI * distance) / (a * radius));
var delta = clients - i var delta = clients - i;
for (var j = 0; j < Math.min(delta, n); i++, j++) { for (var j = 0; j < Math.min(delta, n); i++, j++) {
var angle = 2 * Math.PI / n * j var angle = 2 * Math.PI / n * j;
var x = p.x + distance * Math.cos(angle + d.startAngle) var x = p.x + distance * Math.cos(angle + d.startAngle);
var y = p.y + distance * Math.sin(angle + d.startAngle) var y = p.y + distance * Math.sin(angle + d.startAngle);
ctx.moveTo(x, y) ctx.moveTo(x, y);
ctx.arc(x, y, radius, 0, 2 * Math.PI) ctx.arc(x, y, radius, 0, 2 * Math.PI);
} }
} }
}) });
ctx.fillStyle = "rgba(220, 0, 103, 0.7)" ctx.fillStyle = "rgba(220, 0, 103, 0.7)";
ctx.fill() ctx.fill();
} }
}) });
}) });

View file

@ -7,30 +7,31 @@ define(["leaflet", "rbush"],
["left", "ideographic", 1 / 8], ["left", "ideographic", 1 / 8],
["right", "top", 5 / 8], ["right", "top", 5 / 8],
["center", "ideographic", 2 / 8], ["center", "ideographic", 2 / 8],
["right", "ideographic", 3 / 8]] ["right", "ideographic", 3 / 8]];
var fontFamily = "Roboto" var fontFamily = "Roboto";
var nodeRadius = 4 var nodeRadius = 4;
var ctx = document.createElement("canvas").getContext("2d") var ctx = document.createElement("canvas").getContext("2d");
function measureText(font, text) { function measureText(font, text) {
ctx.font = font ctx.font = font;
return ctx.measureText(text) return ctx.measureText(text);
} }
function mapRTree(d) { function mapRTree(d) {
var o = [d.position.lat, d.position.lng, d.position.lat, d.position.lng] var o = [d.position.lat, d.position.lng, d.position.lat, d.position.lng];
o.label = d o.label = d;
return o return o;
} }
function prepareLabel(fillStyle, fontSize, offset, stroke, minZoom) { function prepareLabel(fillStyle, fontSize, offset, stroke, minZoom) {
return function (d) { return function (d) {
var font = fontSize + "px " + fontFamily var font = fontSize + "px " + fontFamily;
return { position: L.latLng(d.nodeinfo.location.latitude, d.nodeinfo.location.longitude), return {
position: L.latLng(d.nodeinfo.location.latitude, d.nodeinfo.location.longitude),
label: d.nodeinfo.hostname, label: d.nodeinfo.hostname,
offset: offset, offset: offset,
fillStyle: fillStyle, fillStyle: fillStyle,
@ -39,50 +40,54 @@ define(["leaflet", "rbush"],
stroke: stroke, stroke: stroke,
minZoom: minZoom, minZoom: minZoom,
width: measureText(font, d.nodeinfo.hostname).width width: measureText(font, d.nodeinfo.hostname).width
} };
} };
} }
function calcOffset(offset, loc) { function calcOffset(offset, loc) {
return [offset * Math.cos(loc[2] * 2 * Math.PI), return [offset * Math.cos(loc[2] * 2 * Math.PI),
-offset * Math.sin(loc[2] * 2 * Math.PI)] -offset * Math.sin(loc[2] * 2 * Math.PI)];
} }
function labelRect(p, offset, anchor, label, minZoom, maxZoom, z) { function labelRect(p, offset, anchor, label, minZoom, maxZoom, z) {
var margin = 1 + 1.41 * (1 - (z - minZoom) / (maxZoom - minZoom)) var margin = 1 + 1.41 * (1 - (z - minZoom) / (maxZoom - minZoom));
var width = label.width * margin var width = label.width * margin;
var height = label.height * margin var height = label.height * margin;
var dx = { left: 0, var dx = {
left: 0,
right: -width, right: -width,
center: -width / 2 center: -width / 2
} };
var dy = { top: 0, var dy = {
top: 0,
ideographic: -height, ideographic: -height,
middle: -height / 2 middle: -height / 2
} };
var x = p.x + offset[0] + dx[anchor[0]] var x = p.x + offset[0] + dx[anchor[0]];
var y = p.y + offset[1] + dy[anchor[1]] var y = p.y + offset[1] + dy[anchor[1]];
return [x, y, x + width, y + height] return [x, y, x + width, y + height];
} }
var c = L.TileLayer.Canvas.extend({ var c = L.TileLayer.Canvas.extend({
onAdd: function (map) { onAdd: function (map) {
L.TileLayer.Canvas.prototype.onAdd.call(this, map) L.TileLayer.Canvas.prototype.onAdd.call(this, map);
if (this.data) if (this.data) {
this.prepareLabels() this.prepareLabels();
}
}, },
setData: function (d) { setData: function (d) {
this.data = d this.data = d;
if (this._map) if (this._map) {
this.prepareLabels() this.prepareLabels();
}
}, },
prepareLabels: function () { prepareLabels: function () {
var d = this.data var d = this.data;
// label: // label:
// - position (WGS84 coords) // - position (WGS84 coords)
@ -92,137 +97,144 @@ define(["leaflet", "rbush"],
// - label (string) // - label (string)
// - color (string) // - color (string)
var labelsOnline = d.online.map(prepareLabel("rgba(0, 0, 0, 0.9)", 10, 8, true, 13)) var labelsOnline = d.online.map(prepareLabel("rgba(0, 0, 0, 0.9)", 10, 8, true, 13));
var labelsOffline = d.offline.map(prepareLabel("rgba(212, 62, 42, 0.9)", 9, 5, false, 16)) var labelsOffline = d.offline.map(prepareLabel("rgba(212, 62, 42, 0.9)", 9, 5, false, 16));
var labelsNew = d.new.map(prepareLabel("rgba(48, 99, 20, 0.9)", 11, 8, true, 0)) var labelsNew = d.new.map(prepareLabel("rgba(48, 99, 20, 0.9)", 11, 8, true, 0));
var labelsLost = d.lost.map(prepareLabel("rgba(212, 62, 42, 0.9)", 11, 8, true, 0)) var labelsLost = d.lost.map(prepareLabel("rgba(212, 62, 42, 0.9)", 11, 8, true, 0));
var labels = [] var labels = []
.concat(labelsNew) .concat(labelsNew)
.concat(labelsLost) .concat(labelsLost)
.concat(labelsOnline) .concat(labelsOnline)
.concat(labelsOffline) .concat(labelsOffline);
var minZoom = this.options.minZoom var minZoom = this.options.minZoom;
var maxZoom = this.options.maxZoom var maxZoom = this.options.maxZoom;
var trees = [] var trees = [];
var map = this._map var map = this._map;
function nodeToRect(z) { function nodeToRect(z) {
return function (d) { return function (d) {
var p = map.project(d.position, z) var p = map.project(d.position, z);
return [p.x - nodeRadius, p.y - nodeRadius, return [p.x - nodeRadius, p.y - nodeRadius,
p.x + nodeRadius, p.y + nodeRadius] p.x + nodeRadius, p.y + nodeRadius];
} };
} }
for (var z = minZoom; z <= maxZoom; z++) { for (var z = minZoom; z <= maxZoom; z++) {
trees[z] = rbush(9) trees[z] = rbush(9);
trees[z].load(labels.map(nodeToRect(z))) trees[z].load(labels.map(nodeToRect(z)));
} }
labels = labels.map(function (d) { labels = labels.map(function (d) {
var best = labelLocations.map(function (loc) { var best = labelLocations.map(function (loc) {
var offset = calcOffset(d.offset, loc) var offset = calcOffset(d.offset, loc);
var z var z;
for (z = maxZoom; z >= d.minZoom; z--) { for (z = maxZoom; z >= d.minZoom; z--) {
var p = map.project(d.position, z) var p = map.project(d.position, z);
var rect = labelRect(p, offset, loc, d, minZoom, maxZoom, z) var rect = labelRect(p, offset, loc, d, minZoom, maxZoom, z);
var candidates = trees[z].search(rect) var candidates = trees[z].search(rect);
if (candidates.length > 0) if (candidates.length > 0) {
break break;
}
} }
return {loc: loc, z: z + 1} return {loc: loc, z: z + 1};
}).filter(function (d) { }).filter(function (d) {
return d.z <= maxZoom return d.z <= maxZoom;
}).sort(function (a, b) { }).sort(function (a, b) {
return a.z - b.z return a.z - b.z;
})[0] })[0];
if (best !== undefined) { if (best !== undefined) {
d.offset = calcOffset(d.offset, best.loc) d.offset = calcOffset(d.offset, best.loc);
d.minZoom = best.z d.minZoom = best.z;
d.anchor = best.loc d.anchor = best.loc;
for (var z = maxZoom; z >= best.z; z--) { for (var z = maxZoom; z >= best.z; z--) {
var p = map.project(d.position, z) var p = map.project(d.position, z);
var rect = labelRect(p, d.offset, best.loc, d, minZoom, maxZoom, z) var rect = labelRect(p, d.offset, best.loc, d, minZoom, maxZoom, z);
trees[z].insert(rect) trees[z].insert(rect);
} }
return d return d;
} else } else {
return undefined return undefined;
}).filter(function (d) { return d !== undefined }) }
}).filter(function (d) {
return d !== undefined;
});
this.margin = 16 this.margin = 16;
if (labels.length > 0) if (labels.length > 0) {
this.margin += labels.map(function (d) { this.margin += labels.map(function (d) {
return d.width return d.width;
}).sort().reverse()[0] }).sort().reverse()[0];
}
this.labels = rbush(9) this.labels = rbush(9);
this.labels.load(labels.map(mapRTree)) this.labels.load(labels.map(mapRTree));
this.redraw() this.redraw();
}, },
drawTile: function (canvas, tilePoint, zoom) { drawTile: function (canvas, tilePoint, zoom) {
function getTileBBox(s, map, tileSize, margin) { function getTileBBox(s, map, tileSize, margin) {
var tl = map.unproject([s.x - margin, s.y - margin]) var tl = map.unproject([s.x - margin, s.y - margin]);
var br = map.unproject([s.x + margin + tileSize, s.y + margin + tileSize]) var br = map.unproject([s.x + margin + tileSize, s.y + margin + tileSize]);
return [br.lat, tl.lng, tl.lat, br.lng] return [br.lat, tl.lng, tl.lat, br.lng];
} }
if (!this.labels) if (!this.labels) {
return return;
}
var tileSize = this.options.tileSize var tileSize = this.options.tileSize;
var s = tilePoint.multiplyBy(tileSize) var s = tilePoint.multiplyBy(tileSize);
var map = this._map var map = this._map;
function projectNodes(d) { function projectNodes(d) {
var p = map.project(d.label.position) var p = map.project(d.label.position);
p.x -= s.x p.x -= s.x;
p.y -= s.y p.y -= s.y;
return {p: p, label: d.label} return {p: p, label: d.label};
} }
var bbox = getTileBBox(s, map, tileSize, this.margin) var bbox = getTileBBox(s, map, tileSize, this.margin);
var labels = this.labels.search(bbox).map(projectNodes) var labels = this.labels.search(bbox).map(projectNodes);
var ctx = canvas.getContext("2d") var ctx = canvas.getContext("2d");
ctx.lineWidth = 5 ctx.lineWidth = 5;
ctx.strokeStyle = "rgba(255, 255, 255, 0.8)" ctx.strokeStyle = "rgba(255, 255, 255, 0.8)";
ctx.miterLimit = 2 ctx.miterLimit = 2;
function drawLabel(d) { function drawLabel(d) {
ctx.font = d.label.font ctx.font = d.label.font;
ctx.textAlign = d.label.anchor[0] ctx.textAlign = d.label.anchor[0];
ctx.textBaseline = d.label.anchor[1] ctx.textBaseline = d.label.anchor[1];
ctx.fillStyle = d.label.fillStyle ctx.fillStyle = d.label.fillStyle;
if (d.label.stroke) if (d.label.stroke) {
ctx.strokeText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1]) ctx.strokeText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1]);
}
ctx.fillText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1]) ctx.fillText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1]);
} }
labels.filter(function (d) { labels.filter(function (d) {
return zoom >= d.label.minZoom return zoom >= d.label.minZoom;
}).forEach(drawLabel) }).forEach(drawLabel);
} }
}) });
return c return c;
}) });

View file

@ -1,55 +1,61 @@
define(function () { define(function () {
return function (config) { return function (config) {
var self = this var self = this;
var stats, timestamp var stats, timestamp;
self.setData = function (d) { self.setData = function (d) {
var totalNodes = sum(d.nodes.all.map(one)) var totalNodes = sum(d.nodes.all.map(one));
var totalOnlineNodes = sum(d.nodes.all.filter(online).map(one)) var totalOnlineNodes = sum(d.nodes.all.filter(online).map(one));
var totalOfflineNodes = sum(d.nodes.all.filter(function (node) {return !node.flags.online}).map(one)) var totalOfflineNodes = sum(d.nodes.all.filter(function (node) {
var totalNewNodes = sum(d.nodes.new.map(one)) return !node.flags.online;
var totalLostNodes = sum(d.nodes.lost.map(one)) }).map(one));
var totalNewNodes = sum(d.nodes.new.map(one));
var totalLostNodes = sum(d.nodes.lost.map(one));
var totalClients = sum(d.nodes.all.filter(online).map(function (d) { var totalClients = sum(d.nodes.all.filter(online).map(function (d) {
return d.statistics.clients ? d.statistics.clients : 0 return d.statistics.clients ? d.statistics.clients : 0;
})) }));
var totalGateways = sum(Array.from(new Set(d.nodes.all.filter(online).map(function (d) { var totalGateways = sum(Array.from(new Set(d.nodes.all.filter(online).map(function (d) {
return ("gateway" in d.statistics && d.statistics.gateway.id) ? d.statistics.gateway.id : d.statistics.gateway return ("gateway" in d.statistics && d.statistics.gateway.id) ? d.statistics.gateway.id : d.statistics.gateway;
}).concat(d.nodes.all.filter(function (d) { }).concat(d.nodes.all.filter(function (d) {
return d.flags.gateway return d.flags.gateway;
})))).map(function (d) { })))).map(function (d) {
return (typeof d === "string") ? 1 : 0 return (typeof d === "string") ? 1 : 0;
})) }));
var nodetext = [{count: totalOnlineNodes, label: "online"}, var nodetext = [{count: totalOnlineNodes, label: "online"},
{count: totalOfflineNodes, label: "offline"}, {count: totalOfflineNodes, label: "offline"},
{count: totalNewNodes, label: "neu"}, {count: totalNewNodes, label: "neu"},
{count: totalLostNodes, label: "verschwunden"} {count: totalLostNodes, label: "verschwunden"}
].filter( function (d) { return d.count > 0 } ) ].filter(function (d) {
.map( function (d) { return [d.count, d.label].join(" ") } ) return d.count > 0;
.join(", ") })
.map(function (d) {
return [d.count, d.label].join(" ");
})
.join(", ");
stats.textContent = totalNodes + " Knoten " + stats.textContent = totalNodes + " Knoten " +
"(" + nodetext + "), " + "(" + nodetext + "), " +
totalClients + " Client" + ( totalClients === 1 ? ", " : "s, " ) + totalClients + " Client" + ( totalClients === 1 ? ", " : "s, " ) +
totalGateways + " Gateways" totalGateways + " Gateways";
timestamp.textContent = "Diese Daten sind von " + d.timestamp.format("LLLL") + "." timestamp.textContent = "Diese Daten sind von " + d.timestamp.format("LLLL") + ".";
} };
self.render = function (el) { self.render = function (el) {
var h2 = document.createElement("h2") var h2 = document.createElement("h2");
h2.textContent = config.siteName h2.textContent = config.siteName;
el.appendChild(h2) el.appendChild(h2);
var p = document.createElement("p") var p = document.createElement("p");
el.appendChild(p) el.appendChild(p);
stats = document.createTextNode("") stats = document.createTextNode("");
p.appendChild(stats) p.appendChild(stats);
p.appendChild(document.createElement("br")) p.appendChild(document.createElement("br"));
timestamp = document.createTextNode("") timestamp = document.createTextNode("");
p.appendChild(timestamp) p.appendChild(timestamp);
} };
return self return self;
} };
}) });

View file

@ -1,97 +1,107 @@
define(["sorttable", "virtual-dom", "numeral"], function (SortTable, V, numeral) { define(["sorttable", "virtual-dom", "numeral"], function (SortTable, V, numeral) {
function getUptime(now, d) { function getUptime(now, d) {
if (d.flags.online && "uptime" in d.statistics) if (d.flags.online && "uptime" in d.statistics) {
return Math.round(d.statistics.uptime) return Math.round(d.statistics.uptime);
else if (!d.flags.online && "lastseen" in d) } else if (!d.flags.online && "lastseen" in d) {
return Math.round(-(now.unix() - d.lastseen.unix())) return Math.round(-(now.unix() - d.lastseen.unix()));
}
} }
function showUptime(uptime) { function showUptime(uptime) {
var s = "" var s = "";
uptime /= 3600 uptime /= 3600;
if (uptime !== undefined) if (uptime !== undefined) {
if (Math.abs(uptime) >= 24) if (Math.abs(uptime) >= 24) {
s = Math.round(uptime / 24) + "d" s = Math.round(uptime / 24) + "d";
else } else {
s = Math.round(uptime) + "h" s = Math.round(uptime) + "h";
}
return s
} }
var headings = [{ name: "Knoten", return s;
}
var headings = [{
name: "Knoten",
sort: function (a, b) { sort: function (a, b) {
var aname = typeof a.nodeinfo.hostname === "string" ? a.nodeinfo.hostname : a.nodeinfo.node_id var aname = typeof a.nodeinfo.hostname === "string" ? a.nodeinfo.hostname : a.nodeinfo.node_id;
var bname = typeof b.nodeinfo.hostname === "string" ? b.nodeinfo.hostname : b.nodeinfo.node_id var bname = typeof b.nodeinfo.hostname === "string" ? b.nodeinfo.hostname : b.nodeinfo.node_id;
if (typeof aname === "string" && typeof bname === "string") if (typeof aname === "string" && typeof bname === "string") {
return aname.localeCompare(bname) return aname.localeCompare(bname);
return typeof aname === "string" ? 1 : typeof bname === "string" ? -1 : 0 }
return typeof aname === "string" ? 1 : typeof bname === "string" ? -1 : 0;
}, },
reverse: false reverse: false
}, },
{ name: "Uptime", {
name: "Uptime",
sort: function (a, b) { sort: function (a, b) {
return a.uptime - b.uptime return a.uptime - b.uptime;
}, },
reverse: true reverse: true
}, },
{ name: "#Links", {
name: "#Links",
sort: function (a, b) { sort: function (a, b) {
return a.meshlinks - b.meshlinks return a.meshlinks - b.meshlinks;
}, },
reverse: true reverse: true
}, },
{ name: "Clients", {
name: "Clients",
sort: function (a, b) { sort: function (a, b) {
return ("clients" in a.statistics ? a.statistics.clients : -1) - return ("clients" in a.statistics ? a.statistics.clients : -1) -
("clients" in b.statistics ? b.statistics.clients : -1) ("clients" in b.statistics ? b.statistics.clients : -1);
}, },
reverse: true reverse: true
}] }];
return function (router) { return function (router) {
function renderRow(d) { function renderRow(d) {
var td1Content = [] var td1Content = [];
var aClass = ["hostname", d.flags.online ? "online" : "offline"] var aClass = ["hostname", d.flags.online ? "online" : "offline"];
td1Content.push(V.h("a", { className: aClass.join(" "), td1Content.push(V.h("a", {
className: aClass.join(" "),
onclick: router.node(d), onclick: router.node(d),
href: "#!n:" + d.nodeinfo.node_id href: "#!n:" + d.nodeinfo.node_id
}, d.nodeinfo.hostname)) }, d.nodeinfo.hostname));
if (has_location(d)) if (has_location(d)) {
td1Content.push(V.h("span", {className: "icon ion-location"})) 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", d.meshlinks.toString())
var td4 = V.h("td", numeral("clients" in d.statistics ? d.statistics.clients : "").format("0,0"))
return V.h("tr", [td1, td2, td3, td4])
} }
var table = new SortTable(headings, 0, renderRow) var td1 = V.h("td", td1Content);
var td2 = V.h("td", showUptime(d.uptime));
var td3 = V.h("td", d.meshlinks.toString());
var td4 = V.h("td", numeral("clients" in d.statistics ? d.statistics.clients : "").format("0,0"));
return V.h("tr", [td1, td2, td3, td4]);
}
var table = new SortTable(headings, 0, renderRow);
this.render = function (d) { this.render = function (d) {
var el = document.createElement("div") var el = document.createElement("div");
d.appendChild(el) d.appendChild(el);
var h2 = document.createElement("h2") var h2 = document.createElement("h2");
h2.textContent = "Alle Knoten" h2.textContent = "Alle Knoten";
el.appendChild(h2) el.appendChild(h2);
el.appendChild(table.el) el.appendChild(table.el);
} };
this.setData = function (d) { this.setData = function (d) {
var data = d.nodes.all.map(function (e) { var data = d.nodes.all.map(function (e) {
var n = Object.create(e) var n = Object.create(e);
n.uptime = getUptime(d.now, e) || 0 n.uptime = getUptime(d.now, e) || 0;
n.meshlinks = e.meshlinks || 0 n.meshlinks = e.meshlinks || 0;
return n return n;
}) });
table.setData(data) table.setData(data);
} };
} };
}) });

View file

@ -2,235 +2,277 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "verc
function (Chroma, V, numeral, Filter, vercomp) { function (Chroma, V, numeral, Filter, vercomp) {
return function (config, filterManager) { return function (config, filterManager) {
var self = this var self = this;
var scale = Chroma.scale("YlGnBu").mode("lab") var scale = Chroma.scale("YlGnBu").mode("lab");
var statusTable = document.createElement("table") var statusTable = document.createElement("table");
statusTable.classList.add("proportion") statusTable.classList.add("proportion");
var fwTable = document.createElement("table") var fwTable = document.createElement("table");
fwTable.classList.add("proportion") fwTable.classList.add("proportion");
var hwTable = document.createElement("table") var hwTable = document.createElement("table");
hwTable.classList.add("proportion") hwTable.classList.add("proportion");
var geoTable = document.createElement("table") var geoTable = document.createElement("table");
geoTable.classList.add("proportion") geoTable.classList.add("proportion");
var autoTable = document.createElement("table") var autoTable = document.createElement("table");
autoTable.classList.add("proportion") autoTable.classList.add("proportion");
var uplinkTable = document.createElement("table") var uplinkTable = document.createElement("table");
uplinkTable.classList.add("proportion") uplinkTable.classList.add("proportion");
var gwNodesTable = document.createElement("table") var gwNodesTable = document.createElement("table");
gwNodesTable.classList.add("proportion") gwNodesTable.classList.add("proportion");
var gwClientsTable = document.createElement("table") var gwClientsTable = document.createElement("table");
gwClientsTable.classList.add("proportion") gwClientsTable.classList.add("proportion");
var siteTable = document.createElement("table") var siteTable = document.createElement("table");
siteTable.classList.add("proportion") siteTable.classList.add("proportion");
function showStatGlobal(o) { function showStatGlobal(o) {
return showStat(o) return showStat(o);
} }
function count(nodes, key, f) { function count(nodes, key, f) {
var dict = {} var dict = {};
nodes.forEach(function (d) { nodes.forEach(function (d) {
var v = dictGet(d, key.slice(0)) var v = dictGet(d, key.slice(0));
if (f !== undefined) if (f !== undefined) {
v = f(v) v = f(v);
}
if (v === null) if (v === null) {
return return;
}
dict[v] = 1 + (v in dict ? dict[v] : 0) dict[v] = 1 + (v in dict ? dict[v] : 0);
}) });
return Object.keys(dict).map(function (d) { return [d, dict[d], key, f] }) return Object.keys(dict).map(function (d) {
return [d, dict[d], key, f];
});
} }
function countClients(nodes, key, f) { function countClients(nodes, key, f) {
var dict = {} var dict = {};
nodes.forEach(function (d) { nodes.forEach(function (d) {
var v = dictGet(d, key.slice(0)) var v = dictGet(d, key.slice(0));
if (f !== undefined) if (f !== undefined) {
v = f(v) v = f(v);
}
if (v === null) if (v === null) {
return return;
}
dict[v] = d.statistics.clients + (v in dict ? dict[v] : 0) dict[v] = d.statistics.clients + (v in dict ? dict[v] : 0);
}) });
return Object.keys(dict).map(function (d) { return [d, dict[d], key, f] }) return Object.keys(dict).map(function (d) {
return [d, dict[d], key, f];
});
} }
function addFilter(filter) { function addFilter(filter) {
return function () { return function () {
filterManager.addFilter(filter) filterManager.addFilter(filter);
return false return false;
} };
} }
function fillTable(name, table, data) { function fillTable(name, table, data) {
if (!table.last) if (!table.last) {
table.last = V.h("table") 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];
}) }
});
var items = data.map(function (d) { var items = data.map(function (d) {
var v = d[1] / max var v = d[1] / max;
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");
var filter = new Filter(name, d[2], d[0], d[3]) var filter = new Filter(name, d[2], d[0], d[3]);
var a = V.h("a", { href: "#", onclick: addFilter(filter) }, d[0]) var a = V.h("a", {href: "#", onclick: addFilter(filter)}, d[0]);
var th = V.h("th", a) var th = V.h("th", a);
var td = V.h("td", V.h("span", {style: { var td = V.h("td", V.h("span", {
style: {
width: Math.round(v * 100) + "%", width: Math.round(v * 100) + "%",
backgroundColor: scale(v).hex(), backgroundColor: scale(v).hex(),
color: c1 > c2 ? "white" : "black" color: c1 > c2 ? "white" : "black"
}}, numeral(d[1]).format("0,0"))) }
}, numeral(d[1]).format("0,0")));
return V.h("tr", [th, td]) return V.h("tr", [th, td]);
}) });
var tableNew = V.h("table", items) var tableNew = V.h("table", items);
table = V.patch(table, V.diff(table.last, tableNew)) table = V.patch(table, V.diff(table.last, tableNew));
table.last = tableNew table.last = tableNew;
} }
self.setData = function (data) { self.setData = function (data) {
var onlineNodes = data.nodes.all.filter(online) var onlineNodes = data.nodes.all.filter(online);
var nodes = onlineNodes.concat(data.nodes.lost) var nodes = onlineNodes.concat(data.nodes.lost);
var nodeDict = {} var nodeDict = {};
data.nodes.all.forEach(function (d) { data.nodes.all.forEach(function (d) {
nodeDict[d.nodeinfo.node_id] = d nodeDict[d.nodeinfo.node_id] = d;
}) });
var statusDict = count(nodes, ["flags", "online"], function (d) { var statusDict = count(nodes, ["flags", "online"], function (d) {
return d ? "online" : "offline" return d ? "online" : "offline";
}) });
var fwDict = count(nodes, ["nodeinfo", "software", "firmware", "release"]) var fwDict = count(nodes, ["nodeinfo", "software", "firmware", "release"]);
var hwDict = count(nodes, ["nodeinfo", "hardware", "model"], function (d) { var hwDict = count(nodes, ["nodeinfo", "hardware", "model"], function (d) {
if (d) { if (d) {
d = d.replace(/\(r\)|\(tm\)/gi, "").replace(/AMD |Intel |TP-Link | CPU| Processor/g, "") d = d.replace(/\(r\)|\(tm\)/gi, "").replace(/AMD |Intel |TP-Link | CPU| Processor/g, "");
if (d.indexOf("@") > 0) d = d.substring(0, d.indexOf("@")) if (d.indexOf("@") > 0) {
d = d.substring(0, d.indexOf("@"));
} }
return d }
}) return d;
});
var geoDict = count(nodes, ["nodeinfo", "location"], function (d) { var geoDict = count(nodes, ["nodeinfo", "location"], function (d) {
return d && d.longitude && d.latitude ? "ja" : "nein" return d && d.longitude && d.latitude ? "ja" : "nein";
}) });
var autoDict = count(nodes, ["nodeinfo", "software", "autoupdater"], function (d) { var autoDict = count(nodes, ["nodeinfo", "software", "autoupdater"], function (d) {
if (d === null) if (d === null) {
return null return null;
else if (d.enabled) } else if (d.enabled) {
return d.branch return d.branch;
else } else {
return "(deaktiviert)" return "(deaktiviert)";
}) }
});
var uplinkDict = count(nodes, ["flags", "uplink"], function (d) { var uplinkDict = count(nodes, ["flags", "uplink"], function (d) {
return d ? "ja" : "nein" return d ? "ja" : "nein";
}) });
var gwNodesDict = count(onlineNodes, ["statistics", "gateway"], function (d) { var gwNodesDict = count(onlineNodes, ["statistics", "gateway"], function (d) {
if (d === null) if (d === null) {
return null return null;
}
if (d.node) if (d.node) {
return d.node.nodeinfo.hostname return d.node.nodeinfo.hostname;
}
if (d.id) if (d.id) {
return d.id return d.id;
}
return d return d;
}) });
var gwClientsDict = countClients(onlineNodes, ["statistics", "gateway"], function (d) { var gwClientsDict = countClients(onlineNodes, ["statistics", "gateway"], function (d) {
if (d === null) if (d === null) {
return null return null;
}
if (d.node) if (d.node) {
return d.node.nodeinfo.hostname return d.node.nodeinfo.hostname;
}
if (d.id) if (d.id) {
return d.id return d.id;
}
return d return d;
}) });
var siteDict = count(nodes, ["nodeinfo", "system", "site_code"], function (d) { var siteDict = count(nodes, ["nodeinfo", "system", "site_code"], function (d) {
var rt = d var rt = d;
if (config.siteNames) if (config.siteNames) {
config.siteNames.forEach(function (t) { config.siteNames.forEach(function (t) {
if(d === t.site) if (d === t.site) {
rt = t.name rt = t.name;
})
return rt
})
fillTable("Status", statusTable, statusDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Firmware", fwTable, fwDict.sort(function (a, b) { return vercomp(b[0], a[0]) }))
fillTable("Hardware", hwTable, hwDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Koordinaten", geoTable, geoDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Uplink", uplinkTable, uplinkDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Autom. Updates", autoTable, autoDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Gateway", gwNodesTable, gwNodesDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Gateway", gwClientsTable, gwClientsDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Site", siteTable, siteDict.sort(function (a, b) { return b[1] - a[1] }))
} }
});
}
return rt;
});
fillTable("Status", statusTable, statusDict.sort(function (a, b) {
return b[1] - a[1];
}));
fillTable("Firmware", fwTable, fwDict.sort(function (a, b) {
return vercomp(b[0], a[0]);
}));
fillTable("Hardware", hwTable, hwDict.sort(function (a, b) {
return b[1] - a[1];
}));
fillTable("Koordinaten", geoTable, geoDict.sort(function (a, b) {
return b[1] - a[1];
}));
fillTable("Uplink", uplinkTable, uplinkDict.sort(function (a, b) {
return b[1] - a[1];
}));
fillTable("Autom. Updates", autoTable, autoDict.sort(function (a, b) {
return b[1] - a[1];
}));
fillTable("Gateway", gwNodesTable, gwNodesDict.sort(function (a, b) {
return b[1] - a[1];
}));
fillTable("Gateway", gwClientsTable, gwClientsDict.sort(function (a, b) {
return b[1] - a[1];
}));
fillTable("Site", siteTable, siteDict.sort(function (a, b) {
return b[1] - a[1];
}));
};
self.render = function (el) { self.render = function (el) {
var h2 var h2;
self.renderSingle(el, "Status", statusTable) self.renderSingle(el, "Status", statusTable);
self.renderSingle(el, "Nodes an Gateway", gwNodesTable) self.renderSingle(el, "Nodes an Gateway", gwNodesTable);
self.renderSingle(el, "Clients an Gateway", gwClientsTable) self.renderSingle(el, "Clients an Gateway", gwClientsTable);
self.renderSingle(el, "Firmwareversionen", fwTable) self.renderSingle(el, "Firmwareversionen", fwTable);
self.renderSingle(el, "Uplink", uplinkTable) self.renderSingle(el, "Uplink", uplinkTable);
self.renderSingle(el, "Hardwaremodelle", hwTable) self.renderSingle(el, "Hardwaremodelle", hwTable);
self.renderSingle(el, "Auf der Karte sichtbar", geoTable) self.renderSingle(el, "Auf der Karte sichtbar", geoTable);
self.renderSingle(el, "Autoupdater", autoTable) self.renderSingle(el, "Autoupdater", autoTable);
self.renderSingle(el, "Site", siteTable) self.renderSingle(el, "Site", siteTable);
if (config.globalInfos) if (config.globalInfos) {
config.globalInfos.forEach(function (globalInfo) { config.globalInfos.forEach(function (globalInfo) {
h2 = document.createElement("h2") h2 = document.createElement("h2");
h2.textContent = globalInfo.name h2.textContent = globalInfo.name;
el.appendChild(h2) el.appendChild(h2);
el.appendChild(showStatGlobal(globalInfo)) el.appendChild(showStatGlobal(globalInfo));
}) });
} }
};
self.renderSingle = function (el, heading, table) { self.renderSingle = function (el, heading, table) {
var h2 var h2;
h2 = document.createElement("h2") h2 = document.createElement("h2");
h2.textContent = heading h2.textContent = heading;
h2.onclick = function () { h2.onclick = function () {
table.classList.toggle("hidden") table.classList.toggle("hidden");
} };
el.appendChild(h2) el.appendChild(h2);
el.appendChild(table) el.appendChild(table);
} };
return self return self;
} };
}) });

View file

@ -1,214 +1,230 @@
define(function () { define(function () {
return function () { return function () {
var self = this var self = this;
var objects = { nodes: {}, links: {} } var objects = {nodes: {}, links: {}};
var targets = [] var targets = [];
var views = {} var views = {};
var currentView var currentView;
var currentObject var currentObject;
var running = false var running = false;
function saveState() { function saveState() {
var e = [] var e = [];
if (currentView) if (currentView) {
e.push("v:" + currentView) e.push("v:" + currentView);
if (currentObject) {
if ("node" in currentObject)
e.push("n:" + encodeURIComponent(currentObject.node.nodeinfo.node_id))
if ("link" in currentObject)
e.push("l:" + encodeURIComponent(currentObject.link.id))
} }
var s = "#!" + e.join(";") if (currentObject) {
if ("node" in currentObject) {
e.push("n:" + encodeURIComponent(currentObject.node.nodeinfo.node_id));
}
window.history.pushState(s, undefined, s) if ("link" in currentObject) {
e.push("l:" + encodeURIComponent(currentObject.link.id));
}
}
var s = "#!" + e.join(";");
window.history.pushState(s, undefined, s);
} }
function resetView(push) { function resetView(push) {
push = trueDefault(push) push = trueDefault(push);
targets.forEach(function (t) { targets.forEach(function (t) {
t.resetView() t.resetView();
}) });
if (push) { if (push) {
currentObject = undefined currentObject = undefined;
saveState() saveState();
} }
} }
function gotoNode(d) { function gotoNode(d) {
if (!d) if (!d) {
return false return false;
}
targets.forEach(function (t) { targets.forEach(function (t) {
t.gotoNode(d) t.gotoNode(d);
}) });
return true return true;
} }
function gotoLink(d) { function gotoLink(d) {
if (!d) if (!d) {
return false return false;
}
targets.forEach(function (t) { targets.forEach(function (t) {
t.gotoLink(d) t.gotoLink(d);
}) });
return true return true;
} }
function gotoLocation(d) { function gotoLocation(d) {
if (!d) if (!d) {
return false return false;
}
targets.forEach(function (t) { targets.forEach(function (t) {
if(!t.gotoLocation)console.warn("has no gotoLocation", t) if (!t.gotoLocation) {
t.gotoLocation(d) console.warn("has no gotoLocation", t);
}) }
t.gotoLocation(d);
});
return true return true;
} }
function loadState(s) { function loadState(s) {
if (!s) if (!s) {
return false return false;
s = decodeURIComponent(s)
if (!s.startsWith("#!"))
return false
var targetSet = false
s.slice(2).split(";").forEach(function (d) {
var args = d.split(":")
if (args[0] === "v" && args[1] in views) {
currentView = args[1]
views[args[1]]()
} }
var id s = decodeURIComponent(s);
if (!s.startsWith("#!")) {
return false;
}
var targetSet = false;
s.slice(2).split(";").forEach(function (d) {
var args = d.split(":");
if (args[0] === "v" && args[1] in views) {
currentView = args[1];
views[args[1]]();
}
var id;
if (args[0] === "n") { if (args[0] === "n") {
id = args[1] id = args[1];
if (id in objects.nodes) { if (id in objects.nodes) {
currentObject = { node: objects.nodes[id] } currentObject = {node: objects.nodes[id]};
gotoNode(objects.nodes[id]) gotoNode(objects.nodes[id]);
targetSet = true targetSet = true;
} }
} }
if (args[0] === "l") { if (args[0] === "l") {
id = args[1] id = args[1];
if (id in objects.links) { if (id in objects.links) {
currentObject = { link: objects.links[id] } currentObject = {link: objects.links[id]};
gotoLink(objects.links[id]) gotoLink(objects.links[id]);
targetSet = true targetSet = true;
} }
} }
}) });
return targetSet return targetSet;
} }
self.start = function () { self.start = function () {
running = true running = true;
if (!loadState(window.location.hash)) if (!loadState(window.location.hash)) {
resetView(false) resetView(false);
}
window.onpopstate = function (d) { window.onpopstate = function (d) {
if (!loadState(d.state)) if (!loadState(d.state)) {
resetView(false) resetView(false);
}
} }
};
};
self.view = function (d) { self.view = function (d) {
if (d in views) { if (d in views) {
views[d]() views[d]();
if (!currentView || running) if (!currentView || running) {
currentView = d currentView = d;
}
if (!running) if (!running) {
return return;
}
saveState() saveState();
if (!currentObject) { if (!currentObject) {
resetView(false) resetView(false);
return return;
} }
if ("node" in currentObject) if ("node" in currentObject) {
gotoNode(currentObject.node) gotoNode(currentObject.node);
}
if ("link" in currentObject) if ("link" in currentObject) {
gotoLink(currentObject.link) gotoLink(currentObject.link);
} }
} }
};
self.node = function (d) { self.node = function (d) {
return function () { return function () {
if (gotoNode(d)) { if (gotoNode(d)) {
currentObject = { node: d } currentObject = {node: d};
saveState() saveState();
} }
return false return false;
} };
} };
self.link = function (d) { self.link = function (d) {
return function () { return function () {
if (gotoLink(d)) { if (gotoLink(d)) {
currentObject = { link: d } currentObject = {link: d};
saveState() saveState();
} }
return false return false;
} };
} };
self.gotoLocation = gotoLocation self.gotoLocation = gotoLocation;
self.reset = function () { self.reset = function () {
resetView() resetView();
} };
self.addTarget = function (d) { self.addTarget = function (d) {
targets.push(d) targets.push(d);
} };
self.removeTarget = function (d) { self.removeTarget = function (d) {
targets = targets.filter(function (e) { targets = targets.filter(function (e) {
return d !== e return d !== e;
}) });
} };
self.addView = function (k, d) { self.addView = function (k, d) {
views[k] = d views[k] = d;
} };
self.setData = function (data) { self.setData = function (data) {
objects.nodes = {} objects.nodes = {};
objects.links = {} objects.links = {};
data.nodes.all.forEach(function (d) { data.nodes.all.forEach(function (d) {
objects.nodes[d.nodeinfo.node_id] = d objects.nodes[d.nodeinfo.node_id] = d;
}) });
data.graph.links.forEach(function (d) { data.graph.links.forEach(function (d) {
objects.links[d.id] = d objects.links[d.id] = d;
}) });
} };
return self return self;
} };
}) });

View file

@ -1,49 +1,50 @@
define([], function () { define([], function () {
return function (el) { return function (el) {
var self = this var self = this;
var sidebar = document.createElement("div") var sidebar = document.createElement("div");
sidebar.classList.add("sidebar") sidebar.classList.add("sidebar");
el.appendChild(sidebar) el.appendChild(sidebar);
var button = document.createElement("button") var button = document.createElement("button");
sidebar.appendChild(button) sidebar.appendChild(button);
button.classList.add("sidebarhandle") button.classList.add("sidebarhandle");
button.onclick = function () { button.onclick = function () {
sidebar.classList.toggle("hidden") sidebar.classList.toggle("hidden");
} };
var container = document.createElement("div") var container = document.createElement("div");
container.classList.add("container") container.classList.add("container");
sidebar.appendChild(container) sidebar.appendChild(container);
self.getWidth = function () { self.getWidth = function () {
if (sidebar.classList.contains("hidden")) if (sidebar.classList.contains("hidden")) {
return 0 return 0;
var small = window.matchMedia("(max-width: 630pt)")
return small.matches ? 0 : sidebar.offsetWidth
} }
var small = window.matchMedia("(max-width: 630pt)");
return small.matches ? 0 : sidebar.offsetWidth;
};
self.add = function (d) { self.add = function (d) {
d.render(container) d.render(container);
} };
self.ensureVisible = function () { self.ensureVisible = function () {
sidebar.classList.remove("hidden") sidebar.classList.remove("hidden");
} };
self.hide = function () { self.hide = function () {
container.classList.add("hidden") container.classList.add("hidden");
} };
self.reveal = function () { self.reveal = function () {
container.classList.remove("hidden") container.classList.remove("hidden");
} };
self.container = sidebar self.container = sidebar;
return self return self;
} };
}) });

View file

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

View file

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

View file

@ -1,57 +1,61 @@
define([], function () { define([], function () {
return function () { return function () {
var self = this var self = this;
var tabs = document.createElement("ul") var tabs = document.createElement("ul");
tabs.classList.add("tabs") tabs.classList.add("tabs");
var container = document.createElement("div") var container = document.createElement("div");
function gotoTab(li) { function gotoTab(li) {
for (var i = 0; i < tabs.children.length; i++) for (var i = 0; i < tabs.children.length; i++) {
tabs.children[i].classList.remove("visible") tabs.children[i].classList.remove("visible");
}
while (container.firstChild) while (container.firstChild) {
container.removeChild(container.firstChild) container.removeChild(container.firstChild);
}
li.classList.add("visible") li.classList.add("visible");
var tab = document.createElement("div") var tab = document.createElement("div");
tab.classList.add("tab") tab.classList.add("tab");
container.appendChild(tab) container.appendChild(tab);
li.child.render(tab) li.child.render(tab);
} }
function switchTab() { function switchTab() {
gotoTab(this) gotoTab(this);
return false return false;
} }
self.add = function (title, d) { self.add = function (title, d) {
var li = document.createElement("li") var li = document.createElement("li");
li.textContent = title li.textContent = title;
li.onclick = switchTab li.onclick = switchTab;
li.child = d li.child = d;
tabs.appendChild(li) tabs.appendChild(li);
var anyVisible = false var anyVisible = false;
for (var i = 0; i < tabs.children.length; i++) for (var i = 0; i < tabs.children.length; i++) {
if (tabs.children[i].classList.contains("visible")) { if (tabs.children[i].classList.contains("visible")) {
anyVisible = true anyVisible = true;
break break;
}
} }
if (!anyVisible) if (!anyVisible) {
gotoTab(li) gotoTab(li);
} }
};
self.render = function (el) { self.render = function (el) {
el.appendChild(tabs) el.appendChild(tabs);
el.appendChild(container) el.appendChild(container);
} };
return self return self;
} };
}) });

View file

@ -1,35 +1,38 @@
define(function () { define(function () {
return function (config) { return function (config) {
function setTitle(d) { function setTitle(d) {
var title = [config.siteName] var title = [config.siteName];
if (d !== undefined) if (d !== undefined) {
title.push(d) title.push(d);
}
document.title = title.join(": ") document.title = title.join(": ");
} }
this.resetView = function () { this.resetView = function () {
setTitle() setTitle();
} };
this.gotoNode = function (d) { this.gotoNode = function (d) {
if (d) if (d) {
setTitle(d.nodeinfo.hostname) setTitle(d.nodeinfo.hostname);
} }
};
this.gotoLink = function (d) { this.gotoLink = function (d) {
if (d) if (d) {
setTitle((d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + " " + d.target.node.nodeinfo.hostname) setTitle((d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + " " + d.target.node.nodeinfo.hostname);
} }
};
this.gotoLocation = function () { this.gotoLocation = function () {
//ignore //ignore
} };
this.destroy = function () { this.destroy = function () {
} };
return this return this;
} };
}) });

View file

@ -1,60 +1,68 @@
define([], function () { define([], function () {
function order(c) { function order(c) {
if (/^\d$/.test(c)) if (/^\d$/.test(c)) {
return 0 return 0;
else if (/^[a-z]$/i.test(c)) } else if (/^[a-z]$/i.test(c)) {
return c.charCodeAt(0) return c.charCodeAt(0);
else if (c === "~") } else if (c === "~") {
return -1 return -1;
else if (c) } else if (c) {
return c.charCodeAt(0) + 256 return c.charCodeAt(0) + 256;
else } else {
return 0 return 0;
}
} }
// Based on dpkg code // Based on dpkg code
function vercomp(a, b) { function vercomp(a, b) {
var apos = 0, bpos = 0 var apos = 0, bpos = 0;
while (apos < a.length || bpos < b.length) { while (apos < a.length || bpos < b.length) {
var firstDiff = 0 var firstDiff = 0;
while ((apos < a.length && !/^\d$/.test(a[apos])) || (bpos < b.length && !/^\d$/.test(b[bpos]))) { while ((apos < a.length && !/^\d$/.test(a[apos])) || (bpos < b.length && !/^\d$/.test(b[bpos]))) {
var ac = order(a[apos]) var ac = order(a[apos]);
var bc = order(b[bpos]) var bc = order(b[bpos]);
if (ac !== bc) if (ac !== bc) {
return ac - bc return ac - bc;
apos++
bpos++
} }
while (a[apos] === "0") apos++;
apos++ bpos++;
}
while (b[bpos] === "0") while (a[apos] === "0") {
bpos++ apos++;
}
while (b[bpos] === "0") {
bpos++;
}
while (/^\d$/.test(a[apos]) && /^\d$/.test(b[bpos])) { while (/^\d$/.test(a[apos]) && /^\d$/.test(b[bpos])) {
if (firstDiff === 0) if (firstDiff === 0) {
firstDiff = a.charCodeAt(apos) - b.charCodeAt(bpos) firstDiff = a.charCodeAt(apos) - b.charCodeAt(bpos);
apos++
bpos++
} }
if (/^\d$/.test(a[apos])) apos++;
return 1 bpos++;
if (/^\d$/.test(b[bpos]))
return -1
if (firstDiff !== 0)
return firstDiff
} }
return 0 if (/^\d$/.test(a[apos])) {
return 1;
} }
return vercomp if (/^\d$/.test(b[bpos])) {
}) return -1;
}
if (firstDiff !== 0) {
return firstDiff;
}
}
return 0;
}
return vercomp;
});

View file

@ -5,7 +5,7 @@ module.exports = function(grunt) {
html: { html: {
options: { options: {
process: function (content) { process: function (content) {
return content.replace("#revision#", grunt.option("gitRevision")) return content.replace("#revision#", grunt.option("gitRevision"));
} }
}, },
src: ["*.html"], src: ["*.html"],
@ -116,11 +116,11 @@ module.exports = function(grunt) {
} }
} }
} }
}) });
grunt.loadNpmTasks("grunt-bower-install-simple") grunt.loadNpmTasks("grunt-bower-install-simple");
grunt.loadNpmTasks("grunt-contrib-copy") grunt.loadNpmTasks("grunt-contrib-copy");
grunt.loadNpmTasks("grunt-contrib-requirejs") grunt.loadNpmTasks("grunt-contrib-requirejs");
grunt.loadNpmTasks("grunt-sass") grunt.loadNpmTasks("grunt-sass");
grunt.loadNpmTasks("grunt-postcss") grunt.loadNpmTasks("grunt-postcss");
} };

View file

@ -3,7 +3,7 @@ module.exports = function (grunt) {
clean: { clean: {
build: ["build/**/*", "node_modules/grunt-newer/.cache"] build: ["build/**/*", "node_modules/grunt-newer/.cache"]
} }
}) });
grunt.loadNpmTasks("grunt-contrib-clean") grunt.loadNpmTasks("grunt-contrib-clean");
} };

View file

@ -24,8 +24,8 @@ module.exports = function (grunt) {
tasks: [] tasks: []
} }
} }
}) });
grunt.loadNpmTasks("grunt-contrib-connect") grunt.loadNpmTasks("grunt-contrib-connect");
grunt.loadNpmTasks("grunt-contrib-watch") grunt.loadNpmTasks("grunt-contrib-watch");
} };

View file

@ -31,8 +31,8 @@ module.exports = function (grunt) {
src: ["Gruntfile.js", "tasks/*.js"] src: ["Gruntfile.js", "tasks/*.js"]
} }
} }
}) });
grunt.loadNpmTasks("grunt-check-dependencies") grunt.loadNpmTasks("grunt-check-dependencies");
grunt.loadNpmTasks("grunt-eslint") grunt.loadNpmTasks("grunt-eslint");
} };