Compare commits

...

32 commits

Author SHA1 Message Date
Xaver Maierhofer ed06ff6b09 [!!!][TASK] Refactor Scss, add Sass-lint and adjust styling
Add variables to allow easy modifications to color, font and also extending Style
2017-03-18 20:06:42 +01:00
Xaver Maierhofer 6175d6bb7a [TASK] Update eslint to 2.11.0 2017-03-18 16:30:18 +01:00
Milan Pässler 931ca67fc2 [BUGFIX] bower package name should be lowercase 2017-03-18 16:29:47 +01:00
Milan Pässler 340c1d9a53 [TASK] Add required information into package.json 2017-03-18 16:29:30 +01:00
Xaver Maierhofer ae59f75594 [TASK] Use strict 2017-03-18 16:26:29 +01:00
Xaver Maierhofer d57ce0052a [TASK] Add scss to grunt watcher 2017-03-18 16:26:29 +01:00
Xaver Maierhofer 894f32da54 [TASK] Style layer selector 2017-03-18 16:26:29 +01:00
Milan Pässler a8c19ba44c [!!!][TASK] Use eslint default 2017-03-18 16:26:29 +01:00
Xaver Maierhofer a627e2febc [TASK] Remove tablesort number
No complex different styled numbers need to be sorted
2017-03-18 16:26:29 +01:00
Xaver Maierhofer e32fe6648d [TASK] Remove jsHashes 2017-03-18 16:26:29 +01:00
Xaver Maierhofer a2ab0dce4f [TASK] Simple sort for firmware versions 2017-03-18 16:26:29 +01:00
Xaver Maierhofer 90d2c3e444 [TASK] Replace numeraljs with toFixed vanilla js 2017-03-18 16:26:29 +01:00
Xaver Maierhofer a56876a10e [TASK] Update npm bower install module 2017-03-18 16:26:29 +01:00
Xaver Maierhofer 2d5e35324a [TASK] Add helper.js to grunt watch 2017-03-18 16:26:29 +01:00
Xaver Maierhofer 28de7c5403 [TASK] Adjust marker style - focus on online nodes 2017-03-18 16:26:29 +01:00
Milan Pässler 2630b6197d fix linting 2017-03-18 16:26:29 +01:00
Nils Schneider 62d64c717c [TASK] remove build.js, it is not needed anymore 2017-03-18 16:26:29 +01:00
Xaver Maierhofer 5691406d09 [TASK] Format load average 2017-03-18 16:26:29 +01:00
Xaver Maierhofer e80f7d899d [TASK] Only add german language from numeral.js 2017-03-18 16:26:29 +01:00
Xaver Maierhofer 292c5c020c [TASK] Unicode instead of utf8 icons 2017-03-18 16:26:25 +01:00
Xaver Maierhofer 58279ac213 [BUGFIX] Enable build for default 2017-03-17 03:23:28 +01:00
Xaver Maierhofer 6f4b9d6701 [TASK] Replace all specialchars in NODE_NAME in stat urls 2017-03-17 03:23:28 +01:00
Xaver Maierhofer e9e393e105 [TASK] Seperate dev and default for requirejs 2017-03-17 03:23:28 +01:00
Xaver Maierhofer 5921a5f355 [TASK] Add index option to grunt-conncet server 2017-03-17 03:23:28 +01:00
Xaver Maierhofer c9458ffcae [TASK] CGL - Optimize returns and unnecessary math 2017-03-17 03:23:28 +01:00
Xaver Maierhofer c7b58d5743 [TASK] Inline style and icon css and es6-shim 2017-03-17 03:23:28 +01:00
Xaver Maierhofer 7389547e3e [BUGFIX] Correct css path to icon font 2017-03-17 03:23:28 +01:00
Xaver Maierhofer 6aec0b21a8 [TASK] Update bower components 2017-03-17 03:23:28 +01:00
Xaver Maierhofer 04eb8f039b [TASK] Only include additional german locale
English is default
2017-03-17 03:23:28 +01:00
Xaver Maierhofer 0abd4130cf [TASK] Use own iconfont set
Reduce overhead in initial load
2017-03-17 03:23:28 +01:00
Milan Pässler 418b630e02 change code style to ffrgb/meshviewer fork 2017-03-17 03:23:07 +01:00
H4ndl3 59ba0ba29e 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-17 03:16:41 +01:00
79 changed files with 4576 additions and 3960 deletions

View file

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

2
.gitignore vendored
View file

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

3
.sass-lint.yml Normal file
View file

@ -0,0 +1,3 @@
rules:
no-color-literals: 0
single-line-per-selector: 0

View file

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

View file

@ -46,10 +46,19 @@ Just run the following command from the hopglass directory:
This will generate `build/` containing all required files.
## Development
Use `grunt serve` for development.
# Configure
Copy `config.json.example` to `build/config.json` and change it to match your community.
## Customize style
Start your development and edit files in `scss/custom/`. Additional information in comments.
## dataPath (string/array)
`dataPath` can be either a string containing the address of a [HopGlass Server](https://github.com/plumpudding/hopglass-server) or an array containing multiple addresses.

30
app.js
View file

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

View file

@ -0,0 +1,7 @@
@mixin icon($name, $code, $prefix: 'ion-') {
.#{$prefix}#{$name} {
&:before {
content: '#{$code}';
}
}
}

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

45
assets/icons/icon.scss Normal file
View file

@ -0,0 +1,45 @@
// Needed for standalone scss
// @import 'icon-mixin';
@font-face {
font-family: 'ionicons';
font-style: normal;
font-weight: normal;
src: url('fonts/icon.woff2') format('woff2'),
url('fonts/icon.woff') format('woff'),
url('fonts/icon.ttf') format('truetype');
}
[class^='ion-'], [class*=' ion-'] {
&:before {
display: inline-block;
font-family: $font-family-icons;
font-style: normal;
font-variant: normal;
font-weight: normal;
line-height: 1;
speak: none;
text-rendering: auto;
text-transform: none;
vertical-align: 0;
}
}
@include icon('android-add', '\f2c7');
@include icon('chevron-left', '\f124');
@include icon('chevron-right', '\f125');
@include icon('pin', '\f3a3');
@include icon('wifi', '\f25c');
@include icon('eye', '\f133');
@include icon('ios-arrow-thin-left', '\f3d5');
@include icon('ios-arrow-thin-right', '\f3d6');
@include icon('arrow-up-b', '\f10d');
@include icon('arrow-down-b', '\f104');
@include icon('android-locate', '\f2e9');
@include icon('android-close', '\f2d7');
@include icon('android-lock', '\f392');
@include icon('ios-copy', '\f41c');
@include icon('location', '\f456');
@include icon('android-remove', '\f2f4');
@include icon('ios-person', '\f47e');
@include icon('layer', '\f229');

View file

@ -1,5 +1,5 @@
{
"name": "HopGlass",
"name": "hopglass",
"ignore": [
"node_modules",
"bower_components",
@ -9,23 +9,19 @@
],
"dependencies": {
"Leaflet.label": "~0.2.1",
"chroma-js": "~0.6.1",
"leaflet": "~0.7.3",
"ionicons": "~2.0.1",
"moment": "~2.9.0",
"requirejs": "~2.1.16",
"tablesort": "https://github.com/tristen/tablesort.git#v3.0.2",
"chroma-js": "~1.1.1",
"leaflet": "~0.7.7",
"moment": "~2.13.0",
"requirejs": "~2.2.0",
"tablesort": "https://github.com/tristen/tablesort.git#v4.0.1",
"roboto-slab-fontface": "*",
"es6-shim": "~0.27.1",
"almond": "~0.3.1",
"r.js": "~2.1.16",
"d3": "~3.5.5",
"numeraljs": "~1.5.3",
"roboto-fontface": "~0.3.0",
"virtual-dom": "~2.0.1",
"leaflet-providers": "~1.0.27",
"rbush": "https://github.com/mourner/rbush.git#~1.3.5",
"jshashes": "~1.0.5"
"es6-shim": "~0.35.1",
"almond": "~0.3.2",
"d3": "~3.5.17",
"roboto-fontface": "~0.4.5",
"virtual-dom": "~2.1.1",
"leaflet-providers": "~1.1.10",
"rbush": "https://github.com/mourner/rbush.git#~1.4.3"
},
"authors": [
"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) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest()
req.open('GET', url)
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
req.onload = function () {
if (req.status == 200) {
resolve(req.response)
resolve(req.response);
}
else {
reject(Error(req.statusText))
}
reject(Error(req.statusText));
}
};
req.onerror = function() {
reject(Error("Network Error"))
}
req.onerror = function () {
reject(Error("Network Error"));
};
req.send()
})
req.send();
});
}
function getJSON(url) {
return get(url).then(JSON.parse)
return get(url).then(JSON.parse);
}
function sortByKey(key, d) {
return d.slice().sort( function (a, b) {
return a[key] - b[key]
}).reverse()
return d.slice().sort(function (a, b) {
return a[key] - b[key];
}).reverse();
}
function limit(key, m, d) {
return d.filter( function (d) {
return d[key].isAfter(m)
})
return d.filter(function (d) {
return d[key].isAfter(m);
});
}
function sum(a) {
return a.reduce( function (a, b) {
return a + b
}, 0)
return a.reduce(function (a, b) {
return a + b;
}, 0);
}
function one() {
return 1
return 1;
}
function trueDefault(d) {
return d === undefined ? true : d
return d === undefined ? true : d;
}
function dictGet(dict, key) {
var k = key.shift()
var k = key.shift();
if (!(k in dict))
return null
if (!(k in dict)) {
return null;
}
if (key.length == 0)
return dict[k]
if (key.length == 0) {
return dict[k];
}
return dictGet(dict[k], key)
return dictGet(dict[k], key);
}
function localStorageTest() {
var test = 'test'
var test = 'test';
try {
localStorage.setItem(test, test)
localStorage.removeItem(test)
return true
} catch(e) {
return false
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch (e) {
return false;
}
}
function listReplace(s, subst) {
for (key in subst) {
var re = new RegExp(key, 'g')
s = s.replace(re, subst[key])
var re = new RegExp(key, 'g');
s = s.replace(re, subst[key]);
}
return s
return s;
}
/* Helpers working with nodes */
function offline(d) {
return !d.flags.online
return !d.flags.online;
}
function online(d) {
return d.flags.online
return d.flags.online;
}
function has_location(d) {
return "location" in d.nodeinfo &&
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) {
var ids = {}
var ids = {};
b.forEach( function (d) {
ids[d.nodeinfo.node_id] = true
})
b.forEach(function (d) {
ids[d.nodeinfo.node_id] = true;
});
return a.filter( function (d) {
return !(d.nodeinfo.node_id in ids)
})
return a.filter(function (d) {
return !(d.nodeinfo.node_id in ids);
});
}
/* Helpers working with links */
function showDistance(d) {
if (isNaN(d.distance))
return
if (isNaN(d.distance)) {
return;
}
return numeral(d.distance).format("0,0") + " m"
return d.distance.toFixed(0) + " m"
}
function showTq(d) {
return numeral(1/d.tq).format("0%")
return (1 / d.tq * 100).toFixed(0) + "%"
}
/* Infobox stuff (XXX: move to module) */
function attributeEntry(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.className = "routerpic"
if (value === null || value == undefined) {
return;
}
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")
value(td)
else
td.appendChild(document.createTextNode(value))
var td = document.createElement("td");
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) {
el = document.createElement("iframe")
width = typeof width !== 'undefined' ? width : '100%'
height = typeof height !== 'undefined' ? height : '350px'
el = document.createElement("iframe");
width = typeof width !== 'undefined' ? width : '100%';
height = typeof height !== 'undefined' ? height : '350px';
if (opt.src)
el.src = opt.src
else
el.src = opt
if (opt.src) {
el.src = opt.src;
} else {
el.src = opt;
}
if (opt.frameBorder)
el.frameBorder = opt.frameBorder
else
el.frameBorder = 1
if (opt.frameBorder) {
el.frameBorder = opt.frameBorder;
} else {
el.frameBorder = 1;
}
if (opt.width)
el.width = opt.width
else
el.width = width
if (opt.width) {
el.width = opt.width;
} else {
el.width = width;
}
if (opt.height)
el.height = opt.height
else
el.height = height
if (opt.height) {
el.height = opt.height;
} else {
el.height = height;
}
el.scrolling = "no"
el.seamless = "seamless"
el.scrolling = "no";
el.seamless = "seamless";
return el
return el;
}
function showStat(o, subst) {
var content, caption
subst = typeof subst !== 'undefined' ? subst : {}
var content, caption;
subst = typeof subst !== 'undefined' ? subst : {};
if (o.thumbnail) {
content = document.createElement("img")
content.src = listReplace(o.thumbnail, subst)
content = document.createElement("img");
content.src = listReplace(o.thumbnail, subst);
}
if (o.caption) {
caption = listReplace(o.caption, subst)
caption = listReplace(o.caption, subst);
if (!content)
content = document.createTextNode(caption)
if (!content) {
content = document.createTextNode(caption);
}
}
if (o.iframe) {
content = createIframe(o.iframe, o.width, o.height)
if (o.iframe.src)
content.src = listReplace(o.iframe.src, subst)
else
content.src = listReplace(o.iframe, subst)
content = createIframe(o.iframe, o.width, o.height);
if (o.iframe.src) {
content.src = listReplace(o.iframe.src, subst);
} else {
content.src = listReplace(o.iframe, subst);
}
}
var p = document.createElement("p")
var p = document.createElement("p");
if (o.href) {
var link = document.createElement("a")
link.target = "_blank"
link.href = listReplace(o.href, subst)
link.appendChild(content)
var link = document.createElement("a");
link.target = "_blank";
link.href = listReplace(o.href, subst);
link.appendChild(content);
if (caption && o.thumbnail)
link.title = caption
if (caption && o.thumbnail) {
link.title = caption;
}
p.appendChild(link)
} else
p.appendChild(content)
p.appendChild(link);
} else {
p.appendChild(content);
}
return p
return p;
}

View file

@ -1,18 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" href="css/ionicons.min.css">
<link rel="stylesheet" href="roboto-slab-fontface.css">
<link rel="stylesheet" href="roboto-fontface.css">
<link rel="stylesheet" href="style.css">
<script src="vendor/es6-shim/es6-shim.min.js"></script>
<link rel="stylesheet" href="style.css?__inline=true">
<script src="vendor/es6-shim/es6-shim.min.js?__inline=true"></script>
<script src="app.js"></script>
<script>
console.log("Version: #revision#")
console.log("Version: #revision#");
</script>
</head>
<body>
</body>
</head>
<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>

View file

@ -1,17 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<head>
<meta charset="utf-8">
<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-fontface/roboto-fontface.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/ionicons/css/ionicons.min.css">
<link rel="stylesheet" href="assets/icons/hopglass-icons.css">
<link rel="stylesheet" href="style.css">
<script src="bower_components/es6-shim/es6-shim.min.js"></script>
<script src="bower_components/requirejs/require.js" data-main="app"></script>
</head>
<body>
</body>
</head>
<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>

View file

@ -1,38 +1,38 @@
define(function () {
return function() {
"use strict";
return function () {
this.render = function (d) {
var el = document.createElement("div")
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 += "auch zoomen.</p>"
s += "<p>Mit Doppelklick und Shift+Doppelklick kann man in der Karte ";
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) Nils Schneider</p>"
s += "<p>Copyright (C) Milan Pässler</p>";
s += "<p>Copyright (C) Nils Schneider</p>";
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 += "License as published by the Free Software Foundation, either "
s += "version 3 of the License, or (at your option) any later version.</p>"
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 += "License as published by the Free Software Foundation, either ";
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 += "but WITHOUT ANY WARRANTY; without even the implied warranty of "
s += "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
s += "GNU Affero General Public License for more details.</p>"
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 += "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ";
s += "GNU Affero General Public License for more details.</p>";
s += "<p>You should have received a copy of the GNU Affero General "
s += "Public License along with this program. If not, see "
s += "<a href=\"https://www.gnu.org/licenses/\">"
s += "https://www.gnu.org/licenses/</a>.</p>"
s += "<p>You should have received a copy of the GNU Affero General ";
s += "Public License along with this program. If not, see ";
s += "<a href=\"https://www.gnu.org/licenses/\">";
s += "https://www.gnu.org/licenses/</a>.</p>";
s += "<p>The source code is available at "
s += "<a href=\"https://github.com/plumpudding/hopglass\">"
s += "https://github.com/plumpudding/hopglass</a>."
s += "<p>The source code is available at ";
s += "<a href=\"https://github.com/plumpudding/hopglass\">";
s += "https://github.com/plumpudding/hopglass</a>.";
el.innerHTML = s
}
}
})
d.innerHTML = s;
};
};
});

View file

@ -1,20 +1,23 @@
define([], function () {
"use strict";
return function (tag) {
if (!tag)
tag = "div"
if (!tag) {
tag = "div";
}
var self = this
var self = this;
var container = document.createElement(tag)
var container = document.createElement(tag);
self.add = function (d) {
d.render(container)
}
d.render(container);
};
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) {
"use strict";
return function () {
var targets = []
var filterObservers = []
var filters = []
var filteredData
var data
var targets = [];
var filterObservers = [];
var filters = [];
var filteredData;
var data;
function remove(d) {
targets = targets.filter( function (e) { return d !== e } )
targets = targets.filter(function (e) {
return d !== e;
});
}
function add(d) {
targets.push(d)
targets.push(d);
if (filteredData !== undefined)
d.setData(filteredData)
if (filteredData !== undefined) {
d.setData(filteredData);
}
}
function setData(d) {
data = d
refresh()
data = d;
refresh();
}
function refresh() {
if (data === undefined)
return
var filter = filters.reduce( function (a, f) {
return function (d) {
return a(d) && f.run(d)
if (data === undefined) {
return;
}
}, 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) {
t.setData(filteredData)
})
filteredData = new NodeFilter(filter)(data);
targets.forEach(function (t) {
t.setData(filteredData);
});
}
function notifyObservers() {
filterObservers.forEach( function (d) {
d.filtersChanged(filters)
})
filterObservers.forEach(function (d) {
d.filtersChanged(filters);
});
}
function addFilter(d) {
filters.push(d)
notifyObservers()
d.setRefresh(refresh)
refresh()
filters.push(d);
notifyObservers();
d.setRefresh(refresh);
refresh();
}
function removeFilter(d) {
filters = filters.filter( function (e) { return d !== e } )
notifyObservers()
refresh()
filters = filters.filter(function (e) {
return d !== e;
});
notifyObservers();
refresh();
}
function watchFilters(d) {
filterObservers.push(d)
filterObservers.push(d);
d.filtersChanged(filters)
d.filtersChanged(filters);
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,
setData: setData,
addFilter: addFilter,
removeFilter: removeFilter,
watchFilters: watchFilters,
refresh: refresh
}
}
})
};
};
});

View file

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

View file

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

View file

@ -1,33 +1,38 @@
define([], function () {
define(function () {
"use strict";
return function (filter) {
return function (data) {
var n = Object.create(data)
n.nodes = {}
var n = Object.create(data);
n.nodes = {};
for (var key in data.nodes)
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
for (var key in data.nodes) {
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;
};
};
});

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",
"proportions", "forcegraph", "title", "about", "datadistributor",
"filters/filtergui" ],
function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
"filters/filtergui"],
function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
Nodelist, SimpleNodelist, Infobox, Proportions, ForceGraph,
Title, About, DataDistributor, FilterGUI) {
"use strict";
return function (config, router) {
var self = this
var content
var contentDiv
var self = this;
var content;
var contentDiv;
var linkScale = chroma.scale(chroma.interpolate.bezier(["#04C714", "#FF5500", "#F02311"])).domain([1, 5])
var sidebar
var linkScale = chroma.scale(chroma.bezier(["#04C714", "#FF5500", "#F02311"])).domain([1, 5]);
var sidebar;
var buttons = document.createElement("div")
buttons.classList.add("buttons")
var buttons = document.createElement("div");
buttons.classList.add("buttons");
var fanout = new DataDistributor()
var fanoutUnfiltered = new DataDistributor()
fanoutUnfiltered.add(fanout)
var fanout = new DataDistributor();
var fanoutUnfiltered = new DataDistributor();
fanoutUnfiltered.add(fanout);
function removeContent() {
if (!content)
return
if (!content) {
return;
}
router.removeTarget(content)
fanout.remove(content)
router.removeTarget(content);
fanout.remove(content);
content.destroy()
content.destroy();
content = null
content = null;
}
function addContent(K) {
removeContent()
removeContent();
content = new K(config, linkScale, sidebar.getWidth, router, buttons)
content.render(contentDiv)
content = new K(config, linkScale, sidebar.getWidth, router, buttons);
content.render(contentDiv);
fanout.add(content)
router.addTarget(content)
fanout.add(content);
router.addTarget(content);
}
function mkView(K) {
return function () {
addContent(K)
}
addContent(K);
};
}
contentDiv = document.createElement("div")
contentDiv.classList.add("content")
document.body.appendChild(contentDiv)
var loader = document.getElementsByClassName("loader")[0];
loader.classList.add("hide");
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")
buttonToggle.textContent = "\uF133"
contentDiv.appendChild(buttons);
var buttonToggle = document.createElement("button");
buttonToggle.classList.add("ion-eye", "shadow");
buttonToggle.onclick = function () {
if (content.constructor === Map)
router.view("g")
else
router.view("m")
if (content.constructor === Map) {
router.view("g");
} else {
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 infobox = new Infobox(config, sidebar, router)
var tabs = new Tabs()
var overview = new Container()
var meshstats = new Meshstats(config)
var legend = new Legend()
var newnodeslist = new SimpleNodelist("new", "firstseen", router, "Neue Knoten")
var lostnodeslist = new SimpleNodelist("lost", "lastseen", router, "Verschwundene Knoten")
var nodelist = new Nodelist(router)
var linklist = new Linklist(linkScale, router)
var statistics = new Proportions(config, fanout)
var about = new About()
var header = new Container("header");
var infobox = new Infobox(config, sidebar, router);
var tabs = new Tabs();
var overview = new Container();
var meshstats = new Meshstats(config);
var legend = new Legend();
var newnodeslist = new SimpleNodelist("new", "firstseen", router, "Neue Knoten");
var lostnodeslist = new SimpleNodelist("lost", "lastseen", router, "Verschwundene Knoten");
var nodelist = new Nodelist(router);
var linklist = new Linklist(linkScale, router);
var statistics = new Proportions(config, fanout);
var about = new About();
fanoutUnfiltered.add(meshstats)
fanoutUnfiltered.add(newnodeslist)
fanoutUnfiltered.add(lostnodeslist)
fanout.add(nodelist)
fanout.add(linklist)
fanout.add(statistics)
fanoutUnfiltered.add(meshstats);
fanoutUnfiltered.add(newnodeslist);
fanoutUnfiltered.add(lostnodeslist);
fanout.add(nodelist);
fanout.add(linklist);
fanout.add(statistics);
sidebar.add(header)
header.add(meshstats)
header.add(legend)
sidebar.add(header);
header.add(meshstats);
header.add(legend);
overview.add(newnodeslist)
overview.add(lostnodeslist)
overview.add(newnodeslist);
overview.add(lostnodeslist);
var filterGUI = new FilterGUI(fanout)
fanout.watchFilters(filterGUI)
header.add(filterGUI)
var filterGUI = new FilterGUI(fanout);
fanout.watchFilters(filterGUI);
header.add(filterGUI);
sidebar.add(tabs)
tabs.add("Aktuelles", overview)
tabs.add("Knoten", nodelist)
tabs.add("Verbindungen", linklist)
tabs.add("Statistiken", statistics)
tabs.add("Über", about)
sidebar.add(tabs);
tabs.add("Aktuelles", overview);
tabs.add("Knoten", nodelist);
tabs.add("Verbindungen", linklist);
tabs.add("Statistiken", statistics);
tabs.add("Über", about);
router.addTarget(title)
router.addTarget(infobox)
router.addTarget(title);
router.addTarget(infobox);
router.addView("m", mkView(Map))
router.addView("g", mkView(ForceGraph))
router.addView("m", mkView(Map));
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,58 @@
define(function () {
define(["helper"], function (helper) {
"use strict";
function showStatImg(o, d) {
var subst = {}
subst["{SOURCE}"] = d.source.node_id
subst["{SOURCE_NAME}"] = d.source.node.nodeinfo.hostname ? d.source.node.nodeinfo.hostname : "unknown"
subst["{TARGET}"] = d.target.node_id
subst["{TARGET_NAME}"] = d.target.node.nodeinfo.hostname ? d.target.node.nodeinfo.hostname : "unknown"
return showStat(o, subst)
var subst = {};
subst["{SOURCE}"] = d.source.node_id;
subst["{SOURCE_NAME}"] = d.source.node.nodeinfo.hostname ? d.source.node.nodeinfo.hostname : "unknown";
subst["{TARGET}"] = d.target.node_id;
subst["{TARGET_NAME}"] = d.target.node.nodeinfo.hostname ? d.target.node.nodeinfo.hostname : "unknown";
return helper.showStat(o, subst);
}
return function (config, el, router, d) {
var unknown = !(d.source.node)
var h2 = document.createElement("h2")
var a1 = document.createElement("a")
var unknown = !d.source.node;
var h2 = document.createElement("h2");
var a1;
if (!unknown) {
a1.href = "#"
a1.onclick = router.node(d.source.node)
a1 = document.createElement("a");
a1.href = "#";
a1.onclick = router.node(d.source.node);
} else {
a1 = document.createElement("span");
}
a1.textContent = unknown ? d.source.id : d.source.node.nodeinfo.hostname
h2.appendChild(a1)
h2.appendChild(document.createTextNode(" → "))
var a2 = document.createElement("a")
a2.href = "#"
a2.onclick = router.node(d.target.node)
a2.textContent = d.target.node.nodeinfo.hostname
h2.appendChild(a2)
el.appendChild(h2)
a1.textContent = unknown ? d.source.id : d.source.node.nodeinfo.hostname;
h2.appendChild(a1);
var attributes = document.createElement("table")
attributes.classList.add("attributes")
var arrow = document.createElement("spam");
arrow.classList.add("ion-ios-arrow-thin-right");
h2.appendChild(arrow);
attributeEntry(attributes, "TQ", showTq(d))
attributeEntry(attributes, "Entfernung", showDistance(d))
attributeEntry(attributes, "Typ", d.type)
var hw1 = unknown ? null : dictGet(d.source.node.nodeinfo, ["hardware", "model"])
var hw2 = dictGet(d.target.node.nodeinfo, ["hardware", "model"])
attributeEntry(attributes, "Hardware", (hw1 != null ? hw1 : "unbekannt") + " " + (hw2 != null ? hw2 : "unbekannt"))
el.appendChild(attributes)
var a2 = document.createElement("a");
a2.href = "#";
a2.onclick = router.node(d.target.node);
a2.textContent = d.target.node.nodeinfo.hostname;
h2.appendChild(a2);
el.appendChild(h2);
if (config.linkInfos)
config.linkInfos.forEach( function (linkInfo) {
var h4 = document.createElement("h4")
h4.textContent = linkInfo.name
el.appendChild(h4)
el.appendChild(showStatImg(linkInfo, d))
})
var attributes = document.createElement("table");
attributes.classList.add("attributes");
helper.attributeEntry(attributes, "TQ", helper.showTq(d));
helper.attributeEntry(attributes, "Entfernung", helper.showDistance(d));
helper.attributeEntry(attributes, "Typ", d.type);
var hw1 = unknown ? null : helper.dictGet(d.source.node.nodeinfo, ["hardware", "model"]);
var hw2 = helper.dictGet(d.target.node.nodeinfo, ["hardware", "model"]);
helper.attributeEntry(attributes, "Hardware", (hw1 != null ? hw1 : "unbekannt") + " " + (hw2 != null ? hw2 : "unbekannt"));
el.appendChild(attributes);
if (config.linkInfos) {
config.linkInfos.forEach(function (linkInfo) {
var h4 = document.createElement("h4");
h4.textContent = linkInfo.name;
el.appendChild(h4);
el.appendChild(showStatImg(linkInfo, d));
});
}
})
};
});

View file

@ -1,100 +1,84 @@
define(function () {
define(["helper"], function (helper) {
"use strict";
return function (config, el, router, d) {
var sidebarTitle = document.createElement("h2")
sidebarTitle.textContent = "Location: " + d.toString()
el.appendChild(sidebarTitle)
var sidebarTitle = document.createElement("h2");
sidebarTitle.textContent = "Location: " + d.toString();
el.appendChild(sidebarTitle);
getJSON("https://nominatim.openstreetmap.org/reverse?format=json&lat=" + d.lat + "&lon=" + d.lng + "&zoom=18&addressdetails=0")
.then(function(result) {
if(result.display_name)
sidebarTitle.textContent = result.display_name
})
helper.getJSON("https://nominatim.openstreetmap.org/reverse?format=json&lat=" + d.lat + "&lon=" + d.lng + "&zoom=18&addressdetails=0")
.then(function (result) {
if (result.display_name) {
sidebarTitle.textContent = result.display_name;
}
});
var editLat = document.createElement("input")
editLat.type = "text"
editLat.value = d.lat.toFixed(9)
el.appendChild(createBox("lat", "Breitengrad", editLat))
var editLat = document.createElement("input");
editLat.type = "text";
editLat.value = d.lat.toFixed(9);
el.appendChild(createBox("lat", "Breitengrad", editLat));
var editLng = document.createElement("input")
editLng.type = "text"
editLng.value = d.lng.toFixed(9)
el.appendChild(createBox("lng", "Längengrad", editLng))
var editLng = document.createElement("input");
editLng.type = "text";
editLng.value = d.lng.toFixed(9);
el.appendChild(createBox("lng", "Längengrad", editLng));
var editUci = document.createElement("textarea")
var editUci = document.createElement("textarea");
editUci.value =
"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].latitude='" + d.lat.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", "Uci", editUci));
var linkPlain = document.createElement("a")
linkPlain.textContent = "plain"
linkPlain.onclick = function() {
switch2plain()
return false
}
linkPlain.href = "#"
var linkPlain = document.createElement("a");
linkPlain.textContent = "plain";
linkPlain.onclick = function () {
switch2plain();
return false;
};
linkPlain.href = "#";
var linkUci = document.createElement("a")
linkUci.textContent = "uci"
linkUci.onclick = function() {
switch2uci()
return false
}
linkUci.href = "#"
var linkUci = document.createElement("a");
linkUci.textContent = "uci";
linkUci.onclick = function () {
switch2uci();
return false;
};
linkUci.href = "#";
var hintText = document.createElement("p")
hintText.appendChild(document.createTextNode("Du kannst zwischen "))
hintText.appendChild(linkPlain)
hintText.appendChild(document.createTextNode(" und "))
hintText.appendChild(linkUci)
hintText.appendChild(document.createTextNode(" wechseln."))
el.appendChild(hintText)
function createBox(name, title, inputElem, isVisible) {
var visible = typeof isVisible !== "undefined" ? isVisible : true
var box = document.createElement("div")
var heading = document.createElement("h3")
heading.textContent = title
box.appendChild(heading)
var btn = document.createElement("button")
btn.className = "ion-ios-copy"
btn.title = "Kopieren"
btn.onclick = function() { copy2clip(inputElem.id) }
inputElem.id = "location-" + name
inputElem.readOnly = true
var line = document.createElement("p")
line.appendChild(inputElem)
line.appendChild(btn)
box.appendChild(line)
box.id = "box-" + name
box.style.display = visible ? "block" : "none"
return box
function createBox(name, title, inputElem) {
var box = document.createElement("div");
var heading = document.createElement("h3");
heading.textContent = title;
box.appendChild(heading);
var btn = document.createElement("button");
btn.classList.add("ion-ios-copy");
btn.title = "Kopieren";
btn.onclick = function () {
copy2clip(inputElem.id);
};
inputElem.id = "location-" + name;
inputElem.readOnly = true;
var line = document.createElement("p");
line.appendChild(inputElem);
line.appendChild(btn);
box.appendChild(line);
box.id = "box-" + name;
return box;
}
function copy2clip(id) {
var copyField = document.querySelector("#" + id)
copyField.select()
var copyField = document.querySelector("#" + id);
copyField.select();
try {
document.execCommand("copy")
document.execCommand("copy");
} catch (err) {
console.log(err)
console.warn(err);
}
}
function switch2plain() {
document.getElementById("box-uci").style.display = "none"
document.getElementById("box-lat").style.display = "block"
document.getElementById("box-lng").style.display = "block"
}
function switch2uci() {
document.getElementById("box-uci").style.display = "block"
document.getElementById("box-lat").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) {
"use strict";
return function (config, sidebar, router) {
var self = this
var el
var self = this;
var el;
function destroy() {
if (el && el.parentNode) {
el.parentNode.removeChild(el)
el = undefined
sidebar.reveal()
el.parentNode.removeChild(el);
el = undefined;
sidebar.reveal();
}
}
function create() {
destroy()
sidebar.ensureVisible()
sidebar.hide()
destroy();
sidebar.ensureVisible();
sidebar.hide();
el = document.createElement("div")
sidebar.container.insertBefore(el, sidebar.container.firstChild)
el = document.createElement("div");
sidebar.container.insertBefore(el, sidebar.container.firstChild);
el.scrollIntoView(false)
el.classList.add("infobox")
el.destroy = destroy
el.scrollIntoView(false);
el.classList.add("infobox");
el.destroy = destroy;
var closeButton = document.createElement("button")
closeButton.classList.add("close")
closeButton.onclick = router.reset
el.appendChild(closeButton)
var closeButton = document.createElement("button");
closeButton.classList.add("close", "ion-android-close");
closeButton.onclick = router.reset;
el.appendChild(closeButton);
}
self.resetView = destroy
self.resetView = destroy;
self.gotoNode = function (d) {
create()
new Node(config, el, router, d)
}
create();
Node(config, el, router, d);
};
self.gotoLink = function (d) {
create()
new Link(config, el, router, d)
}
create();
Link(config, el, router, d);
};
self.gotoLocation = function (d) {
create()
new Location(config, el, router, d)
}
create();
Location(config, el, router, d);
};
return self
}
})
return self;
};
});

View file

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

View file

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

View file

@ -1,60 +1,65 @@
define(["sorttable", "virtual-dom"], function (SortTable, V) {
define(["sorttable", "virtual-dom", "helper"], function (SortTable, V, helper) {
"use strict";
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) {
return linkName(a).localeCompare(linkName(b))
return linkName(a).localeCompare(linkName(b));
},
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
},
{ name: "Typ",
{
name: "Typ",
sort: function (a, b) {
return a.type.localeCompare(b.type)
return a.type.localeCompare(b.type);
},
reverse: false
},
{ name: "Entfernung",
{
name: "Entfernung",
sort: function (a, b) {
return (a.distance === undefined ? -1 : a.distance) -
(b.distance === undefined ? -1 : b.distance)
(b.distance === undefined ? -1 : b.distance);
},
reverse: true
}]
}];
return function(linkScale, router) {
var table = new SortTable(headings, 2, renderRow)
return function (linkScale, router) {
var table = new SortTable(headings, 2, renderRow);
table.el.classList.add("link-list");
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 td2 = V.h("td", {style: {color: linkScale(d.tq).hex()}}, showTq(d))
var td3 = V.h("td", d.type)
var td4 = V.h("td", showDistance(d))
var td1 = V.h("td", td1Content);
var td2 = V.h("td", {style: {color: linkScale(d.tq).hex()}}, helper.showTq(d));
var td3 = V.h("td", d.type);
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) {
var el = document.createElement("div")
el.last = V.h("div")
d.appendChild(el)
var h2 = document.createElement("h2");
h2.textContent = "Verbindungen";
d.appendChild(h2);
var h2 = document.createElement("h2")
h2.textContent = "Verbindungen"
el.appendChild(h2)
el.appendChild(table.el)
}
d.appendChild(table.el);
};
this.setData = function (d) {
table.setData(d.graph.links)
}
}
})
table.setData(d.graph.links);
};
};
});

View file

@ -1,4 +1,6 @@
define(["leaflet"], function (L) {
"use strict";
return L.CircleMarker.extend({
outerCircle: {
stroke: false,
@ -29,31 +31,31 @@ define(["leaflet"], function (L) {
fillOpacity: 0.2
},
initialize: function(latlng) {
this.accuracyCircle = L.circle(latlng, 0, this.accuracyCircle)
this.outerCircle = L.circleMarker(latlng, this.outerCircle)
L.CircleMarker.prototype.initialize.call(this, latlng, this.innerCircle)
initialize: function (latlng) {
this.accuracyCircle = L.circle(latlng, 0, this.accuracyCircle);
this.outerCircle = L.circleMarker(latlng, this.outerCircle);
L.CircleMarker.prototype.initialize.call(this, latlng, this.innerCircle);
this.on("remove", function() {
this._map.removeLayer(this.accuracyCircle)
this._map.removeLayer(this.outerCircle)
})
this.on("remove", function () {
this._map.removeLayer(this.accuracyCircle);
this._map.removeLayer(this.outerCircle);
});
},
setLatLng: function(latlng) {
this.accuracyCircle.setLatLng(latlng)
this.outerCircle.setLatLng(latlng)
L.CircleMarker.prototype.setLatLng.call(this, latlng)
setLatLng: function (latlng) {
this.accuracyCircle.setLatLng(latlng);
this.outerCircle.setLatLng(latlng);
L.CircleMarker.prototype.setLatLng.call(this, latlng);
},
setAccuracy: function(accuracy) {
this.accuracyCircle.setRadius(accuracy)
setAccuracy: function (accuracy) {
this.accuracyCircle.setRadius(accuracy);
},
onAdd: function(map) {
this.accuracyCircle.addTo(map).bringToBack()
this.outerCircle.addTo(map)
L.CircleMarker.prototype.onAdd.call(this, map)
onAdd: function (map) {
this.accuracyCircle.addTo(map).bringToBack();
this.outerCircle.addTo(map);
L.CircleMarker.prototype.onAdd.call(this, map);
}
})
})
});
});

View file

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

View file

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

View file

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

View file

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

View file

@ -1,55 +1,63 @@
define(function () {
define(["helper"], function (helper) {
"use strict";
return function (config) {
var self = this
var stats, timestamp
var self = this;
var stats, timestamp;
self.setData = function (d) {
var totalNodes = sum(d.nodes.all.map(one))
var totalOnlineNodes = sum(d.nodes.all.filter(online).map(one))
var totalOfflineNodes = sum(d.nodes.all.filter(function (node) {return !node.flags.online}).map(one))
var totalNewNodes = sum(d.nodes.new.map(one))
var totalLostNodes = sum(d.nodes.lost.map(one))
var totalClients = sum(d.nodes.all.filter(online).map( function (d) {
return d.statistics.clients ? d.statistics.clients : 0
}))
var totalGateways = sum(Array.from(new Set(d.nodes.all.filter(online).map( function(d) {
return ("gateway" in d.statistics && d.statistics.gateway.id) ? d.statistics.gateway.id : d.statistics.gateway
}).concat(d.nodes.all.filter( function (d) {
return d.flags.gateway
})))).map(function(d) {
return (typeof d === "string") ? 1 : 0
}))
var totalNodes = helper.sum(d.nodes.all.map(helper.one));
var totalOnlineNodes = helper.sum(d.nodes.all.filter(helper.online).map(helper.one));
var totalOfflineNodes = helper.sum(d.nodes.all.filter(function (node) {
return !node.flags.online;
}).map(helper.one));
var totalNewNodes = helper.sum(d.nodes.new.map(helper.one));
var totalLostNodes = helper.sum(d.nodes.lost.map(helper.one));
var totalClients = helper.sum(d.nodes.all.filter(helper.online).map(function (d) {
return d.statistics.clients ? d.statistics.clients : 0;
}));
var totalGateways = helper.sum(Array.from(new Set(d.nodes.all.filter(helper.online).map(function (d) {
return ("gateway" in d.statistics && d.statistics.gateway.id) ? d.statistics.gateway.id : d.statistics.gateway;
}).concat(d.nodes.all.filter(function (d) {
return d.flags.gateway;
})))).map(function (d) {
return (typeof d === "string") ? 1 : 0;
}));
var nodetext = [{ count: totalOnlineNodes, label: "online" },
{ count: totalOfflineNodes, label: "offline" },
{ count: totalNewNodes, label: "neu" },
{ count: totalLostNodes, label: "verschwunden" }
].filter( function (d) { return d.count > 0 } )
.map( function (d) { return [d.count, d.label].join(" ") } )
.join(", ")
var nodetext = [{count: totalOnlineNodes, label: "online"},
{count: totalOfflineNodes, label: "offline"},
{count: totalNewNodes, label: "neu"},
{count: totalLostNodes, label: "verschwunden"}
].filter(function (d) {
return d.count > 0;
})
.map(function (d) {
return [d.count, d.label].join(" ");
})
.join(", ");
stats.textContent = totalNodes + " Knoten " +
"(" + nodetext + "), " +
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) {
var h2 = document.createElement("h2")
h2.textContent = config.siteName
el.appendChild(h2)
var h2 = document.createElement("h2");
h2.textContent = config.siteName;
el.appendChild(h2);
var p = document.createElement("p")
el.appendChild(p)
stats = document.createTextNode("")
p.appendChild(stats)
p.appendChild(document.createElement("br"))
timestamp = document.createTextNode("")
p.appendChild(timestamp)
}
var p = document.createElement("p");
el.appendChild(p);
stats = document.createTextNode("");
p.appendChild(stats);
p.appendChild(document.createElement("br"));
timestamp = document.createTextNode("");
p.appendChild(timestamp);
};
return self
}
})
return self;
};
});

View file

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

View file

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

View file

@ -1,214 +1,232 @@
define(function () {
define(["helper"], function (helper) {
"use strict";
return function () {
var self = this
var objects = { nodes: {}, links: {} }
var targets = []
var views = {}
var currentView
var currentObject
var running = false
var self = this;
var objects = {nodes: {}, links: {}};
var targets = [];
var views = {};
var currentView;
var currentObject;
var running = false;
function saveState() {
var e = []
var e = [];
if (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))
if (currentView) {
e.push("v:" + currentView);
}
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) {
push = trueDefault(push)
push = helper.trueDefault(push);
targets.forEach( function (t) {
t.resetView()
})
targets.forEach(function (t) {
t.resetView();
});
if (push) {
currentObject = undefined
saveState()
currentObject = undefined;
saveState();
}
}
function gotoNode(d) {
if (!d)
return false
if (!d) {
return false;
}
targets.forEach( function (t) {
t.gotoNode(d)
})
targets.forEach(function (t) {
t.gotoNode(d);
});
return true
return true;
}
function gotoLink(d) {
if (!d)
return false
if (!d) {
return false;
}
targets.forEach( function (t) {
t.gotoLink(d)
})
targets.forEach(function (t) {
t.gotoLink(d);
});
return true
return true;
}
function gotoLocation(d) {
if (!d)
return false
if (!d) {
return false;
}
targets.forEach( function (t) {
if(!t.gotoLocation)console.warn("has no gotoLocation", t)
t.gotoLocation(d)
})
targets.forEach(function (t) {
if (!t.gotoLocation) {
console.warn("has no gotoLocation", t);
}
t.gotoLocation(d);
});
return true
return true;
}
function loadState(s) {
if (!s)
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]]()
if (!s) {
return false;
}
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") {
id = args[1]
id = args[1];
if (id in objects.nodes) {
currentObject = { node: objects.nodes[id] }
gotoNode(objects.nodes[id])
targetSet = true
currentObject = {node: objects.nodes[id]};
gotoNode(objects.nodes[id]);
targetSet = true;
}
}
if (args[0] === "l") {
id = args[1]
id = args[1];
if (id in objects.links) {
currentObject = { link: objects.links[id] }
gotoLink(objects.links[id])
targetSet = true
currentObject = {link: objects.links[id]};
gotoLink(objects.links[id]);
targetSet = true;
}
}
})
});
return targetSet
return targetSet;
}
self.start = function () {
running = true
running = true;
if (!loadState(window.location.hash))
resetView(false)
if (!loadState(window.location.hash)) {
resetView(false);
}
window.onpopstate = function (d) {
if (!loadState(d.state))
resetView(false)
}
if (!loadState(d.state)) {
resetView(false);
}
};
};
self.view = function (d) {
if (d in views) {
views[d]()
views[d]();
if (!currentView || running)
currentView = d
if (!currentView || running) {
currentView = d;
}
if (!running)
return
if (!running) {
return;
}
saveState()
saveState();
if (!currentObject) {
resetView(false)
return
resetView(false);
return;
}
if ("node" in currentObject)
gotoNode(currentObject.node)
if ("node" in currentObject) {
gotoNode(currentObject.node);
}
if ("link" in currentObject)
gotoLink(currentObject.link)
if ("link" in currentObject) {
gotoLink(currentObject.link);
}
}
};
self.node = function (d) {
return function () {
if (gotoNode(d)) {
currentObject = { node: d }
saveState()
currentObject = {node: d};
saveState();
}
return false
}
}
return false;
};
};
self.link = function (d) {
return function () {
if (gotoLink(d)) {
currentObject = { link: d }
saveState()
currentObject = {link: d};
saveState();
}
return false
}
}
return false;
};
};
self.gotoLocation = gotoLocation
self.gotoLocation = gotoLocation;
self.reset = function () {
resetView()
}
resetView();
};
self.addTarget = function (d) {
targets.push(d)
}
targets.push(d);
};
self.removeTarget = function (d) {
targets = targets.filter( function (e) {
return d !== e
})
}
targets = targets.filter(function (e) {
return d !== e;
});
};
self.addView = function (k, d) {
views[k] = d
}
views[k] = d;
};
self.setData = function (data) {
objects.nodes = {}
objects.links = {}
objects.nodes = {};
objects.links = {};
data.nodes.all.forEach( function (d) {
objects.nodes[d.nodeinfo.node_id] = d
})
data.nodes.all.forEach(function (d) {
objects.nodes[d.nodeinfo.node_id] = d;
});
data.graph.links.forEach( function (d) {
objects.links[d.id] = d
})
}
data.graph.links.forEach(function (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) {
var self = this
var self = this;
var sidebar = document.createElement("div")
sidebar.classList.add("sidebar")
el.appendChild(sidebar)
var sidebar = document.createElement("div");
sidebar.classList.add("sidebar");
el.appendChild(sidebar);
var button = document.createElement("button")
sidebar.appendChild(button)
var button = document.createElement("button");
sidebar.appendChild(button);
button.classList.add("sidebarhandle")
button.classList.add("sidebarhandle", "shadow");
button.onclick = function () {
sidebar.classList.toggle("hidden")
}
sidebar.classList.toggle("hidden");
};
var container = document.createElement("div")
container.classList.add("container")
sidebar.appendChild(container)
var container = document.createElement("div");
container.classList.add("container");
sidebar.appendChild(container);
self.getWidth = function () {
if (sidebar.classList.contains("hidden"))
return 0
var small = window.matchMedia("(max-width: 630pt)")
return small.matches ? 0 : sidebar.offsetWidth
if (sidebar.classList.contains("hidden")) {
return 0;
}
var small = window.matchMedia("(max-width: 630pt)");
return small.matches ? 0 : sidebar.offsetWidth;
};
self.add = function (d) {
d.render(container)
}
d.render(container);
};
self.ensureVisible = function () {
sidebar.classList.remove("hidden")
}
sidebar.classList.remove("hidden");
};
self.hide = function () {
container.classList.add("hidden")
}
container.classList.add("hidden");
};
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,70 @@
define(["moment", "virtual-dom"], function (moment, V) {
return function(nodes, field, router, title) {
var self = this
var el, tbody
define(["moment", "virtual-dom", "helper", "moment.de"], function (moment, V, helper) {
"use strict";
return function (nodes, field, router, title) {
var self = this;
var el, tbody;
self.render = function (d) {
el = document.createElement("div")
d.appendChild(el)
}
el = d;
};
self.setData = function (data) {
var list = data.nodes[nodes]
var list = data.nodes[nodes];
if (list.length === 0) {
while (el.firstChild)
el.removeChild(el.firstChild)
while (el.firstChild) {
el.removeChild(el.firstChild);
}
tbody = null
tbody = null;
return
return;
}
if (!tbody) {
var h2 = document.createElement("h2")
h2.textContent = title
el.appendChild(h2)
var h2 = document.createElement("h2");
h2.textContent = title;
el.appendChild(h2);
var table = document.createElement("table")
el.appendChild(table)
var table = document.createElement("table");
table.classList.add("node-list");
el.appendChild(table);
tbody = document.createElement("tbody")
tbody.last = V.h("tbody")
table.appendChild(tbody)
tbody = document.createElement("tbody");
tbody.last = V.h("tbody");
table.appendChild(tbody);
}
var items = list.map( function (d) {
var time = moment(d[field]).from(data.now)
var td1Content = []
var items = list.map(function (d) {
var time = moment(d[field]).from(data.now);
var td0Content = [];
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),
href: "#!n:" + d.nodeinfo.node_id
}, d.nodeinfo.hostname))
}, d.nodeinfo.hostname));
if (has_location(d))
td1Content.push(V.h("span", {className: "icon ion-location"}))
var td1 = V.h("td", td1Content)
var td2 = V.h("td", 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
if (helper.hasLocation(d)) {
td0Content.push(V.h("span", {className: "icon ion-location"}));
}
return self
}
})
var td0 = V.h("td", td0Content);
var td1 = V.h("td", td1Content);
var td2 = V.h("td", time);
return V.h("tr", [td0, 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) {
return function(headings, sortIndex, renderRow) {
var data
var sortReverse = false
var el = document.createElement("table")
var elLast = V.h("table")
"use strict";
return function (headings, sortIndex, renderRow) {
var data;
var sortReverse = false;
var el = document.createElement("table");
var elLast = V.h("table");
function sortTable(i) {
sortReverse = i === sortIndex ? !sortReverse : false
sortIndex = i
sortReverse = i === sortIndex ? !sortReverse : false;
sortIndex = i;
updateView()
updateView();
}
function sortTableHandler(i) {
return function () { sortTable(i) }
return function () {
sortTable(i);
};
}
function updateView() {
var children = []
var children = [];
if (data.length !== 0) {
var th = headings.map(function (d, i) {
var properties = { onclick: sortTableHandler(i),
var properties = {
onclick: sortTableHandler(i),
className: "sort-header"
};
if (sortIndex === i) {
properties.className += sortReverse ? " sort-up" : " sort-down";
}
if (sortIndex === i)
properties.className += sortReverse ? " sort-up" : " sort-down"
return V.h("th", properties, d.name);
});
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()
children.push(V.h("thead", V.h("tr", th)))
children.push(V.h("tbody", links.map(renderRow)))
if (headings[sortIndex].reverse ? !sortReverse : sortReverse) {
links = links.reverse();
}
var elNew = V.h("table", children)
el = V.patch(el, V.diff(elLast, elNew))
elLast = elNew
children.push(V.h("thead", V.h("tr", th)));
children.push(V.h("tbody", links.map(renderRow)));
}
var elNew = V.h("table", children);
el = V.patch(el, V.diff(elLast, elNew));
elLast = elNew;
}
this.setData = function (d) {
data = d
updateView()
}
data = d;
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 () {
var self = this
var self = this;
var tabs = document.createElement("ul")
tabs.classList.add("tabs")
var tabs = document.createElement("ul");
tabs.classList.add("tabs");
var container = document.createElement("div")
var container = document.createElement("div");
function gotoTab(li) {
for (var i = 0; i < tabs.children.length; i++)
tabs.children[i].classList.remove("visible")
for (var i = 0; i < tabs.children.length; i++) {
tabs.children[i].classList.remove("visible");
}
while (container.firstChild)
container.removeChild(container.firstChild)
while (container.firstChild) {
container.removeChild(container.firstChild);
}
li.classList.add("visible")
li.classList.add("visible");
var tab = document.createElement("div")
tab.classList.add("tab")
container.appendChild(tab)
li.child.render(tab)
var tab = document.createElement("div");
tab.classList.add("tab");
container.appendChild(tab);
li.child.render(tab);
}
function switchTab() {
gotoTab(this)
gotoTab(this);
return false
return false;
}
self.add = function (title, d) {
var li = document.createElement("li")
li.textContent = title
li.onclick = switchTab
li.child = d
tabs.appendChild(li)
var li = document.createElement("li");
li.textContent = title;
li.onclick = switchTab;
li.child = d;
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")) {
anyVisible = true
break
anyVisible = true;
break;
}
}
if (!anyVisible)
gotoTab(li)
if (!anyVisible) {
gotoTab(li);
}
};
self.render = function (el) {
el.appendChild(tabs)
el.appendChild(container)
}
el.appendChild(tabs);
el.appendChild(container);
};
return self
}
})
return self;
};
});

View file

@ -1,35 +1,40 @@
define(function () {
"use strict";
return function (config) {
function setTitle(d) {
var title = [config.siteName]
var title = [config.siteName];
if (d !== undefined)
title.push(d)
if (d !== undefined) {
title.push(d);
}
document.title = title.join(": ")
document.title = title.join(": ");
}
this.resetView = function () {
setTitle()
}
setTitle();
};
this.gotoNode = function (d) {
if (d)
setTitle(d.nodeinfo.hostname)
if (d) {
setTitle(d.nodeinfo.hostname);
}
};
this.gotoLink = function (d) {
if (d)
setTitle((d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + " " + d.target.node.nodeinfo.hostname)
if (d) {
setTitle((d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + " " + d.target.node.nodeinfo.hostname);
}
};
this.gotoLocation = function() {
this.gotoLocation = function () {
//ignore
}
};
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",
"license": "AGPL-3.0",
"repository": {
"type": "git",
"url": "https://github.com/hopglass/hopglass.git"
},
"bugs": {
"url": "https://github.com/hopglass/hopglass/issues"
},
"scripts": {
"test": "node -e \"require('grunt').cli()\" '' clean lint"
},
"devDependencies": {
"autoprefixer": "^6.3.3",
"grunt": "^0.4.5",
"grunt-check-dependencies": "^0.6.0",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-connect": "^0.8.0",
"grunt-contrib-copy": "^0.5.0",
"grunt-contrib-cssmin": "^0.12.2",
"grunt-contrib-requirejs": "^0.4.4",
"grunt-sass": "^1.1.0",
"grunt-postcss": "^0.7.2",
"grunt-contrib-uglify": "^0.5.1",
"grunt-contrib-watch": "^0.6.1",
"grunt-eslint": "^10.0.0",
"grunt-bower-install-simple": "^1.1.2",
"grunt-git-describe": "^2.3.2"
"autoprefixer": "^6.3.6",
"eslint": "^2.11.0",
"eslint-config-defaults": "^9.0.0",
"grunt": "^1.0.1",
"grunt-bower-install-simple": "^1.2.3",
"grunt-check-dependencies": "^0.12.0",
"grunt-contrib-clean": "^1.0.0",
"grunt-contrib-connect": "^1.0.2",
"grunt-contrib-copy": "^1.0.0",
"grunt-contrib-requirejs": "^1.0.0",
"grunt-contrib-uglify": "^1.0.1",
"grunt-contrib-watch": "^1.0.0",
"grunt-eslint": "^18.1.0",
"grunt-inline": "^0.3.6",
"grunt-postcss": "^0.8.0",
"grunt-sass": "^1.2.0",
"grunt-sass-lint": "^0.2.0"
},
"eslintConfig": {
"env": {
@ -26,24 +36,6 @@
"amd": true,
"es6": 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 {
font-size: 0.67em;
}
.hide {
display: none;
}

View file

@ -1,53 +0,0 @@
.filters {
margin: 0;
display: flex;
flex-wrap: wrap;
font-family: Roboto, sans-serif;
font-size: 0.83em;
font-weight: bold;
padding: 0 6pt 6pt !important;
li {
border-radius: 20pt;
display: flex;
padding: 0 0 0 8pt;
margin: 3pt;
align-items: center;
background: #009ee0;
color: rgba(255, 255, 255, 0.8);
label {
cursor: pointer;
strong {
color: rgba(255, 255, 255, 1);
}
}
&.not {
background: #dc0067;
}
button {
box-shadow: none;
margin: 2pt;
padding: 0;
width: 18pt;
height: 18pt;
background: rgba(255, 255, 255, 0.0);
font-size: 12pt;
vertical-align: middle;
color: rgba(255, 255, 255, 0.8);
&:hover {
box-shadow: none !important;
color: #dc0067;
background: rgba(255, 255, 255, 0.9);
}
&:active {
box-shadow: none;
}
}
}
}

View file

@ -1,23 +0,0 @@
.legend .symbol {
width: 1em;
height: 1em;
border-radius: 50%;
display: inline-block;
vertical-align: -5%;
}
.legend-new .symbol {
background-color: #93E929;
}
.legend-online .symbol {
background-color: #1566A9;
}
.legend-offline .symbol {
background-color: #D43E2A;
}
.legend-online, .legend-offline {
margin-left: 1em;
}

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

@ -1,59 +0,0 @@
.stroke-first {
paint-order: stroke;
}
.pick-coordinates {
cursor: crosshair;
}
.map {
width: 100%;
height: 100%;
.node-alert {
-webkit-animation: blink 2s linear;
-webkit-animation-iteration-count: infinite;
animation: blink 2s linear;
animation-iteration-count: infinite;
}
.leaflet-top button.leaflet-control {
margin-top: $buttondistance;
}
.leaflet-bottom button.leaflet-control {
margin-bottom: $buttondistance;
}
.leaflet-left button.leaflet-control {
margin-left: $buttondistance;
}
.leaflet-right button.leaflet-control {
margin-right: $buttondistance;
}
}
@-webkit-keyframes blink {
0% {
opacity: 1.0;
}
80% {
opacity: 1.0;
}
90% {
opacity: 0.0;
}
}
@keyframes blink {
0% {
opacity: 1.0;
}
80% {
opacity: 1.0;
}
90% {
opacity: 0.0;
}
}

View file

@ -1,15 +0,0 @@
/* Original is in LESS and can be found here: https://gist.github.com/gefangenimnetz/3ef3e18364edf105c5af */
@mixin shadow($level:1) {
@if $level == 1 {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
} @else if $level == 2 {
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
} @else if $level == 3 {
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
} @else if $level == 4 {
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
} @else if $level == 5 {
box-shadow: 0 19px 38px rgba(0, 0, 0, 0.30), 0 15px 12px rgba(0, 0, 0, 0.22);
}
}

10
scss/custom/_custom.scss Normal file
View file

@ -0,0 +1,10 @@
// Example of overwriting variables. Take a look at modules/variables
// .node-links {
// color: $color-primary;
// }
// You can also include additional files for style example https://github.com/ffrgb/meshviewer/tree/ffrgb-config/scss/custom
// Include syntax: @include "name" -> Filename: _name.scss
// SCSS supports css with a lot of additional features like variables or mixins.
// Autoprefixer runs in postcss, no need to add browser-prefixes like -webkit, -moz or -ms

View file

@ -0,0 +1,4 @@
// Example of overwriting variables. Take a look at modules/variables
//$color-black: #fff;
//$color-white: invert($color-white);
//$color-primary: invert($color-primary);

View file

@ -1,493 +1,26 @@
@import '_reset';
@import '_shadow';
@import '_base';
@import '_leaflet';
@import '_leaflet.label';
@import '_filters';
$minscreenwidth: 630pt;
$sidebarwidth: 420pt;
$sidebarwidthsmall: 320pt;
$buttondistance: 12pt;
@import '_sidebar';
@import '_map';
@import '_forcegraph';
@import '_legend';
.content {
position: fixed;
width: 100%;
height: 100vh;
.buttons {
direction: rtl;
unicode-bidi: bidi-override;
z-index: 100;
position: absolute;
top: $buttondistance;
right: $buttondistance;
button {
margin-left: $buttondistance;
}
}
}
.tabs, header {
background: rgba(0, 0, 0, 0.02);
}
.tabs {
padding: 1em 0 0 !important;
margin: 0;
list-style: none;
display: flex;
font-family: Roboto, sans-serif;
@include shadow(1);
}
.tabs li {
flex: 1 1 auto;
text-transform: uppercase;
text-align: center;
padding: 0.5em 0.5em 1em;
cursor: pointer;
color: rgba(0, 0, 0, 0.5);
}
.tabs li:hover {
color: #dc0067;
}
.tabs .visible {
border-bottom: 2pt solid #dc0067;
color: #dc0067;
}
body {
margin: 0;
padding: 0;
font-family: 'Roboto Slab', serif;
font-size: 11pt;
color: #333;
}
th.sort-header::selection {
background: transparent;
}
th.sort-header {
cursor: pointer;
}
table th.sort-header:after {
font-family: "ionicons";
padding-left: 0.25em;
content: '\f10d';
visibility: hidden;
}
table th.sort-header:hover:after {
visibility: visible;
}
table th.sort-up:after, table th.sort-down:after, table th.sort-down:hover:after {
visibility: visible;
opacity: 0.4;
}
table th.sort-up:after {
content: '\f104';
}
table.attributes th {
text-align: left;
font-weight: bold;
vertical-align: top;
padding-right: 1em;
white-space: nowrap;
line-height: 1.41em;
}
table.attributes td {
text-align: left !important;
width: 100%;
line-height: 1.41em;
}
table.attributes tr.routerpic {
max-height: 128px;
max-width: 128px;
min-width: 128px;
min-height: 128px;
/*height: 128px;*/
/*background-color: green;*/
}
table.attributes tr.routerpic td {
font-weight: bold;
/*background-color: red;*/
font-size: large;
vertical-align:bottom;
}
table.attributes tr.routerpic th {
font-weight: bold;
/*background-color: red;*/
font-size: large;
vertical-align:bottom;
}
.nodenamesidebar {
position: relative;
font-weight: bold;
/*background-color: red;*/
font-size: large;
vertical-align:bottom;
bottom: 0px;
}
.sidebar {
.infobox, .container {
@include shadow(2);
background: rgba(255, 255, 255, 0.97);
border-radius: 2px;
}
.container.hidden {
display: none;
}
.container table.hidden {
display: none;
}
p {
line-height: 1.67em;
}
}
.infobox .clients {
font-family: "ionicons";
color: #1566A9;
word-spacing: -0.2em;
white-space: normal;
}
.infobox .clientsMesh {
font-family: "ionicons";
color: #dbdbdb;
word-spacing: -0.2em;
white-space: normal;
}
.infobox {
position: relative;
padding: 0.25em 0;
margin-bottom: $buttondistance;
img {
max-width: 100%;
}
input[type="text"], textarea {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
border-radius: 5px;
border: none;
line-height: 1.67em;
vertical-align: bottom;
margin-right: 0.7em;
padding: 3px 6px;
font-family: monospace;
width: 70%;
max-width: 500px;
min-height: 42px;
font-size: 1.15em;
}
textarea {
resize: vertical;
overflow: auto;
max-height: 300px;
}
}
button {
-webkit-tap-highlight-color: transparent;
font-family: "ionicons";
@include shadow(1);
border-radius: 0.9em;
background: rgba(255, 255, 255, 0.7);
color: #333;
border: none;
cursor: pointer;
height: 1.8em;
width: 1.8em;
font-size: 20pt;
transition: box-shadow 0.5s, color 0.5s;
outline: none;
}
button.active {
color: #dc0067 !important;
}
button:hover {
background: white;
color: #dc0067;
@include shadow(2);
}
button:active {
box-shadow: inset 0 5px 20px rgba(0, 0, 0, 0.19), inset 0 3px 6px rgba(0, 0, 0, 0.23);
}
button::-moz-focus-inner {
border: 0;
}
button.close {
width: auto;
height: auto;
font-size: 20pt;
float: right;
margin-right: $buttondistance;
margin-top: $buttondistance;
box-shadow: none;
background: transparent;
border-radius: 0;
color: rgba(0, 0, 0, 0.5);
font-family: "ionicons";
position: absolute;
right: 0;
&:hover {
color: #dc0067;
}
&:after {
content: '\f2d7';
}
}
.sidebar h2, .sidebar h3 {
padding-left: $buttondistance;
padding-right: $buttondistance;
}
.sidebar {
p, pre, ul, h4 {
padding: 0 $buttondistance 1em;
}
table {
padding: 0 $buttondistance;
}
img {
max-width: 100%;
}
}
table {
border-spacing: 0 0.5em;
td, th {
line-height: 1.41em;
}
}
.sidebar table {
border-collapse: separate;
}
.sidebar table th {
font-weight: bold;
}
.sidebar table:not(.node-links) th:first-child,
.sidebar table:not(.node-links) td:first-child {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 50%;
}
.sidebar table.node-links th:nth-child(2),
.sidebar table.node-links td:nth-child(2) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 45%;
}
.sidebar table.node-links th:first-child,
.sidebar table.node-links td:first-child {
width: 1.5em;
}
.sidebarhandle {
position: fixed;
left: $sidebarwidth + 2 * $buttondistance;
top: $buttondistance;
z-index: 10;
transition: left 0.5s, box-shadow 0.5s, color 0.5s, transform 0.5s;
}
.sidebarhandle:after {
padding-right: 0.125em;
content: "\f124";
}
.sidebar.hidden .sidebarhandle {
transform: scale(-1, 1);
left: $buttondistance;
}
.online {
color: #558020 !important;
}
.offline {
color: #D43E2A !important;
}
.unseen {
color: #D89100 !important;
}
.sidebar {
z-index: 5;
width: $sidebarwidth;
box-sizing: border-box;
position: absolute;
top: $buttondistance;
left: $buttondistance;
padding-bottom: $buttondistance;
transition: left 0.5s;
}
.sidebar.hidden {
left: -$sidebarwidth - $buttondistance;
}
.sidebar .icon {
padding: 0 0.25em;
}
.sidebar table {
width: 100%;
table-layout: fixed;
}
.sidebar table th {
text-align: left;
}
.sidebar table:not(.node-links) td:not(:first-child),
.sidebar table:not(.node-links) th:not(:first-child) {
text-align: right;
}
.sidebar table.node-links td:not(:nth-child(-n+2)),
.sidebar table.node-links th:not(:nth-child(-n+2)) {
text-align: right;
}
.sidebar a {
color: #1566A9;
}
.bar {
display: block;
height: 1.4em;
background: rgba(85, 128, 32, 0.5);
position: relative;
span {
display: inline-block;
height: 1.4em;
background: rgba(85, 128, 32, 0.8);
}
label {
font-weight: bold;
white-space: nowrap;
color: white;
position: absolute;
right: 0.5em;
top: 0.1em;
}
}
.proportion th {
font-weight: normal !important;
text-align: right !important;
font-size: 0.95em;
padding-right: 0.71em;
}
.proportion td {
text-align: left !important;
width: 100%;
}
.proportion td, .proportion th {
white-space: nowrap;
}
.proportion span {
display: inline-block;
background: black;
padding: 0.25em 0.5em;
font-weight: bold;
min-width: 1.5em;
box-sizing: border-box;
}
@media screen and (max-width: 80em) {
.sidebar {
font-size: 0.8em;
top: 0;
left: 0;
margin: 0;
width: $sidebarwidthsmall;
min-height: 100vh;
@include shadow(2);
background: white;
.sidebarhandle {
left: $sidebarwidthsmall + $buttondistance;
}
.container, .infobox {
margin: 0;
box-shadow: none;
border-radius: 0;
}
}
}
@media screen and (max-width: $minscreenwidth) {
.sidebarhandle {
display: none;
}
.content {
position: relative;
width: auto;
height: 60vh;
}
.sidebar {
position: static;
margin: 0 !important;
width: auto;
height: auto;
min-height: 0;
}
.sidebar.hidden {
width: auto;
}
}
// Set variables
@import 'modules/variables';
@import 'custom/variables';
// Add modules
@import 'mixins/shadow';
@import 'mixins/icon-mixin';
@import 'modules/reset';
@import 'modules/base';
@import 'modules/icon';
@import 'leaflet';
@import 'leaflet.label';
@import 'modules/leaflet-layer';
@import 'modules/table';
@import 'modules/filter';
@import 'modules/sidebar';
@import 'modules/map';
@import 'modules/forcegraph';
@import 'modules/legend';
@import 'modules/proportion';
@import 'modules/tabs';
@import 'modules/node';
@import 'modules/infobox';
@import 'modules/button';
// Make adjustments in custom scss
@import 'custom/custom';

View file

@ -0,0 +1 @@
../../assets/icons/_icon-mixin.scss

14
scss/mixins/_shadow.scss Normal file
View file

@ -0,0 +1,14 @@
// Original is in LESS and can be found here: https://gist.github.com/gefangenimnetz/3ef3e18364edf105c5af
@mixin shadow($level: 1) {
@if $level == 1 {
box-shadow: 0 1px 3px rgba($color-black, .12), 0 1px 2px rgba($color-black, .24);
} @else if $level == 2 {
box-shadow: 0 3px 6px rgba($color-black, .16), 0 3px 6px rgba($color-black, .23);
} @else if $level == 3 {
box-shadow: 0 10px 20px rgba($color-black, .19), 0 6px 6px rgba($color-black, .23);
} @else if $level == 4 {
box-shadow: 0 14px 28px rgba($color-black, .25), 0 10px 10px rgba($color-black, .22);
} @else if $level == 5 {
box-shadow: 0 19px 38px rgba($color-black, .3), 0 15px 12px rgba($color-black, .22);
}
}

56
scss/modules/_base.scss Normal file
View file

@ -0,0 +1,56 @@
body {
background: $color-white;
color: $color-black;
font-family: $font-family;
font-size: $font-size;
overflow-y: scroll;
}
header {
background: rgba($color-black, .02);
}
h1, h2, h3, h4, h5, h6 {
font-weight: bold;
}
h1 {
font-size: 2em;
padding: .67em 0;
}
h2 {
font-size: 1.5em;
padding: .83em 0;
}
h3 {
font-size: 1.17em;
padding: 1em 0;
}
h2, h3 {
padding-left: $buttondistance;
padding-right: $buttondistance;
}
p, pre, ul, h4 {
padding: 0 $buttondistance 1em;
}
img {
max-width: 100%;
}
a {
color: $color-online;
text-decoration: none;
}
p {
line-height: 1.67em;
}
strong {
font-weight: bold;
}

47
scss/modules/_button.scss Normal file
View file

@ -0,0 +1,47 @@
button {
background: $color-white;
border: 0;
border-radius: .9em;
color: $color-black;
cursor: pointer;
font-family: $font-family-icons;
font-size: 20pt;
height: 1.8em;
opacity: .7;
outline: none;
transition: box-shadow .5s, color .5s;
width: 1.8em;
&.active {
color: $color-primary;
}
&:hover {
background: $color-white;
color: $color-primary;
}
&.shadow {
@include shadow(1);
&:hover {
@include shadow(2);
}
&:active {
box-shadow: inset 0 5px 20px rgba($color-black, .19), inset 0 3px 6px rgba($color-black, .23);
}
}
&.close {
background: none;
border-radius: 0;
box-shadow: none;
color: rgba($color-black, .5);
float: right;
font-size: 20pt;
height: auto;
margin: $buttondistance;
width: auto;
}
}

40
scss/modules/_filter.scss Normal file
View file

@ -0,0 +1,40 @@
.filters {
display: flex;
flex-wrap: wrap;
font-size: .83em;
padding: 0 6pt 6pt;
li {
align-items: center;
background: transparent;
border: 1px solid $color-primary;
color: $color-primary;
display: flex;
margin: 3pt;
padding: 0 0 0 8pt;
label {
cursor: pointer;
}
button {
background: none;
color: $color-gray-light;
font-size: 12pt;
height: 18pt;
margin: 2pt;
width: 18pt;
&:hover {
color: $color-primary;
}
}
&.not {
label {
color: $color-primary;
text-decoration: line-through;
}
}
}
}

View file

@ -1,7 +1,7 @@
.graph {
background: $color-gray-dark;
height: 100%;
width: 100%;
background: #2B2B2B;
canvas {
display: block;

View file

@ -0,0 +1,27 @@
.infobox {
.clients {
color: $color-online;
font-family: $font-family-icons;
}
input, textarea {
border: 1px solid $color-gray-light;
font-family: $font-family-monospace;
font-size: 1.15em;
line-height: 1.67em;
margin-right: .7em;
max-width: 500px;
min-height: 42px;
padding: 3px 6px;
vertical-align: bottom;
width: calc(100% - 80px);
}
textarea {
font-size: .8em;
height: 100px;
max-height: 300px;
overflow: auto;
resize: vertical;
}
}

View file

@ -0,0 +1,34 @@
.leaflet-control-layers {
background: none;
border-radius: 0;
box-shadow: none;
}
.leaflet-control-layers-toggle {
background: none;
&::before {
color: $color-primary;
content: '\f229';
display: inline-block;
font-family: $font-family-icons;
font-size: 2.3rem;
line-height: 1;
speak: none;
text-rendering: auto;
}
}
.leaflet-control-layers-expanded {
padding: 0;
}
.leaflet-control-layers-list {
background: rgba($color-white, .9);
color: $color-gray-dark;
padding: 10px;
label {
cursor: pointer;
}
}

32
scss/modules/_legend.scss Normal file
View file

@ -0,0 +1,32 @@
.legend {
.symbol {
border-radius: 50%;
display: inline-block;
height: 1em;
vertical-align: -5%;
width: 1em;
}
}
// Dot looks compared to thin font a bit darker - lighten it 10%
.legend-new {
.symbol {
background-color: lighten($color-new, 10%);
}
}
.legend-online {
.symbol {
background-color: lighten($color-online, 10%);
}
}
.legend-offline {
.symbol {
background-color: lighten($color-offline, 10%);
}
}
.legend-online, .legend-offline {
margin-left: 1em;
}

41
scss/modules/_map.scss Normal file
View file

@ -0,0 +1,41 @@
.content {
height: 100vh;
position: fixed;
width: 100%;
.buttons {
direction: rtl;
position: absolute;
right: $buttondistance;
top: $buttondistance;
unicode-bidi: bidi-override;
z-index: 100;
button {
margin-left: $buttondistance;
}
@media screen and (max-width: $minscreenwidth) {
right: 0;
transform: scale(.8);
}
}
@media screen and (max-width: $minscreenwidth) {
height: 60vh;
position: relative;
width: auto;
}
}
.stroke-first {
paint-order: stroke;
}
.pick-coordinates {
cursor: crosshair;
}
.map {
height: 100%;
width: 100%;
}

21
scss/modules/_node.scss Normal file
View file

@ -0,0 +1,21 @@
.bar {
background: rgba($color-new, .5);
display: block;
height: 1.4em;
position: relative;
span {
background: rgba($color-new, .8);
display: inline-block;
height: 1.4em;
}
label {
color: $color-white;
font-weight: bold;
position: absolute;
right: .5em;
top: .1em;
white-space: nowrap;
}
}

View file

@ -0,0 +1,20 @@
.proportion {
th {
font-size: .95em;
font-weight: normal;
padding-right: .71em;
text-align: right;
}
td {
text-align: left;
}
span {
box-sizing: border-box;
display: inline-block;
font-weight: bold;
min-width: 1.5em;
padding: .25em .5em;
}
}

View file

@ -1,7 +1,5 @@
/**
* Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
* http://cssreset.com
*/
// Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
// http://cssreset.com
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
@ -12,23 +10,17 @@ dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
figure, figcaption, footer, header,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
border: 0;
font: inherit;
font-size: 100%;
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
@ -41,12 +33,6 @@ blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;

127
scss/modules/_sidebar.scss Normal file
View file

@ -0,0 +1,127 @@
.sidebar {
box-sizing: border-box;
position: absolute;
transition: left .5s;
width: $sidebarwidth;
z-index: 5;
&.hidden {
left: -$sidebarwidth - $buttondistance;
.sidebarhandle {
left: $buttondistance;
transform: scale(-1, 1);
}
@media screen and (max-width: $minscreenwidth) {
width: auto;
}
}
.node-list, .node-links {
th, td {
&:first-child {
width: 25px;
}
&:nth-child(2) {
overflow: hidden;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
width: 50%;
}
}
}
.node-links {
th, td {
&:first-child {
width: 50px;
}
}
}
.link-list {
th, td {
&:nth-child(1) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 66%;
}
}
}
.infobox, .container {
@include shadow(2);
background: rgba($color-white, .97);
min-height: 100vh;
overflow-y: visible;
}
.container {
&.hidden {
display: none;
}
}
@media screen and (max-width: 80em) {
@include shadow(2);
background: $color-white;
font-size: .8em;
margin: 0;
width: $sidebarwidthsmall;
.sidebarhandle {
left: $sidebarwidthsmall + $buttondistance;
}
.container, .infobox {
border-radius: 0;
box-shadow: none;
margin: 0;
}
}
@media screen and (max-width: $minscreenwidth) {
height: auto;
min-height: 0;
position: static;
width: auto;
.sidebarhandle {
display: none;
}
.content {
height: 60vh;
position: relative;
width: auto;
}
}
}
.sidebarhandle {
left: $sidebarwidth + 2 * $buttondistance;
position: fixed;
top: $buttondistance;
transition: left .5s, box-shadow .5s, color .5s, transform .5s;
z-index: 10;
&::after {
content: '\f124';
padding-right: .125em;
}
}
.online {
color: $color-new;
}
.offline {
color: $color-offline;
}
.unseen {
color: #d89100;
}

76
scss/modules/_table.scss Normal file
View file

@ -0,0 +1,76 @@
table {
border-collapse: separate;
border-spacing: 0 .5em;
padding: 0 $buttondistance;
width: 100%;
&.attributes {
line-height: 1.41em;
th {
font-weight: bold;
padding-right: 1em;
text-align: left;
vertical-align: top;
white-space: nowrap;
}
td {
text-align: left;
width: 100%;
}
}
}
td, th {
line-height: 1.41em;
text-align: right;
&:first-child {
text-align: left;
}
}
th {
font-weight: bold;
&.sort-header {
cursor: pointer;
&::selection {
background: transparent;
}
&::after {
content: '\f10d';
font-family: $font-family-icons;
padding-left: .25em;
visibility: hidden;
}
&:hover {
&::after {
visibility: visible;
}
}
}
&.sort-up {
&::after {
content: '\f104';
}
}
&.sort-up, &.sort-down {
&:after {
opacity: .4;
visibility: visible;
}
}
}
.tab {
table {
table-layout: fixed;
}
}

27
scss/modules/_tabs.scss Normal file
View file

@ -0,0 +1,27 @@
.tabs {
@include shadow(1);
background: rgba($color-black, .02);
display: flex;
font-family: $font-family;
list-style: none;
margin: 0;
padding: 1em 0 0;
li {
color: rgba($color-black, .5);
cursor: pointer;
flex: 1 1 auto;
padding: .5em .5em 1em;
text-align: center;
text-transform: uppercase;
&:hover {
color: $color-black;
}
}
.visible {
border-bottom: 2pt solid $color-primary;
color: $color-primary;
}
}

View file

@ -0,0 +1,22 @@
$color-white: #fff !default;
$color-black: #000 !default;
$color-gray-light: darken($color-white, 30%) !default;
$color-gray-dark: lighten($color-black, 20%) !default;
$color-primary: #dc0067 !default;
$color-new: #459c18 !default;
$color-online: #1566a9 !default;
$color-offline: #cf3e2a !default;
$font-family: 'Roboto Slab', serif !default;
$font-family-secondary: Roboto, sans-serif !default;
$font-family-icons: ionicons !default;
$font-family-monospace: monospace !default;
$font-size: 11pt !default;
$minscreenwidth: 630pt !default;
$sidebarwidth: 420pt !default;
$sidebarwidthsmall: 320pt !default;
$buttondistance: 12pt !default;

1
scss/modules/icon.scss Symbolic link
View file

@ -0,0 +1 @@
../../assets/icons/icon.scss

View file

@ -1 +0,0 @@
build/style.css

View file

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

View file

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

View file

@ -1,4 +1,6 @@
module.exports = function (grunt) {
"use strict";
grunt.config.merge({
checkDependencies: {
options: {
@ -11,19 +13,13 @@ module.exports = function (grunt) {
},
npm: {}
},
eslint: {
sasslint: {
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
}
configFile: '.sass-lint.yml'
},
target: ['scss/main.scss', 'scss/*/*.scss']
},
eslint: {
sources: {
src: ["app.js", "!Gruntfile.js", "lib/**/*.js"]
},
@ -31,8 +27,9 @@ module.exports = function (grunt) {
src: ["Gruntfile.js", "tasks/*.js"]
}
}
})
});
grunt.loadNpmTasks("grunt-check-dependencies")
grunt.loadNpmTasks("grunt-eslint")
}
grunt.loadNpmTasks("grunt-check-dependencies");
grunt.loadNpmTasks('grunt-sass-lint');
grunt.loadNpmTasks("grunt-eslint");
};