Compare commits

...

32 commits

Author SHA1 Message Date
Milan Paessler bdde93a2c8 [TASK] travis: bump node version 2017-03-21 22:56:16 +01:00
Xaver Maierhofer 27704d4e3e [TASK] Update eslint to 2.11.0 2017-03-21 22:53:36 +01:00
Milan Pässler 4171594782 [BUGFIX] bower package name should be lowercase 2017-03-21 22:53:36 +01:00
Milan Pässler 4c26a8c2c9 [TASK] Add required information into package.json 2017-03-21 22:53:36 +01:00
Xaver Maierhofer 9987e443f7 [TASK] Use strict 2017-03-21 22:53:36 +01:00
Xaver Maierhofer 5c15ccf340 [TASK] Add scss to grunt watcher 2017-03-21 22:53:36 +01:00
Xaver Maierhofer 6869a19ea0 [TASK] Style layer selector 2017-03-21 22:53:36 +01:00
Milan Pässler 173674c2a1 [!!!][TASK] Use eslint default 2017-03-21 22:53:35 +01:00
Xaver Maierhofer c451775021 [TASK] Remove tablesort number
No complex different styled numbers need to be sorted
2017-03-21 22:53:35 +01:00
Xaver Maierhofer 09714f3b58 [TASK] Remove jsHashes 2017-03-21 22:53:35 +01:00
Xaver Maierhofer d4670d8742 [TASK] Simple sort for firmware versions 2017-03-21 22:53:35 +01:00
Xaver Maierhofer fe3ba88887 [TASK] Replace numeraljs with toFixed vanilla js 2017-03-21 22:53:35 +01:00
Xaver Maierhofer 2738967343 [TASK] Update npm bower install module 2017-03-21 22:53:35 +01:00
Xaver Maierhofer d5aa447ab8 [TASK] Add helper.js to grunt watch 2017-03-21 22:53:35 +01:00
Xaver Maierhofer 41c6a03e6b [TASK] Adjust marker style - focus on online nodes 2017-03-21 22:53:35 +01:00
Milan Pässler f1e9aacdf7 fix linting 2017-03-21 22:53:35 +01:00
Nils Schneider 8520abf676 [TASK] remove build.js, it is not needed anymore 2017-03-21 22:53:35 +01:00
Xaver Maierhofer 6908e2953f [TASK] Format load average 2017-03-21 22:53:35 +01:00
Xaver Maierhofer 136157eb37 [TASK] Only add german language from numeral.js 2017-03-21 22:53:35 +01:00
Xaver Maierhofer d367bf58bf [TASK] Unicode instead of utf8 icons 2017-03-21 22:53:35 +01:00
Xaver Maierhofer b510dda006 [BUGFIX] Enable build for default 2017-03-21 22:53:35 +01:00
Xaver Maierhofer 7f3a86596a [TASK] Replace all specialchars in NODE_NAME in stat urls 2017-03-21 22:53:34 +01:00
Xaver Maierhofer 231bfcedd7 [TASK] Seperate dev and default for requirejs 2017-03-21 22:53:34 +01:00
Xaver Maierhofer 88f43bc57c [TASK] Add index option to grunt-conncet server 2017-03-21 22:53:34 +01:00
Xaver Maierhofer 8bb0da9d87 [TASK] CGL - Optimize returns and unnecessary math 2017-03-21 22:53:34 +01:00
Xaver Maierhofer 44571c51f0 [TASK] Inline style and icon css and es6-shim 2017-03-21 22:53:34 +01:00
Xaver Maierhofer 30b6f84b6d [BUGFIX] Correct css path to icon font 2017-03-21 22:53:34 +01:00
Xaver Maierhofer f7f5744a78 [TASK] Update bower components 2017-03-21 22:53:34 +01:00
Xaver Maierhofer 0a22ed5e6f [TASK] Only include additional german locale
English is default
2017-03-21 22:53:34 +01:00
Xaver Maierhofer 9afb214360 [TASK] Use own iconfont set
Reduce overhead in initial load
2017-03-21 22:53:34 +01:00
Milan Pässler eb4c7a04b0 change code style to ffrgb/meshviewer fork 2017-03-21 22:53:34 +01:00
H4ndl3 8b5bcbfede add loading animation
Create _loader.scss

Update main.scss

Update _base.scss

Update gui.js

Update index.html

removed trailing spaces

removed trailing white spaces for real
2017-03-21 22:53:23 +01:00
55 changed files with 4033 additions and 3283 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

8
.eslintrc Normal file
View file

@ -0,0 +1,8 @@
---
"extends":
- "defaults/configurations/eslint"
rules:
"semi": ["error", "always"]
"no-undef": 0
"no-console": ["error", { allow: ["warn", "error"] }]

1
.gitignore vendored
View file

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

View file

@ -1,4 +1,5 @@
language: node_js language: node_js
node_js: node
before_install: before_install:
- gem install sass - gem install sass
- npm install -g grunt-cli - npm install -g grunt-cli

View file

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

View file

@ -46,6 +46,10 @@ Just run the following command from the hopglass directory:
This will generate `build/` containing all required files. This will generate `build/` containing all required files.
## Development
Use `grunt serve` for development.
# Configure # Configure
Copy `config.json.example` to `build/config.json` and change it to match your community. Copy `config.json.example` to `build/config.json` and change it to match your community.

30
app.js
View file

@ -1,3 +1,5 @@
"use strict";
require.config({ require.config({
baseUrl: "lib", baseUrl: "lib",
paths: { paths: {
@ -5,32 +7,24 @@ require.config({
"leaflet.label": "../bower_components/Leaflet.label/dist/leaflet.label", "leaflet.label": "../bower_components/Leaflet.label/dist/leaflet.label",
"leaflet.providers": "../bower_components/leaflet-providers/leaflet-providers", "leaflet.providers": "../bower_components/leaflet-providers/leaflet-providers",
"chroma-js": "../bower_components/chroma-js/chroma.min", "chroma-js": "../bower_components/chroma-js/chroma.min",
"moment": "../bower_components/moment/min/moment-with-locales.min", "moment": "../bower_components/moment/min/moment.min",
"tablesort": "../bower_components/tablesort/tablesort.min", "moment.de": "../bower_components/moment/locale/de",
"tablesort.numeric": "../bower_components/tablesort/src/sorts/tablesort.numeric", "tablesort": "../bower_components/tablesort/src/tablesort",
"d3": "../bower_components/d3/d3.min", "d3": "../bower_components/d3/d3.min",
"numeral": "../bower_components/numeraljs/min/numeral.min",
"numeral-intl": "../bower_components/numeraljs/min/languages.min",
"virtual-dom": "../bower_components/virtual-dom/dist/virtual-dom", "virtual-dom": "../bower_components/virtual-dom/dist/virtual-dom",
"rbush": "../bower_components/rbush/rbush", "rbush": "../bower_components/rbush/rbush",
"helper": "../helper", "helper": "utils/helper"
"jshashes": "../bower_components/jshashes/hashes"
}, },
shim: { shim: {
"leaflet.label": ["leaflet"], "leaflet.label": ["leaflet"],
"leaflet.providers": ["leaflet"], "leaflet.providers": ["leaflet"],
"moment.de": ["moment"],
"tablesort": { "tablesort": {
exports: "Tablesort" exports: "Tablesort"
},
"numeral-intl": {
deps: ["numeral"],
exports: "numeral"
},
"tablesort.numeric": ["tablesort"],
"helper": ["numeral-intl"]
} }
}) }
});
require(["main", "helper"], function (main) { require(["main", "helper"], function (main, helper) {
getJSON("config.json").then(main) helper.getJSON("config.json").then(main);
}) });

BIN
assets/icons/fonts/icon.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,96 @@
@font-face {
font-family: "Ionicons";
src: url("fonts/icon.woff2") format("woff2"),
url("fonts/icon.woff") format("woff"),
url("fonts/icon.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
[class^="ion-"]:before,
[class*=" ion-"]:before, .ion-inside {
display: inline-block;
font-family: "ionicons" !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
text-rendering: auto;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.ion-android-add:before {
content: "\f2c7";
}
.ion-chevron-left:before {
content: "\f124";
}
.ion-chevron-right:before {
content: "\f125";
}
.ion-pin:before {
content: "\f3a3";
}
.ion-wifi:before {
content: "\f25c";
}
.ion-eye:before {
content: "\f133";
}
.ion-ios-arrow-thin-left:before {
content: "\f3d5";
}
.ion-ios-arrow-thin-right:before {
content: "\f3d6";
}
.ion-arrow-up-b:before {
content: "\f10d";
}
.ion-arrow-down-b:before {
content: "\f104";
}
.ion-android-locate:before {
content: "\f2e9";
}
.ion-android-close:before {
content: "\f2d7";
}
.ion-android-lock:before {
content: "\f392";
}
.ion-ios-copy:before {
content: "\f41c";
}
.ion-location:before {
content: "\f456";
}
.ion-android-remove:before {
content: "\f2f4";
}
.ion-ios-person:before {
content: "\f47e";
}
.ion-layer:before {
content: "\f229";
}

View file

@ -1,5 +1,5 @@
{ {
"name": "HopGlass", "name": "hopglass",
"ignore": [ "ignore": [
"node_modules", "node_modules",
"bower_components", "bower_components",
@ -9,23 +9,19 @@
], ],
"dependencies": { "dependencies": {
"Leaflet.label": "~0.2.1", "Leaflet.label": "~0.2.1",
"chroma-js": "~0.6.1", "chroma-js": "~1.1.1",
"leaflet": "~0.7.3", "leaflet": "~0.7.7",
"ionicons": "~2.0.1", "moment": "~2.13.0",
"moment": "~2.9.0", "requirejs": "~2.2.0",
"requirejs": "~2.1.16", "tablesort": "https://github.com/tristen/tablesort.git#v4.0.1",
"tablesort": "https://github.com/tristen/tablesort.git#v3.0.2",
"roboto-slab-fontface": "*", "roboto-slab-fontface": "*",
"es6-shim": "~0.27.1", "es6-shim": "~0.35.1",
"almond": "~0.3.1", "almond": "~0.3.2",
"r.js": "~2.1.16", "d3": "~3.5.17",
"d3": "~3.5.5", "roboto-fontface": "~0.4.5",
"numeraljs": "~1.5.3", "virtual-dom": "~2.1.1",
"roboto-fontface": "~0.3.0", "leaflet-providers": "~1.1.10",
"virtual-dom": "~2.0.1", "rbush": "https://github.com/mourner/rbush.git#~1.4.3"
"leaflet-providers": "~1.0.27",
"rbush": "https://github.com/mourner/rbush.git#~1.3.5",
"jshashes": "~1.0.5"
}, },
"authors": [ "authors": [
"Milan Pässler <me@petabyteboy.de>", "Milan Pässler <me@petabyteboy.de>",

View file

@ -1,9 +0,0 @@
({
baseUrl: "lib",
name: "../bower_components/almond/almond",
mainConfigFile: "app.js",
include: "../app",
wrap: true,
optimize: "uglify",
out: "app-combined.js"
})

247
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 d.distance.toFixed(0) + " m"
} }
function showTq(d) { function showTq(d) {
return numeral(1/d.tq).format("0%") return (1 / d.tq * 100).toFixed(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) p.appendChild(link);
} else } else {
p.appendChild(content) p.appendChild(content);
}
return p return p;
} }

View file

@ -1,18 +1,28 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" href="css/ionicons.min.css"> <link rel="stylesheet" href="hopglass-icons.css?__inline=true">
<link rel="stylesheet" href="roboto-slab-fontface.css"> <link rel="stylesheet" href="roboto-slab-fontface.css">
<link rel="stylesheet" href="roboto-fontface.css"> <link rel="stylesheet" href="roboto-fontface.css">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css?__inline=true">
<script src="vendor/es6-shim/es6-shim.min.js"></script> <script src="vendor/es6-shim/es6-shim.min.js?__inline=true"></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>
</body> <div class="loader">
<p>
Lade<br/>
<span class="spinner"></span><br/>
Karte &amp; Knoten...
</p>
<noscript>
<strong>JavaScript required</strong>
</noscript>
</div>
</body>
</html> </html>

View file

@ -1,17 +1,27 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" href="bower_components/roboto-slab-fontface/roboto-slab-fontface.css"> <link rel="stylesheet" href="bower_components/roboto-slab-fontface/roboto-slab-fontface.css">
<link rel="stylesheet" href="bower_components/roboto-fontface/roboto-fontface.css"> <link rel="stylesheet" href="bower_components/roboto-fontface/roboto-fontface.css">
<link rel="stylesheet" href="bower_components/leaflet/dist/leaflet.css"> <link rel="stylesheet" href="bower_components/leaflet/dist/leaflet.css">
<link rel="stylesheet" href="bower_components/Leaflet.label/dist/leaflet.label.css"> <link rel="stylesheet" href="bower_components/Leaflet.label/dist/leaflet.label.css">
<link rel="stylesheet" href="bower_components/ionicons/css/ionicons.min.css"> <link rel="stylesheet" href="assets/icons/hopglass-icons.css">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="bower_components/es6-shim/es6-shim.min.js"></script> <script src="bower_components/es6-shim/es6-shim.min.js"></script>
<script src="bower_components/requirejs/require.js" data-main="app"></script> <script src="bower_components/requirejs/require.js" data-main="app"></script>
</head> </head>
<body> <body>
</body> <div class="loader">
<p>
Lade<br/>
<span class="spinner"></span><br \>
Karte &amp; Knoten...
</p>
<noscript>
<strong>JavaScript required</strong>
</noscript>
</div>
</body>
</html> </html>

View file

@ -1,38 +1,40 @@
define(function () { define(function () {
return function() { "use strict";
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,23 @@
define([], function () { define([], function () {
"use strict";
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,93 @@
define(["filters/nodefilter"], function (NodeFilter) { define(["filters/nodefilter"], function (NodeFilter) {
"use strict";
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) {
return function (d) {
return a(d) && f.run(d)
} }
}, function () { return true })
filteredData = new NodeFilter(filter)(data) var filter = filters.reduce(function (a, f) {
return function (d) {
return a(d) && f.run(d);
};
}, function () {
return true;
});
targets.forEach( function (t) { filteredData = new NodeFilter(filter)(data);
t.setData(filteredData)
}) targets.forEach(function (t) {
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,45 @@
define([], function () { define(function () {
"use strict";
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,58 @@
define([], function () { define(["helper"], function (helper) {
return function (name, key, value, f) { "use strict";
var negate = false
var refresh
var label = document.createElement("label") return function (name, key, value, f) {
var strong = document.createElement("strong") var negate = false;
label.textContent = name + " " var refresh;
label.appendChild(strong)
var label = document.createElement("label");
var strong = document.createElement("strong");
label.textContent = name + " ";
label.appendChild(strong);
function run(d) { function run(d) {
var o = dictGet(d, key.slice(0)) var o = helper.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,38 @@
define([], function () { define(function () {
"use strict";
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()
n.graph = {}
n.graph.nodes = data.graph.nodes.filter( function (d) {
var r
if (d.node)
r = filter(d.node)
else
r = filter({})
if (r)
filteredIds.add(d.id)
return r
})
n.graph.links = data.graph.links.filter( function (d) {
return filteredIds.has(d.source.id) && filteredIds.has(d.target.id)
})
return n
} }
var filteredIds = new Set();
n.graph = {};
n.graph.nodes = data.graph.nodes.filter(function (d) {
var r;
if (d.node) {
r = filter(d.node);
} else {
r = filter({});
} }
})
if (r) {
filteredIds.add(d.id);
}
return r;
});
n.graph.links = data.graph.links.filter(function (d) {
return filteredIds.has(d.source.id) && filteredIds.has(d.target.id);
});
return n;
};
};
});

File diff suppressed because it is too large Load diff

View file

@ -1,122 +1,129 @@
define([ "chroma-js", "map", "sidebar", "tabs", "container", "meshstats", define(["chroma-js", "map", "sidebar", "tabs", "container", "meshstats",
"legend", "linklist", "nodelist", "simplenodelist", "infobox/main", "legend", "linklist", "nodelist", "simplenodelist", "infobox/main",
"proportions", "forcegraph", "title", "about", "datadistributor", "proportions", "forcegraph", "title", "about", "datadistributor",
"filters/filtergui" ], "filters/filtergui"],
function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist, 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) {
"use strict";
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.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);
} };
} }
contentDiv = document.createElement("div") var loader = document.getElementsByClassName("loader")[0];
contentDiv.classList.add("content") loader.classList.add("hide");
document.body.appendChild(contentDiv)
sidebar = new Sidebar(document.body) contentDiv = document.createElement("div");
contentDiv.classList.add("content");
document.body.appendChild(contentDiv);
contentDiv.appendChild(buttons) sidebar = new Sidebar(document.body);
var buttonToggle = document.createElement("button") contentDiv.appendChild(buttons);
buttonToggle.textContent = "\uF133"
var buttonToggle = document.createElement("button");
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,52 @@
define(function () { define(["helper"], function (helper) {
"use strict";
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 helper.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(" \uF3D6 "));
var a2 = document.createElement("a") h2.className = "ion-inside";
a2.href = "#" var a2 = document.createElement("a");
a2.onclick = router.node(d.target.node) a2.href = "#";
a2.textContent = d.target.node.nodeinfo.hostname a2.onclick = router.node(d.target.node);
h2.appendChild(a2) a2.textContent = d.target.node.nodeinfo.hostname;
el.appendChild(h2) h2.appendChild(a2);
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)) helper.attributeEntry(attributes, "TQ", helper.showTq(d));
attributeEntry(attributes, "Entfernung", showDistance(d)) helper.attributeEntry(attributes, "Entfernung", helper.showDistance(d));
attributeEntry(attributes, "Typ", d.type) helper.attributeEntry(attributes, "Typ", d.type);
var hw1 = unknown ? null : dictGet(d.source.node.nodeinfo, ["hardware", "model"]) var hw1 = unknown ? null : helper.dictGet(d.source.node.nodeinfo, ["hardware", "model"]);
var hw2 = dictGet(d.target.node.nodeinfo, ["hardware", "model"]) var hw2 = helper.dictGet(d.target.node.nodeinfo, ["hardware", "model"]);
attributeEntry(attributes, "Hardware", (hw1 != null ? hw1 : "unbekannt") + " " + (hw2 != null ? hw2 : "unbekannt")) helper.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,105 @@
define(function () { define(["helper"], function (helper) {
"use strict";
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") helper.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.warn(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,53 @@
define(["infobox/link", "infobox/node", "infobox/location"], function (Link, Node, Location) { define(["infobox/link", "infobox/node", "infobox/location"], function (Link, Node, Location) {
"use strict";
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) Node(config, el, router, d);
} };
self.gotoLink = function (d) { self.gotoLink = function (d) {
create() create();
new Link(config, el, router, d) Link(config, el, router, d);
} };
self.gotoLocation = function (d) { self.gotoLocation = function (d) {
create() create();
new Location(config, el, router, d) Location(config, el, router, d);
} };
return self return self;
} };
}) });

View file

@ -1,84 +1,93 @@
define(["moment", "numeral", "tablesort", "tablesort.numeric"], define(["moment", "tablesort", "helper", "moment.de"],
function (moment, numeral, Tablesort) { function (moment, Tablesort, helper) {
"use strict";
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 + "° " + min.toFixed(3) + 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 + "° " + min.toFixed(3) + suffix;
} }
if (!has_location(d)) if (!helper.hasLocation(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 = helper.dictGet(d.nodeinfo, ["software", "firmware", "release"]);
var base = dictGet(d.nodeinfo, ["software", "firmware", "base"]) var base = helper.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 = helper.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 +124,511 @@ 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 = "\uF47E ".repeat(d.statistics.clients);
el.appendChild(span);
var spanmesh = document.createElement("span");
spanmesh.classList.add("clientsMesh");
spanmesh.textContent = "\uF47E ".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 = helper.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.toFixed(2)));
} };
} }
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 (helper.dictGet(d.statistics, ["nexthop"])) {
nh = dictGet(d.statistics, ["nexthop"]) nh = helper.dictGet(d.statistics, ["nexthop"]);
if (dictGet(d.statistics, ["gateway_nexthop"])) }
nh = dictGet(d.statistics, ["gateway_nexthop"]) if (helper.dictGet(d.statistics, ["gateway_nexthop"])) {
var gw = dictGet(d.statistics, ["gateway"]) nh = helper.dictGet(d.statistics, ["gateway_nexthop"]);
}
var gw = helper.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 (!helper.dictGet(nh.node.statistics, ["gateway"]) || !helper.dictGet(nh.node.statistics, ["gateway"]).id) {
nh = dictGet(nh.node.statistics, ["nexthop"]) break;
else }
break if (helper.dictGet(nh.node.statistics, ["gateway"]).id !== gw.id) {
break;
}
if (helper.dictGet(nh.node.statistics, ["gateway_nexthop"])) {
nh = helper.dictGet(nh.node.statistics, ["gateway_nexthop"]);
} else if (helper.dictGet(nh.node.statistics, ["nexthop"])) {
nh = helper.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 = helper.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 = helper.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.replace(/[^a-z0-9\-]/ig, "_") : "unknown";
return showStat(o, subst) return helper.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, helper.dictGet(d, ["nodeinfo", "hardware", "model"])));
} catch (err) { } catch (err) {
console.log(err.message) console.warn(err.message);
} }
}) });
} catch (err) { } catch (err) {
console.log(err.message) console.warn(err.message);
} }
attributeEntry(attributes, top, d.nodeinfo.hostname) helper.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)) helper.attributeEntry(attributes, "Status", showStatus(d));
attributeEntry(attributes, "Gateway", d.flags.gateway ? "ja" : null) helper.attributeEntry(attributes, "Gateway", d.flags.gateway ? "ja" : null);
attributeEntry(attributes, "Koordinaten", showGeoURI(d)) helper.attributeEntry(attributes, "Koordinaten", showGeoURI(d));
if (config.showContact) if (config.showContact) {
attributeEntry(attributes, "Kontakt", dictGet(d.nodeinfo, ["owner", "contact"])) helper.attributeEntry(attributes, "Kontakt", helper.dictGet(d.nodeinfo, ["owner", "contact"]));
}
attributeEntry(attributes, "Hardware", dictGet(d.nodeinfo, ["hardware", "model"])) helper.attributeEntry(attributes, "Hardware", helper.dictGet(d.nodeinfo, ["hardware", "model"]));
attributeEntry(attributes, "Primäre MAC", dictGet(d.nodeinfo, ["network", "mac"])) helper.attributeEntry(attributes, "Primäre MAC", helper.dictGet(d.nodeinfo, ["network", "mac"]));
attributeEntry(attributes, "Node ID", dictGet(d.nodeinfo, ["node_id"])) helper.attributeEntry(attributes, "Node ID", helper.dictGet(d.nodeinfo, ["node_id"]));
attributeEntry(attributes, "Firmware", showFirmware(d)) helper.attributeEntry(attributes, "Firmware", showFirmware(d));
attributeEntry(attributes, "Site", showSite(d, config)) helper.attributeEntry(attributes, "Site", showSite(d, config));
attributeEntry(attributes, "Uptime", showUptime(d)) helper.attributeEntry(attributes, "Uptime", showUptime(d));
attributeEntry(attributes, "Teil des Netzes", showFirstseen(d)) helper.attributeEntry(attributes, "Teil des Netzes", showFirstseen(d));
attributeEntry(attributes, "Kanal 2.4 GHz", showWifiChannel(dictGet(d.nodeinfo, ["wireless", "chan2"]))) helper.attributeEntry(attributes, "Kanal 2.4 GHz", showWifiChannel(helper.dictGet(d.nodeinfo, ["wireless", "chan2"])));
attributeEntry(attributes, "Kanal 5 GHz", showWifiChannel(dictGet(d.nodeinfo, ["wireless", "chan5"]))) helper.attributeEntry(attributes, "Kanal 5 GHz", showWifiChannel(helper.dictGet(d.nodeinfo, ["wireless", "chan5"])));
attributeEntry(attributes, "Airtime 2.4 GHz", showAirtime(2, dictGet(d.statistics, ["wireless", "airtime2"]))) helper.attributeEntry(attributes, "Airtime 2.4 GHz", showAirtime(2, helper.dictGet(d.statistics, ["wireless", "airtime2"])));
attributeEntry(attributes, "Airtime 5 GHz", showAirtime(5, dictGet(d.statistics, ["wireless", "airtime5"]))) helper.attributeEntry(attributes, "Airtime 5 GHz", showAirtime(5, helper.dictGet(d.statistics, ["wireless", "airtime5"])));
attributeEntry(attributes, "Systemlast", showLoad(d)) helper.attributeEntry(attributes, "Systemlast", showLoad(d));
attributeEntry(attributes, "Arbeitsspeicher", showRAM(d)) helper.attributeEntry(attributes, "Arbeitsspeicher", showRAM(d));
attributeEntry(attributes, "IP Adressen", showIPs(d)) helper.attributeEntry(attributes, "IP Adressen", showIPs(d));
attributeEntry(attributes, "Webseite", showPages(d)) helper.attributeEntry(attributes, "Webseite", showPages(d));
attributeEntry(attributes, "Gewähltes Gateway", showGateway(d, router)) helper.attributeEntry(attributes, "Gewähltes Gateway", showGateway(d, router));
attributeEntry(attributes, "Autom. Updates", showAutoupdate(d)) helper.attributeEntry(attributes, "Autom. Updates", showAutoupdate(d));
attributeEntry(attributes, "Clients", showClients(d), showMeshClients(d)) helper.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.className = "ion-inside";
tr.appendChild(td1) td1.appendChild(document.createTextNode(d.incoming ? " \uF3D5 " : " \uF3D6 "));
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 && helper.hasLocation(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 = helper.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 = helper.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) Tablesort(table);
el.appendChild(table) el.appendChild(table);
} }
} };
}) });

View file

@ -1,41 +1,43 @@
define(function () { define(function () {
"use strict";
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,68 @@
define(["sorttable", "virtual-dom"], function (SortTable, V) { define(["sorttable", "virtual-dom", "helper"], function (SortTable, V, helper) {
"use strict";
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()}}, helper.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", helper.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

@ -1,4 +1,6 @@
define(["leaflet"], function (L) { define(["leaflet"], function (L) {
"use strict";
return L.CircleMarker.extend({ return L.CircleMarker.extend({
outerCircle: { outerCircle: {
stroke: false, stroke: false,
@ -29,31 +31,31 @@ define(["leaflet"], function (L) {
fillOpacity: 0.2 fillOpacity: 0.2
}, },
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

@ -1,182 +1,192 @@
define(["moment", "router", "leaflet", "gui", "numeral"], define(["moment", "router", "leaflet", "gui", "helper", "moment.de"],
function (moment, Router, L, GUI, numeral) { function (moment, Router, L, GUI, helper) {
"use strict";
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.error(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.error(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 = helper.limit("firstseen", age, helper.sortByKey("firstseen", nodes).filter(helper.online));
var lostnodes = limit("lastseen", age, sortByKey("lastseen", nodes).filter(offline)) var lostnodes = helper.limit("lastseen", age, helper.sortByKey("lastseen", nodes).filter(helper.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 +197,46 @@ function (moment, Router, L, GUI, numeral) {
links: links, links: links,
nodes: graph.nodes nodes: graph.nodes
} }
} };
} }
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(helper.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.warn(e);
}) });
} };
}) });

View file

@ -1,10 +1,13 @@
define(["map/clientlayer", "map/labelslayer", define(["map/clientlayer", "map/labelslayer",
"d3", "leaflet", "moment", "locationmarker", "rbush", "d3", "leaflet", "moment", "locationmarker", "rbush", "helper",
"leaflet.label", "leaflet.providers"], "leaflet.label", "leaflet.providers", "moment.de"],
function (ClientLayer, LabelsLayer, d3, L, moment, LocationMarker, rbush) { function (ClientLayer, LabelsLayer, d3, L, moment, LocationMarker, rbush, helper) {
var options = { worldCopyJump: true, "use strict";
var options = {
worldCopyJump: true,
zoomControl: false zoomControl: false
} };
var AddLayerButton = L.Control.extend({ var AddLayerButton = L.Control.extend({
options: { options: {
@ -12,24 +15,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 +43,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 +82,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 = "\uF3A3";
// 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) { return 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>" + helper.showDistance(d) + " / " + helper.showTq(d) + "</strong>");
line.on("click", router.link(d));
dict[d.id] = line;
return line;
});
} }
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: 4,
fillOpacity: 0.8,
opacity: 0.8,
weight: 1,
className: "stroke-first"
};
var iconAlert = {
color: "#D43E2A",
fillColor: "#D43E2A",
radius: 5,
fillOpacity: 0.8,
opacity: 0.8,
weight: 2,
className: "stroke-first"
};
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 (helper.localStorageTest()) {
localStorage.setItem("map/customLayers", JSON.stringify(Object.keys(customLayers))) localStorage.setItem("map/customLayers", JSON.stringify(Object.keys(customLayers)));
}
} catch (e) { } catch (e) {
return console.error(e);
} }
} }
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 (helper.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 (helper.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: 4, opacity: 1, dashArray: "5, 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)
groupOnline.clearLayers()
if (groupNew)
groupNew.clearLayers()
if (groupLost)
groupLost.clearLayers()
if (groupLines)
groupLines.clearLayers()
var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router)
groupLines = L.featureGroup(lines).addTo(map)
if (typeof config.fixedCenter === "undefined")
barycenter = calcBarycenter(data.nodes.all.filter(has_location))
else
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 nodesOffline = subtract(data.nodes.all.filter(offline), data.nodes.lost)
var markersOnline = nodesOnline.filter(has_location)
.map(mkMarker(nodeDict, function () { return iconOnline }, router))
var markersOffline = nodesOffline.filter(has_location)
.map(mkMarker(nodeDict, function () { return iconOffline }, router))
var markersNew = data.nodes.new.filter(has_location)
.map(mkMarker(nodeDict, function () { return iconNew }, router))
var markersLost = data.nodes.lost.filter(has_location)
.map(mkMarker(nodeDict, function (d) {
if (d.lastseen.isAfter(moment(data.now).subtract(3, "days")))
return iconAlert
return iconLost
}, router))
groupOffline = L.featureGroup(markersOffline).addTo(map)
groupOnline = L.featureGroup(markersOnline).addTo(map)
groupLost = L.featureGroup(markersLost).addTo(map)
groupNew = L.featureGroup(markersNew).addTo(map)
var rtreeOnlineAll = rbush(9)
rtreeOnlineAll.load(data.nodes.all.filter(online).filter(has_location).map(mapRTree))
clientLayer.setData(rtreeOnlineAll)
labelsLayer.setData({online: nodesOnline.filter(has_location),
offline: nodesOffline.filter(has_location),
new: data.nodes.new.filter(has_location),
lost: data.nodes.lost.filter(has_location)
})
updateView(true)
} }
if (groupOnline) {
groupOnline.clearLayers();
}
if (groupNew) {
groupNew.clearLayers();
}
if (groupLost) {
groupLost.clearLayers();
}
if (groupLines) {
groupLines.clearLayers();
}
var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router);
groupLines = L.featureGroup(lines).addTo(map);
if (typeof config.fixedCenter === "undefined") {
barycenter = calcBarycenter(data.nodes.all.filter(helper.hasLocation));
} else {
barycenter = L.circle(L.latLng(new L.LatLng(config.fixedCenter.lat, config.fixedCenter.lng)), config.fixedCenter.radius * 1000);
}
var nodesOnline = helper.subtract(data.nodes.all.filter(helper.online), data.nodes.new);
var nodesOffline = helper.subtract(data.nodes.all.filter(helper.offline), data.nodes.lost);
var markersOnline = nodesOnline.filter(helper.hasLocation)
.map(mkMarker(nodeDict, function () {
return iconOnline;
}, router));
var markersOffline = nodesOffline.filter(helper.hasLocation)
.map(mkMarker(nodeDict, function () {
return iconOffline;
}, router));
var markersNew = data.nodes.new.filter(helper.hasLocation)
.map(mkMarker(nodeDict, function () {
return iconNew;
}, router));
var markersLost = data.nodes.lost.filter(helper.hasLocation)
.map(mkMarker(nodeDict, function (d) {
if (d.lastseen.isAfter(moment(data.now).subtract(3, "days"))) {
return iconAlert;
}
if (d.lastseen.isAfter(moment(data.now).subtract(14, "days"))) {
return iconLost;
}
}, router));
groupOffline = L.featureGroup(markersOffline).addTo(map);
groupLost = L.featureGroup(markersLost).addTo(map);
groupOnline = L.featureGroup(markersOnline).addTo(map);
groupNew = L.featureGroup(markersNew).addTo(map);
var rtreeOnlineAll = rbush(9);
rtreeOnlineAll.load(data.nodes.all.filter(helper.online).filter(helper.hasLocation).map(mapRTree));
clientLayer.setData(rtreeOnlineAll);
labelsLayer.setData({
online: nodesOnline.filter(helper.hasLocation),
offline: nodesOffline.filter(helper.hasLocation),
new: data.nodes.new.filter(helper.hasLocation),
lost: data.nodes.lost.filter(helper.hasLocation)
});
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,78 @@
define(["leaflet", "jshashes"], define(["leaflet"],
function (L, jsHashes) { function (L) {
var MD5 = new jsHashes.MD5() "use strict";
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) d.startAngle = (parseInt(d.node.nodeinfo.node_id.substr(10, 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

@ -1,5 +1,7 @@
define(["leaflet", "rbush"], define(["leaflet", "rbush"],
function (L, rbush) { function (L, rbush) {
"use strict";
var labelLocations = [["left", "middle", 0 / 8], var labelLocations = [["left", "middle", 0 / 8],
["center", "top", 6 / 8], ["center", "top", 6 / 8],
["right", "middle", 4 / 8], ["right", "middle", 4 / 8],
@ -7,30 +9,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 +42,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 y = p.y + offset[1] + dy[anchor[1]];
return [x, y, x + width, y + height];
} }
var x = p.x + offset[0] + dx[anchor[0]] return L.TileLayer.Canvas.extend({
var y = p.y + offset[1] + dy[anchor[1]]
return [x, y, x + width, y + height]
}
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 +99,142 @@ 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
})

View file

@ -1,55 +1,63 @@
define(function () { define(["helper"], function (helper) {
"use strict";
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 = helper.sum(d.nodes.all.map(helper.one));
var totalOnlineNodes = sum(d.nodes.all.filter(online).map(one)) var totalOnlineNodes = helper.sum(d.nodes.all.filter(helper.online).map(helper.one));
var totalOfflineNodes = sum(d.nodes.all.filter(function (node) {return !node.flags.online}).map(one)) var totalOfflineNodes = helper.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(helper.one));
var totalClients = sum(d.nodes.all.filter(online).map( function (d) { var totalNewNodes = helper.sum(d.nodes.new.map(helper.one));
return d.statistics.clients ? d.statistics.clients : 0 var totalLostNodes = helper.sum(d.nodes.lost.map(helper.one));
})) var totalClients = helper.sum(d.nodes.all.filter(helper.online).map(function (d) {
var totalGateways = sum(Array.from(new Set(d.nodes.all.filter(online).map( function(d) { return d.statistics.clients ? d.statistics.clients : 0;
return ("gateway" in d.statistics && d.statistics.gateway.id) ? d.statistics.gateway.id : d.statistics.gateway }));
}).concat(d.nodes.all.filter( function (d) { var totalGateways = helper.sum(Array.from(new Set(d.nodes.all.filter(helper.online).map(function (d) {
return d.flags.gateway return ("gateway" in d.statistics && d.statistics.gateway.id) ? d.statistics.gateway.id : d.statistics.gateway;
})))).map(function(d) { }).concat(d.nodes.all.filter(function (d) {
return (typeof d === "string") ? 1 : 0 return d.flags.gateway;
})) })))).map(function (d) {
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,109 @@
define(["sorttable", "virtual-dom", "numeral"], function (SortTable, V, numeral) { define(["sorttable", "virtual-dom", "helper"], function (SortTable, V, helper) {
"use strict";
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 (helper.hasLocation(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", ("clients" in d.statistics ? d.statistics.clients : 0).toFixed(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

@ -1,236 +1,284 @@
define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "vercomp" ], define(["chroma-js", "virtual-dom", "filters/genericnode", "helper"],
function (Chroma, V, numeral, Filter, vercomp) { function (Chroma, V, Filter, helper) {
"use strict";
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 helper.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 = helper.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 = helper.dictGet(d, key.slice(0));
if (f !== undefined) if (f !== undefined) {
v = f(v) v = f(v);
if (v === null)
return
dict[v] = d.statistics.clients + (v in dict ? dict[v] : 0)
})
return Object.keys(dict).map(function (d) { return [d, dict[d], key, f] })
} }
if (v === null) {
return;
}
dict[v] = d.statistics.clients + (v in dict ? dict[v] : 0);
});
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"))) }
}, d[1].toFixed(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(helper.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) {
if (b[0] < a[0]) {
return -1;
}
if (b[0] > a[0]) {
return 1;
}
return 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,232 @@
define(function () { define(["helper"], function (helper) {
"use strict";
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 = helper.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,52 @@
define([], function () { define(function () {
"use strict";
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,68 @@
define(["moment", "virtual-dom"], function (moment, V) { define(["moment", "virtual-dom", "helper", "moment.de"], function (moment, V, helper) {
return function(nodes, field, router, title) { "use strict";
var self = this
var el, tbody return function (nodes, field, router, title) {
var self = this;
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 (helper.hasLocation(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,64 @@
define(["virtual-dom"], function (V) { define(["virtual-dom"], function (V) {
return function(headings, sortIndex, renderRow) { "use strict";
var data
var sortReverse = false return function (headings, sortIndex, renderRow) {
var el = document.createElement("table") var data;
var elLast = V.h("table") var sortReverse = false;
var el = document.createElement("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,63 @@
define([], function () { define(function () {
"use strict";
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,40 @@
define(function () { define(function () {
"use strict";
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;
} };
}) });

243
lib/utils/helper.js Normal file
View file

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

View file

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

View file

@ -1,24 +1,34 @@
{ {
"name": "hopglass", "name": "hopglass",
"license": "AGPL-3.0",
"repository": {
"type": "git",
"url": "https://github.com/hopglass/hopglass.git"
},
"bugs": {
"url": "https://github.com/hopglass/hopglass/issues"
},
"scripts": { "scripts": {
"test": "node -e \"require('grunt').cli()\" '' clean lint" "test": "node -e \"require('grunt').cli()\" '' clean lint"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^6.3.3", "autoprefixer": "^6.3.6",
"grunt": "^0.4.5", "eslint": "^2.11.0",
"grunt-check-dependencies": "^0.6.0", "eslint-config-defaults": "^9.0.0",
"grunt-contrib-clean": "^0.6.0", "grunt": "^1.0.1",
"grunt-contrib-connect": "^0.8.0", "grunt-bower-install-simple": "^1.2.3",
"grunt-contrib-copy": "^0.5.0", "grunt-check-dependencies": "^0.12.0",
"grunt-contrib-cssmin": "^0.12.2", "grunt-contrib-clean": "^1.0.0",
"grunt-contrib-requirejs": "^0.4.4", "grunt-contrib-connect": "^1.0.2",
"grunt-sass": "^1.1.0", "grunt-contrib-copy": "^1.0.0",
"grunt-postcss": "^0.7.2", "grunt-contrib-cssmin": "^1.0.1",
"grunt-contrib-uglify": "^0.5.1", "grunt-contrib-requirejs": "^1.0.0",
"grunt-contrib-watch": "^0.6.1", "grunt-contrib-uglify": "^1.0.1",
"grunt-eslint": "^10.0.0", "grunt-contrib-watch": "^1.0.0",
"grunt-bower-install-simple": "^1.1.2", "grunt-eslint": "^18.1.0",
"grunt-git-describe": "^2.3.2" "grunt-inline": "^0.3.6",
"grunt-postcss": "^0.8.0",
"grunt-sass": "^1.2.0"
}, },
"eslintConfig": { "eslintConfig": {
"env": { "env": {
@ -26,24 +36,6 @@
"amd": true, "amd": true,
"es6": true, "es6": true,
"node": true "node": true
},
"globals": {
"showStat": false,
"attributeEntry": false,
"dictGet": false,
"getJSON": false,
"has_location": false,
"limit": false,
"localStorageTest": false,
"offline": false,
"one": false,
"online": false,
"showDistance": false,
"showTq": false,
"sortByKey": false,
"subtract": false,
"sum": false,
"trueDefault": false
} }
} }
} }

View file

@ -28,3 +28,7 @@ h5 {
h6 { h6 {
font-size: 0.67em; font-size: 0.67em;
} }
.hide {
display: none;
}

33
scss/_leaflet-layer.scss Normal file
View file

@ -0,0 +1,33 @@
.leaflet-control-layers {
box-shadow: none;
border-radius: 0;
background: none;
}
.leaflet-control-layers-toggle {
background: none;
&::before {
content: "\f229";
display: inline-block;
font-family: "ionicons" !important;
font-size: 2.3rem;
speak: none;
text-rendering: auto;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #e32d6d;
}
}
.leaflet-control-layers-expanded {
padding: 0;
}
.leaflet-control-layers-list {
background: rgba(255, 255, 255, 0.9);
padding: 10px;
label {
cursor: pointer;
}
}

23
scss/_loader.scss Normal file
View file

@ -0,0 +1,23 @@
.loader {
color: #000000;
font-size: 1.8em;
line-height: 2;
margin: 30vh auto;
text-align: center;
}
.spinner {
animation: .6s spinner ease-in-out infinite alternate;
border-bottom: 2px solid #000000;
border-radius: 50%;
display: inline-block;
height: 64px;
margin-top: 10px;
width: 64px;
}
@keyframes spinner {
to {
transform: rotate(360deg);
}
}

View file

@ -3,7 +3,9 @@
@import '_base'; @import '_base';
@import '_leaflet'; @import '_leaflet';
@import '_leaflet.label'; @import '_leaflet.label';
@import '_leaflet-layer';
@import '_filters'; @import '_filters';
@import '_loader';
$minscreenwidth: 630pt; $minscreenwidth: 630pt;
$sidebarwidth: 420pt; $sidebarwidth: 420pt;
@ -130,14 +132,14 @@ table.attributes tr.routerpic td {
font-weight: bold; font-weight: bold;
/*background-color: red;*/ /*background-color: red;*/
font-size: large; font-size: large;
vertical-align:bottom; vertical-align: bottom;
} }
table.attributes tr.routerpic th { table.attributes tr.routerpic th {
font-weight: bold; font-weight: bold;
/*background-color: red;*/ /*background-color: red;*/
font-size: large; font-size: large;
vertical-align:bottom; vertical-align: bottom;
} }
.nodenamesidebar { .nodenamesidebar {
@ -145,7 +147,7 @@ table.attributes tr.routerpic th {
font-weight: bold; font-weight: bold;
/*background-color: red;*/ /*background-color: red;*/
font-size: large; font-size: large;
vertical-align:bottom; vertical-align: bottom;
bottom: 0px; bottom: 0px;
} }

View file

@ -1,13 +1,10 @@
module.exports = function(grunt) { module.exports = function (grunt) {
"use strict";
grunt.config.merge({ grunt.config.merge({
bowerdir: "bower_components", bowerdir: "bower_components",
copy: { copy: {
html: { html: {
options: {
process: function (content) {
return content.replace("#revision#", grunt.option("gitRevision"))
}
},
src: ["*.html"], src: ["*.html"],
expand: true, expand: true,
cwd: "html/", cwd: "html/",
@ -26,7 +23,7 @@ module.exports = function(grunt) {
dest: "build/vendor/" dest: "build/vendor/"
}, },
robotoSlab: { robotoSlab: {
src: [ "fonts/*", src: ["fonts/*",
"roboto-slab-fontface.css" "roboto-slab-fontface.css"
], ],
expand: true, expand: true,
@ -34,7 +31,7 @@ module.exports = function(grunt) {
cwd: "bower_components/roboto-slab-fontface" cwd: "bower_components/roboto-slab-fontface"
}, },
roboto: { roboto: {
src: [ "fonts/*", src: ["fonts/*",
"roboto-fontface.css" "roboto-fontface.css"
], ],
expand: true, expand: true,
@ -42,15 +39,15 @@ module.exports = function(grunt) {
cwd: "bower_components/roboto-fontface" cwd: "bower_components/roboto-fontface"
}, },
ionicons: { ionicons: {
src: [ "fonts/*", src: ["fonts/*",
"css/ionicons.min.css" "hopglass-icons.css"
], ],
expand: true, expand: true,
dest: "build/", dest: "build/",
cwd: "bower_components/ionicons/" cwd: "assets/icons/"
}, },
leafletImages: { leafletImages: {
src: [ "images/*" ], src: ["images/*"],
expand: true, expand: true,
dest: "build/", dest: "build/",
cwd: "bower_components/leaflet/dist/" cwd: "bower_components/leaflet/dist/"
@ -83,13 +80,23 @@ module.exports = function(grunt) {
cssmin: { cssmin: {
target: { target: {
files: { files: {
"build/style.css": [ "bower_components/leaflet/dist/leaflet.css", "build/style.css": ["bower_components/leaflet/dist/leaflet.css",
"bower_components/Leaflet.label/dist/leaflet.label.css", "bower_components/Leaflet.label/dist/leaflet.label.css",
"style.css" "style.css"
] ]
} }
} }
}, },
inline: {
dist: {
options: {
cssmin: true,
uglify: true
},
src: "build/index.html",
dest: "build/index.html"
}
},
"bower-install-simple": { "bower-install-simple": {
options: { options: {
directory: "<%=bowerdir%>", directory: "<%=bowerdir%>",
@ -104,23 +111,34 @@ module.exports = function(grunt) {
} }
}, },
requirejs: { requirejs: {
compile: { default: {
options: { options: {
baseUrl: "lib", baseUrl: "lib",
name: "../bower_components/almond/almond", name: "../bower_components/almond/almond",
mainConfigFile: "app.js", mainConfigFile: "app.js",
include: "../app", include: "../app",
wrap: true, out: "build/app.js",
optimize: "uglify", build: true
out: "build/app.js" }
},
dev: {
options: {
baseUrl: "lib",
name: "../bower_components/almond/almond",
mainConfigFile: "app.js",
include: "../app",
optimize: "none",
out: "build/app.js",
build: false
} }
} }
} }
}) });
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");
} grunt.loadNpmTasks("grunt-inline");
};

View file

@ -1,9 +1,11 @@
module.exports = function (grunt) { module.exports = function (grunt) {
"use strict";
grunt.config.merge({ grunt.config.merge({
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

@ -1,9 +1,16 @@
module.exports = function (grunt) { module.exports = function (grunt) {
"use strict";
grunt.config.merge({ grunt.config.merge({
connect: { connect: {
server: { server: {
options: { options: {
base: "build/", //TODO: once grunt-contrib-connect 0.9 is released, set index file base: {
path: "build",
options: {
index: "index.html"
}
},
livereload: true livereload: true
} }
} }
@ -13,8 +20,8 @@ module.exports = function (grunt) {
options: { options: {
livereload: true livereload: true
}, },
files: ["*.css", "app.js", "lib/**/*.js", "*.html"], files: ["*.css", "app.js", "lib/**/*.js", "*.html", "scss/**/*.scss"],
tasks: ["default"] tasks: ["dev"]
}, },
config: { config: {
options: { options: {
@ -24,8 +31,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

@ -1,4 +1,6 @@
module.exports = function (grunt) { module.exports = function (grunt) {
"use strict";
grunt.config.merge({ grunt.config.merge({
checkDependencies: { checkDependencies: {
options: { options: {
@ -12,18 +14,6 @@ module.exports = function (grunt) {
npm: {} npm: {}
}, },
eslint: { eslint: {
options: {
rules: {
"semi": [2, "never"],
"curly": [2, "multi"],
"strict": [2, "never"],
"no-multi-spaces": 0,
"no-new": 0,
"no-shadow": 0,
"no-use-before-define": [1, "nofunc"],
"no-underscore-dangle": 0
}
},
sources: { sources: {
src: ["app.js", "!Gruntfile.js", "lib/**/*.js"] src: ["app.js", "!Gruntfile.js", "lib/**/*.js"]
}, },
@ -31,8 +21,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");
} };