Compare commits
32 commits
master
...
rgb-sass-w
Author | SHA1 | Date | |
---|---|---|---|
ed06ff6b09 | |||
6175d6bb7a | |||
931ca67fc2 | |||
340c1d9a53 | |||
ae59f75594 | |||
d57ce0052a | |||
894f32da54 | |||
a8c19ba44c | |||
a627e2febc | |||
e32fe6648d | |||
a2ab0dce4f | |||
90d2c3e444 | |||
a56876a10e | |||
2d5e35324a | |||
28de7c5403 | |||
2630b6197d | |||
62d64c717c | |||
5691406d09 | |||
e80f7d899d | |||
292c5c020c | |||
58279ac213 | |||
6f4b9d6701 | |||
e9e393e105 | |||
5921a5f355 | |||
c9458ffcae | |||
c7b58d5743 | |||
7389547e3e | |||
6aec0b21a8 | |||
04eb8f039b | |||
0abd4130cf | |||
418b630e02 | |||
59ba0ba29e |
|
@ -11,6 +11,6 @@ trim_trailing_whitespace = true
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[*.{js,html,scss,json}]
|
[*.{js,html,scss,json,yml,md}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,3 +3,5 @@ node_modules/
|
||||||
build/
|
build/
|
||||||
.sass-cache/
|
.sass-cache/
|
||||||
config.json
|
config.json
|
||||||
|
.idea/
|
||||||
|
.eslintrc
|
||||||
|
|
3
.sass-lint.yml
Normal file
3
.sass-lint.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
rules:
|
||||||
|
no-color-literals: 0
|
||||||
|
single-line-per-selector: 0
|
27
Gruntfile.js
27
Gruntfile.js
|
@ -1,24 +1,11 @@
|
||||||
module.exports = function (grunt) {
|
module.exports = function (grunt) {
|
||||||
grunt.loadNpmTasks("grunt-git-describe")
|
"use strict";
|
||||||
|
|
||||||
grunt.initConfig({
|
grunt.loadTasks("tasks");
|
||||||
"git-describe": {
|
|
||||||
options: {},
|
|
||||||
default: {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
grunt.registerTask("saveRevision", function() {
|
grunt.registerTask("default", ["bower-install-simple", "lint", "copy", "sass", "postcss", "requirejs:default", "inline"]);
|
||||||
grunt.event.once("git-describe", function (rev) {
|
grunt.registerTask("lint", ["sasslint", "eslint"]);
|
||||||
grunt.option("gitRevision", rev)
|
grunt.registerTask("dev", ["bower-install-simple", "lint", "copy", "sass", "requirejs:dev"]);
|
||||||
})
|
grunt.registerTask("serve", ["dev", "connect:server", "watch"]);
|
||||||
grunt.task.run("git-describe")
|
};
|
||||||
})
|
|
||||||
|
|
||||||
grunt.loadTasks("tasks")
|
|
||||||
|
|
||||||
grunt.registerTask("default", ["bower-install-simple", "lint", "saveRevision", "copy", "sass", "postcss", "requirejs"])
|
|
||||||
grunt.registerTask("lint", ["eslint"])
|
|
||||||
grunt.registerTask("dev", ["default", "connect:server", "watch"])
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -46,10 +46,19 @@ Just run the following command from the hopglass directory:
|
||||||
|
|
||||||
This will generate `build/` containing all required files.
|
This will generate `build/` containing all required files.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Use `grunt serve` for development.
|
||||||
|
|
||||||
# Configure
|
# Configure
|
||||||
|
|
||||||
Copy `config.json.example` to `build/config.json` and change it to match your community.
|
Copy `config.json.example` to `build/config.json` and change it to match your community.
|
||||||
|
|
||||||
|
|
||||||
|
## Customize style
|
||||||
|
|
||||||
|
Start your development and edit files in `scss/custom/`. Additional information in comments.
|
||||||
|
|
||||||
## dataPath (string/array)
|
## 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.
|
`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
30
app.js
|
@ -1,3 +1,5 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
require.config({
|
require.config({
|
||||||
baseUrl: "lib",
|
baseUrl: "lib",
|
||||||
paths: {
|
paths: {
|
||||||
|
@ -5,32 +7,24 @@ require.config({
|
||||||
"leaflet.label": "../bower_components/Leaflet.label/dist/leaflet.label",
|
"leaflet.label": "../bower_components/Leaflet.label/dist/leaflet.label",
|
||||||
"leaflet.providers": "../bower_components/leaflet-providers/leaflet-providers",
|
"leaflet.providers": "../bower_components/leaflet-providers/leaflet-providers",
|
||||||
"chroma-js": "../bower_components/chroma-js/chroma.min",
|
"chroma-js": "../bower_components/chroma-js/chroma.min",
|
||||||
"moment": "../bower_components/moment/min/moment-with-locales.min",
|
"moment": "../bower_components/moment/min/moment.min",
|
||||||
"tablesort": "../bower_components/tablesort/tablesort.min",
|
"moment.de": "../bower_components/moment/locale/de",
|
||||||
"tablesort.numeric": "../bower_components/tablesort/src/sorts/tablesort.numeric",
|
"tablesort": "../bower_components/tablesort/src/tablesort",
|
||||||
"d3": "../bower_components/d3/d3.min",
|
"d3": "../bower_components/d3/d3.min",
|
||||||
"numeral": "../bower_components/numeraljs/min/numeral.min",
|
|
||||||
"numeral-intl": "../bower_components/numeraljs/min/languages.min",
|
|
||||||
"virtual-dom": "../bower_components/virtual-dom/dist/virtual-dom",
|
"virtual-dom": "../bower_components/virtual-dom/dist/virtual-dom",
|
||||||
"rbush": "../bower_components/rbush/rbush",
|
"rbush": "../bower_components/rbush/rbush",
|
||||||
"helper": "../helper",
|
"helper": "utils/helper"
|
||||||
"jshashes": "../bower_components/jshashes/hashes"
|
|
||||||
},
|
},
|
||||||
shim: {
|
shim: {
|
||||||
"leaflet.label": ["leaflet"],
|
"leaflet.label": ["leaflet"],
|
||||||
"leaflet.providers": ["leaflet"],
|
"leaflet.providers": ["leaflet"],
|
||||||
|
"moment.de": ["moment"],
|
||||||
"tablesort": {
|
"tablesort": {
|
||||||
exports: "Tablesort"
|
exports: "Tablesort"
|
||||||
},
|
}
|
||||||
"numeral-intl": {
|
|
||||||
deps: ["numeral"],
|
|
||||||
exports: "numeral"
|
|
||||||
},
|
|
||||||
"tablesort.numeric": ["tablesort"],
|
|
||||||
"helper": ["numeral-intl"]
|
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
require(["main", "helper"], function (main) {
|
require(["main", "helper"], function (main, helper) {
|
||||||
getJSON("config.json").then(main)
|
helper.getJSON("config.json").then(main);
|
||||||
})
|
});
|
||||||
|
|
7
assets/icons/_icon-mixin.scss
Normal file
7
assets/icons/_icon-mixin.scss
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
@mixin icon($name, $code, $prefix: 'ion-') {
|
||||||
|
.#{$prefix}#{$name} {
|
||||||
|
&:before {
|
||||||
|
content: '#{$code}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
assets/icons/fonts/icon.ttf
Normal file
BIN
assets/icons/fonts/icon.ttf
Normal file
Binary file not shown.
BIN
assets/icons/fonts/icon.woff
Normal file
BIN
assets/icons/fonts/icon.woff
Normal file
Binary file not shown.
BIN
assets/icons/fonts/icon.woff2
Normal file
BIN
assets/icons/fonts/icon.woff2
Normal file
Binary file not shown.
45
assets/icons/icon.scss
Normal file
45
assets/icons/icon.scss
Normal 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');
|
30
bower.json
30
bower.json
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "HopGlass",
|
"name": "hopglass",
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"bower_components",
|
"bower_components",
|
||||||
|
@ -9,23 +9,19 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Leaflet.label": "~0.2.1",
|
"Leaflet.label": "~0.2.1",
|
||||||
"chroma-js": "~0.6.1",
|
"chroma-js": "~1.1.1",
|
||||||
"leaflet": "~0.7.3",
|
"leaflet": "~0.7.7",
|
||||||
"ionicons": "~2.0.1",
|
"moment": "~2.13.0",
|
||||||
"moment": "~2.9.0",
|
"requirejs": "~2.2.0",
|
||||||
"requirejs": "~2.1.16",
|
"tablesort": "https://github.com/tristen/tablesort.git#v4.0.1",
|
||||||
"tablesort": "https://github.com/tristen/tablesort.git#v3.0.2",
|
|
||||||
"roboto-slab-fontface": "*",
|
"roboto-slab-fontface": "*",
|
||||||
"es6-shim": "~0.27.1",
|
"es6-shim": "~0.35.1",
|
||||||
"almond": "~0.3.1",
|
"almond": "~0.3.2",
|
||||||
"r.js": "~2.1.16",
|
"d3": "~3.5.17",
|
||||||
"d3": "~3.5.5",
|
"roboto-fontface": "~0.4.5",
|
||||||
"numeraljs": "~1.5.3",
|
"virtual-dom": "~2.1.1",
|
||||||
"roboto-fontface": "~0.3.0",
|
"leaflet-providers": "~1.1.10",
|
||||||
"virtual-dom": "~2.0.1",
|
"rbush": "https://github.com/mourner/rbush.git#~1.4.3"
|
||||||
"leaflet-providers": "~1.0.27",
|
|
||||||
"rbush": "https://github.com/mourner/rbush.git#~1.3.5",
|
|
||||||
"jshashes": "~1.0.5"
|
|
||||||
},
|
},
|
||||||
"authors": [
|
"authors": [
|
||||||
"Milan Pässler <me@petabyteboy.de>",
|
"Milan Pässler <me@petabyteboy.de>",
|
||||||
|
|
9
build.js
9
build.js
|
@ -1,9 +0,0 @@
|
||||||
({
|
|
||||||
baseUrl: "lib",
|
|
||||||
name: "../bower_components/almond/almond",
|
|
||||||
mainConfigFile: "app.js",
|
|
||||||
include: "../app",
|
|
||||||
wrap: true,
|
|
||||||
optimize: "uglify",
|
|
||||||
out: "app-combined.js"
|
|
||||||
})
|
|
249
helper.js
249
helper.js
|
@ -1,228 +1,241 @@
|
||||||
function get(url) {
|
function get(url) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
var req = new XMLHttpRequest()
|
var req = new XMLHttpRequest();
|
||||||
req.open('GET', url)
|
req.open('GET', url);
|
||||||
|
|
||||||
req.onload = function() {
|
req.onload = function () {
|
||||||
if (req.status == 200) {
|
if (req.status == 200) {
|
||||||
resolve(req.response)
|
resolve(req.response);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
reject(Error(req.statusText))
|
reject(Error(req.statusText));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
req.onerror = function() {
|
req.onerror = function () {
|
||||||
reject(Error("Network Error"))
|
reject(Error("Network Error"));
|
||||||
}
|
};
|
||||||
|
|
||||||
req.send()
|
req.send();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getJSON(url) {
|
function getJSON(url) {
|
||||||
return get(url).then(JSON.parse)
|
return get(url).then(JSON.parse);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortByKey(key, d) {
|
function sortByKey(key, d) {
|
||||||
return d.slice().sort( function (a, b) {
|
return d.slice().sort(function (a, b) {
|
||||||
return a[key] - b[key]
|
return a[key] - b[key];
|
||||||
}).reverse()
|
}).reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
function limit(key, m, d) {
|
function limit(key, m, d) {
|
||||||
return d.filter( function (d) {
|
return d.filter(function (d) {
|
||||||
return d[key].isAfter(m)
|
return d[key].isAfter(m);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sum(a) {
|
function sum(a) {
|
||||||
return a.reduce( function (a, b) {
|
return a.reduce(function (a, b) {
|
||||||
return a + b
|
return a + b;
|
||||||
}, 0)
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function one() {
|
function one() {
|
||||||
return 1
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function trueDefault(d) {
|
function trueDefault(d) {
|
||||||
return d === undefined ? true : d
|
return d === undefined ? true : d;
|
||||||
}
|
}
|
||||||
|
|
||||||
function dictGet(dict, key) {
|
function dictGet(dict, key) {
|
||||||
var k = key.shift()
|
var k = key.shift();
|
||||||
|
|
||||||
if (!(k in dict))
|
if (!(k in dict)) {
|
||||||
return null
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (key.length == 0)
|
if (key.length == 0) {
|
||||||
return dict[k]
|
return dict[k];
|
||||||
|
}
|
||||||
|
|
||||||
return dictGet(dict[k], key)
|
return dictGet(dict[k], key);
|
||||||
}
|
}
|
||||||
|
|
||||||
function localStorageTest() {
|
function localStorageTest() {
|
||||||
var test = 'test'
|
var test = 'test';
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(test, test)
|
localStorage.setItem(test, test);
|
||||||
localStorage.removeItem(test)
|
localStorage.removeItem(test);
|
||||||
return true
|
return true;
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function listReplace(s, subst) {
|
function listReplace(s, subst) {
|
||||||
for (key in subst) {
|
for (key in subst) {
|
||||||
var re = new RegExp(key, 'g')
|
var re = new RegExp(key, 'g');
|
||||||
s = s.replace(re, subst[key])
|
s = s.replace(re, subst[key]);
|
||||||
}
|
}
|
||||||
return s
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helpers working with nodes */
|
/* Helpers working with nodes */
|
||||||
|
|
||||||
function offline(d) {
|
function offline(d) {
|
||||||
return !d.flags.online
|
return !d.flags.online;
|
||||||
}
|
}
|
||||||
|
|
||||||
function online(d) {
|
function online(d) {
|
||||||
return d.flags.online
|
return d.flags.online;
|
||||||
}
|
}
|
||||||
|
|
||||||
function has_location(d) {
|
function has_location(d) {
|
||||||
return "location" in d.nodeinfo &&
|
return "location" in d.nodeinfo &&
|
||||||
Math.abs(d.nodeinfo.location.latitude) < 90 &&
|
Math.abs(d.nodeinfo.location.latitude) < 90 &&
|
||||||
Math.abs(d.nodeinfo.location.longitude) < 180
|
Math.abs(d.nodeinfo.location.longitude) < 180;
|
||||||
}
|
}
|
||||||
|
|
||||||
function subtract(a, b) {
|
function subtract(a, b) {
|
||||||
var ids = {}
|
var ids = {};
|
||||||
|
|
||||||
b.forEach( function (d) {
|
b.forEach(function (d) {
|
||||||
ids[d.nodeinfo.node_id] = true
|
ids[d.nodeinfo.node_id] = true;
|
||||||
})
|
});
|
||||||
|
|
||||||
return a.filter( function (d) {
|
return a.filter(function (d) {
|
||||||
return !(d.nodeinfo.node_id in ids)
|
return !(d.nodeinfo.node_id in ids);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helpers working with links */
|
/* Helpers working with links */
|
||||||
|
|
||||||
function showDistance(d) {
|
function showDistance(d) {
|
||||||
if (isNaN(d.distance))
|
if (isNaN(d.distance)) {
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return numeral(d.distance).format("0,0") + " m"
|
return d.distance.toFixed(0) + " m"
|
||||||
}
|
}
|
||||||
|
|
||||||
function showTq(d) {
|
function showTq(d) {
|
||||||
return numeral(1/d.tq).format("0%")
|
return (1 / d.tq * 100).toFixed(0) + "%"
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Infobox stuff (XXX: move to module) */
|
/* Infobox stuff (XXX: move to module) */
|
||||||
|
|
||||||
function attributeEntry(el, label, value) {
|
function attributeEntry(el, label, value) {
|
||||||
if (value === null || value == undefined)
|
if (value === null || value == undefined) {
|
||||||
return
|
return;
|
||||||
|
|
||||||
var tr = document.createElement("tr")
|
|
||||||
var th = document.createElement("th")
|
|
||||||
if (typeof label === "string")
|
|
||||||
th.textContent = label
|
|
||||||
else {
|
|
||||||
th.appendChild(label)
|
|
||||||
tr.className = "routerpic"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.appendChild(th)
|
var tr = document.createElement("tr");
|
||||||
|
var th = document.createElement("th");
|
||||||
|
if (typeof label === "string") {
|
||||||
|
th.textContent = label;
|
||||||
|
} else {
|
||||||
|
th.appendChild(label);
|
||||||
|
tr.className = "routerpic";
|
||||||
|
}
|
||||||
|
|
||||||
var td = document.createElement("td")
|
tr.appendChild(th);
|
||||||
|
|
||||||
if (typeof value == "function")
|
var td = document.createElement("td");
|
||||||
value(td)
|
|
||||||
else
|
|
||||||
td.appendChild(document.createTextNode(value))
|
|
||||||
|
|
||||||
tr.appendChild(td)
|
if (typeof value == "function") {
|
||||||
|
value(td);
|
||||||
|
} else {
|
||||||
|
td.appendChild(document.createTextNode(value));
|
||||||
|
}
|
||||||
|
|
||||||
el.appendChild(tr)
|
tr.appendChild(td);
|
||||||
|
|
||||||
return td
|
el.appendChild(tr);
|
||||||
|
|
||||||
|
return td;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createIframe(opt, width, height) {
|
function createIframe(opt, width, height) {
|
||||||
el = document.createElement("iframe")
|
el = document.createElement("iframe");
|
||||||
width = typeof width !== 'undefined' ? width : '100%'
|
width = typeof width !== 'undefined' ? width : '100%';
|
||||||
height = typeof height !== 'undefined' ? height : '350px'
|
height = typeof height !== 'undefined' ? height : '350px';
|
||||||
|
|
||||||
if (opt.src)
|
if (opt.src) {
|
||||||
el.src = opt.src
|
el.src = opt.src;
|
||||||
else
|
} else {
|
||||||
el.src = opt
|
el.src = opt;
|
||||||
|
}
|
||||||
|
|
||||||
if (opt.frameBorder)
|
if (opt.frameBorder) {
|
||||||
el.frameBorder = opt.frameBorder
|
el.frameBorder = opt.frameBorder;
|
||||||
else
|
} else {
|
||||||
el.frameBorder = 1
|
el.frameBorder = 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (opt.width)
|
if (opt.width) {
|
||||||
el.width = opt.width
|
el.width = opt.width;
|
||||||
else
|
} else {
|
||||||
el.width = width
|
el.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
if (opt.height)
|
if (opt.height) {
|
||||||
el.height = opt.height
|
el.height = opt.height;
|
||||||
else
|
} else {
|
||||||
el.height = height
|
el.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
el.scrolling = "no"
|
el.scrolling = "no";
|
||||||
el.seamless = "seamless"
|
el.seamless = "seamless";
|
||||||
|
|
||||||
return el
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showStat(o, subst) {
|
function showStat(o, subst) {
|
||||||
var content, caption
|
var content, caption;
|
||||||
subst = typeof subst !== 'undefined' ? subst : {}
|
subst = typeof subst !== 'undefined' ? subst : {};
|
||||||
|
|
||||||
if (o.thumbnail) {
|
if (o.thumbnail) {
|
||||||
content = document.createElement("img")
|
content = document.createElement("img");
|
||||||
content.src = listReplace(o.thumbnail, subst)
|
content.src = listReplace(o.thumbnail, subst);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o.caption) {
|
if (o.caption) {
|
||||||
caption = listReplace(o.caption, subst)
|
caption = listReplace(o.caption, subst);
|
||||||
|
|
||||||
if (!content)
|
if (!content) {
|
||||||
content = document.createTextNode(caption)
|
content = document.createTextNode(caption);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (o.iframe) {
|
if (o.iframe) {
|
||||||
content = createIframe(o.iframe, o.width, o.height)
|
content = createIframe(o.iframe, o.width, o.height);
|
||||||
if (o.iframe.src)
|
if (o.iframe.src) {
|
||||||
content.src = listReplace(o.iframe.src, subst)
|
content.src = listReplace(o.iframe.src, subst);
|
||||||
else
|
} else {
|
||||||
content.src = listReplace(o.iframe, subst)
|
content.src = listReplace(o.iframe, subst);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var p = document.createElement("p")
|
var p = document.createElement("p");
|
||||||
|
|
||||||
if (o.href) {
|
if (o.href) {
|
||||||
var link = document.createElement("a")
|
var link = document.createElement("a");
|
||||||
link.target = "_blank"
|
link.target = "_blank";
|
||||||
link.href = listReplace(o.href, subst)
|
link.href = listReplace(o.href, subst);
|
||||||
link.appendChild(content)
|
link.appendChild(content);
|
||||||
|
|
||||||
if (caption && o.thumbnail)
|
if (caption && o.thumbnail) {
|
||||||
link.title = caption
|
link.title = caption;
|
||||||
|
}
|
||||||
|
|
||||||
p.appendChild(link)
|
p.appendChild(link);
|
||||||
} else
|
} else {
|
||||||
p.appendChild(content)
|
p.appendChild(content);
|
||||||
|
}
|
||||||
|
|
||||||
return p
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,27 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||||
<link rel="stylesheet" href="css/ionicons.min.css">
|
<link rel="stylesheet" href="roboto-slab-fontface.css">
|
||||||
<link rel="stylesheet" href="roboto-slab-fontface.css">
|
<link rel="stylesheet" href="roboto-fontface.css">
|
||||||
<link rel="stylesheet" href="roboto-fontface.css">
|
<link rel="stylesheet" href="style.css?__inline=true">
|
||||||
<link rel="stylesheet" href="style.css">
|
<script src="vendor/es6-shim/es6-shim.min.js?__inline=true"></script>
|
||||||
<script src="vendor/es6-shim/es6-shim.min.js"></script>
|
<script src="app.js"></script>
|
||||||
<script src="app.js"></script>
|
<script>
|
||||||
<script>
|
console.log("Version: #revision#");
|
||||||
console.log("Version: #revision#")
|
</script>
|
||||||
</script>
|
</head>
|
||||||
</head>
|
<body>
|
||||||
<body>
|
<div class="loader">
|
||||||
</body>
|
<p>
|
||||||
|
Lade<br/>
|
||||||
|
<span class="spinner"></span><br/>
|
||||||
|
Karte & Knoten...
|
||||||
|
</p>
|
||||||
|
<noscript>
|
||||||
|
<strong>JavaScript required</strong>
|
||||||
|
</noscript>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
38
index.html
38
index.html
|
@ -1,17 +1,27 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||||
<link rel="stylesheet" href="bower_components/roboto-slab-fontface/roboto-slab-fontface.css">
|
<link rel="stylesheet" href="bower_components/roboto-slab-fontface/roboto-slab-fontface.css">
|
||||||
<link rel="stylesheet" href="bower_components/roboto-fontface/roboto-fontface.css">
|
<link rel="stylesheet" href="bower_components/roboto-fontface/roboto-fontface.css">
|
||||||
<link rel="stylesheet" href="bower_components/leaflet/dist/leaflet.css">
|
<link rel="stylesheet" href="bower_components/leaflet/dist/leaflet.css">
|
||||||
<link rel="stylesheet" href="bower_components/Leaflet.label/dist/leaflet.label.css">
|
<link rel="stylesheet" href="bower_components/Leaflet.label/dist/leaflet.label.css">
|
||||||
<link rel="stylesheet" href="bower_components/ionicons/css/ionicons.min.css">
|
<link rel="stylesheet" href="assets/icons/hopglass-icons.css">
|
||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
<script src="bower_components/es6-shim/es6-shim.min.js"></script>
|
<script src="bower_components/es6-shim/es6-shim.min.js"></script>
|
||||||
<script src="bower_components/requirejs/require.js" data-main="app"></script>
|
<script src="bower_components/requirejs/require.js" data-main="app"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
</body>
|
<div class="loader">
|
||||||
|
<p>
|
||||||
|
Lade<br/>
|
||||||
|
<span class="spinner"></span><br \>
|
||||||
|
Karte & Knoten...
|
||||||
|
</p>
|
||||||
|
<noscript>
|
||||||
|
<strong>JavaScript required</strong>
|
||||||
|
</noscript>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
56
lib/about.js
56
lib/about.js
|
@ -1,38 +1,38 @@
|
||||||
define(function () {
|
define(function () {
|
||||||
return function() {
|
"use strict";
|
||||||
|
|
||||||
|
return function () {
|
||||||
this.render = function (d) {
|
this.render = function (d) {
|
||||||
var el = document.createElement("div")
|
var s = "<h2>Über HopGlass</h2>";
|
||||||
d.appendChild(el)
|
|
||||||
var s = "<h2>Über HopGlass</h2>"
|
|
||||||
|
|
||||||
s += "<p>Mit Doppelklick und Shift+Doppelklick kann man in der Karte "
|
s += "<p>Mit Doppelklick und Shift+Doppelklick kann man in der Karte ";
|
||||||
s += "auch zoomen.</p>"
|
s += "auch zoomen.</p>";
|
||||||
|
|
||||||
s += "<h3>AGPL 3</h3>"
|
s += "<h3>AGPL 3</h3>";
|
||||||
|
|
||||||
s += "<p>Copyright (C) Milan Pässler</p>"
|
s += "<p>Copyright (C) Milan Pässler</p>";
|
||||||
s += "<p>Copyright (C) Nils Schneider</p>"
|
s += "<p>Copyright (C) Nils Schneider</p>";
|
||||||
|
|
||||||
s += "<p>This program is free software: you can redistribute it and/or "
|
s += "<p>This program is free software: you can redistribute it and/or ";
|
||||||
s += "modify it under the terms of the GNU Affero General Public "
|
s += "modify it under the terms of the GNU Affero General Public ";
|
||||||
s += "License as published by the Free Software Foundation, either "
|
s += "License as published by the Free Software Foundation, either ";
|
||||||
s += "version 3 of the License, or (at your option) any later version.</p>"
|
s += "version 3 of the License, or (at your option) any later version.</p>";
|
||||||
|
|
||||||
s += "<p>This program is distributed in the hope that it will be useful, "
|
s += "<p>This program is distributed in the hope that it will be useful, ";
|
||||||
s += "but WITHOUT ANY WARRANTY; without even the implied warranty of "
|
s += "but WITHOUT ANY WARRANTY; without even the implied warranty of ";
|
||||||
s += "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
|
s += "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ";
|
||||||
s += "GNU Affero General Public License for more details.</p>"
|
s += "GNU Affero General Public License for more details.</p>";
|
||||||
|
|
||||||
s += "<p>You should have received a copy of the GNU Affero General "
|
s += "<p>You should have received a copy of the GNU Affero General ";
|
||||||
s += "Public License along with this program. If not, see "
|
s += "Public License along with this program. If not, see ";
|
||||||
s += "<a href=\"https://www.gnu.org/licenses/\">"
|
s += "<a href=\"https://www.gnu.org/licenses/\">";
|
||||||
s += "https://www.gnu.org/licenses/</a>.</p>"
|
s += "https://www.gnu.org/licenses/</a>.</p>";
|
||||||
|
|
||||||
s += "<p>The source code is available at "
|
s += "<p>The source code is available at ";
|
||||||
s += "<a href=\"https://github.com/plumpudding/hopglass\">"
|
s += "<a href=\"https://github.com/plumpudding/hopglass\">";
|
||||||
s += "https://github.com/plumpudding/hopglass</a>."
|
s += "https://github.com/plumpudding/hopglass</a>.";
|
||||||
|
|
||||||
el.innerHTML = s
|
d.innerHTML = s;
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
define([], function () {
|
define([], function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return function (tag) {
|
return function (tag) {
|
||||||
if (!tag)
|
if (!tag) {
|
||||||
tag = "div"
|
tag = "div";
|
||||||
|
}
|
||||||
|
|
||||||
var self = this
|
var self = this;
|
||||||
|
|
||||||
var container = document.createElement(tag)
|
var container = document.createElement(tag);
|
||||||
|
|
||||||
self.add = function (d) {
|
self.add = function (d) {
|
||||||
d.render(container)
|
d.render(container);
|
||||||
}
|
};
|
||||||
|
|
||||||
self.render = function (el) {
|
self.render = function (el) {
|
||||||
el.appendChild(container)
|
el.appendChild(container);
|
||||||
}
|
};
|
||||||
|
|
||||||
return self
|
return self;
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,80 +1,93 @@
|
||||||
define(["filters/nodefilter"], function (NodeFilter) {
|
define(["filters/nodefilter"], function (NodeFilter) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
var targets = []
|
var targets = [];
|
||||||
var filterObservers = []
|
var filterObservers = [];
|
||||||
var filters = []
|
var filters = [];
|
||||||
var filteredData
|
var filteredData;
|
||||||
var data
|
var data;
|
||||||
|
|
||||||
function remove(d) {
|
function remove(d) {
|
||||||
targets = targets.filter( function (e) { return d !== e } )
|
targets = targets.filter(function (e) {
|
||||||
|
return d !== e;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function add(d) {
|
function add(d) {
|
||||||
targets.push(d)
|
targets.push(d);
|
||||||
|
|
||||||
if (filteredData !== undefined)
|
if (filteredData !== undefined) {
|
||||||
d.setData(filteredData)
|
d.setData(filteredData);
|
||||||
}
|
|
||||||
|
|
||||||
function setData(d) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}, function () { return true })
|
|
||||||
|
|
||||||
filteredData = new NodeFilter(filter)(data)
|
|
||||||
|
|
||||||
targets.forEach( function (t) {
|
|
||||||
t.setData(filteredData)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function notifyObservers() {
|
|
||||||
filterObservers.forEach( function (d) {
|
|
||||||
d.filtersChanged(filters)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function addFilter(d) {
|
|
||||||
filters.push(d)
|
|
||||||
notifyObservers()
|
|
||||||
d.setRefresh(refresh)
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeFilter(d) {
|
|
||||||
filters = filters.filter( function (e) { return d !== e } )
|
|
||||||
notifyObservers()
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
function watchFilters(d) {
|
|
||||||
filterObservers.push(d)
|
|
||||||
|
|
||||||
d.filtersChanged(filters)
|
|
||||||
|
|
||||||
return function () {
|
|
||||||
filterObservers = filterObservers.filter( function (e) { return d !== e })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { add: add,
|
function setData(d) {
|
||||||
remove: remove,
|
data = d;
|
||||||
setData: setData,
|
refresh();
|
||||||
addFilter: addFilter,
|
}
|
||||||
removeFilter: removeFilter,
|
|
||||||
watchFilters: watchFilters,
|
function refresh() {
|
||||||
refresh: refresh
|
if (data === undefined) {
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
var filter = filters.reduce(function (a, f) {
|
||||||
|
return function (d) {
|
||||||
|
return a(d) && f.run(d);
|
||||||
|
};
|
||||||
|
}, function () {
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
filteredData = new NodeFilter(filter)(data);
|
||||||
|
|
||||||
|
targets.forEach(function (t) {
|
||||||
|
t.setData(filteredData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyObservers() {
|
||||||
|
filterObservers.forEach(function (d) {
|
||||||
|
d.filtersChanged(filters);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFilter(d) {
|
||||||
|
filters.push(d);
|
||||||
|
notifyObservers();
|
||||||
|
d.setRefresh(refresh);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFilter(d) {
|
||||||
|
filters = filters.filter(function (e) {
|
||||||
|
return d !== e;
|
||||||
|
});
|
||||||
|
notifyObservers();
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchFilters(d) {
|
||||||
|
filterObservers.push(d);
|
||||||
|
|
||||||
|
d.filtersChanged(filters);
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
filterObservers = filterObservers.filter(function (e) {
|
||||||
|
return d !== e;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
add: add,
|
||||||
|
remove: remove,
|
||||||
|
setData: setData,
|
||||||
|
addFilter: addFilter,
|
||||||
|
removeFilter: removeFilter,
|
||||||
|
watchFilters: watchFilters,
|
||||||
|
refresh: refresh
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
|
@ -1,40 +1,43 @@
|
||||||
define([], function () {
|
define(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return function (distributor) {
|
return function (distributor) {
|
||||||
var container = document.createElement("ul")
|
var container = document.createElement("ul");
|
||||||
container.classList.add("filters")
|
container.classList.add("filters");
|
||||||
var div = document.createElement("div")
|
var div = document.createElement("div");
|
||||||
|
|
||||||
function render(el) {
|
function render(el) {
|
||||||
el.appendChild(div)
|
el.appendChild(div);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filtersChanged(filters) {
|
function filtersChanged(filters) {
|
||||||
while (container.firstChild)
|
while (container.firstChild) {
|
||||||
container.removeChild(container.firstChild)
|
container.removeChild(container.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
filters.forEach( function (d) {
|
filters.forEach(function (d) {
|
||||||
var li = document.createElement("li")
|
var li = document.createElement("li");
|
||||||
var div = document.createElement("div")
|
container.appendChild(li);
|
||||||
container.appendChild(li)
|
d.render(li);
|
||||||
li.appendChild(div)
|
|
||||||
d.render(div)
|
|
||||||
|
|
||||||
var button = document.createElement("button")
|
var button = document.createElement("button");
|
||||||
button.textContent = ""
|
button.classList.add("ion-android-close");
|
||||||
button.onclick = function () {
|
button.onclick = function () {
|
||||||
distributor.removeFilter(d)
|
distributor.removeFilter(d);
|
||||||
}
|
};
|
||||||
li.appendChild(button)
|
li.appendChild(button);
|
||||||
})
|
});
|
||||||
|
|
||||||
if (container.parentNode === div && filters.length === 0)
|
if (container.parentNode === div && filters.length === 0) {
|
||||||
div.removeChild(container)
|
div.removeChild(container);
|
||||||
else if (filters.length > 0)
|
} else if (filters.length > 0) {
|
||||||
div.appendChild(container)
|
div.appendChild(container);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { render: render,
|
return {
|
||||||
filtersChanged: filtersChanged
|
render: render,
|
||||||
}
|
filtersChanged: filtersChanged
|
||||||
}
|
};
|
||||||
})
|
};
|
||||||
|
});
|
||||||
|
|
|
@ -1,52 +1,58 @@
|
||||||
define([], function () {
|
define(["helper"], function (helper) {
|
||||||
return function (name, key, value, f) {
|
"use strict";
|
||||||
var negate = false
|
|
||||||
var refresh
|
|
||||||
|
|
||||||
var label = document.createElement("label")
|
return function (name, key, value, f) {
|
||||||
var strong = document.createElement("strong")
|
var negate = false;
|
||||||
label.textContent = name + " "
|
var refresh;
|
||||||
label.appendChild(strong)
|
|
||||||
|
var label = document.createElement("label");
|
||||||
|
var strong = document.createElement("strong");
|
||||||
|
label.textContent = name + ": ";
|
||||||
|
label.appendChild(strong);
|
||||||
|
|
||||||
function run(d) {
|
function run(d) {
|
||||||
var o = dictGet(d, key.slice(0))
|
var o = helper.dictGet(d, key.slice(0));
|
||||||
|
|
||||||
if (f)
|
if (f) {
|
||||||
o = f(o)
|
o = f(o);
|
||||||
|
}
|
||||||
|
|
||||||
return o === value ? !negate : negate
|
return o === value ? !negate : negate;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRefresh(f) {
|
function setRefresh(f) {
|
||||||
refresh = f
|
refresh = f;
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw(el) {
|
function draw(el) {
|
||||||
if (negate)
|
if (negate) {
|
||||||
el.parentNode.classList.add("not")
|
el.classList.add("not");
|
||||||
else
|
} else {
|
||||||
el.parentNode.classList.remove("not")
|
el.classList.remove("not");
|
||||||
|
}
|
||||||
|
|
||||||
strong.textContent = (negate ? "¬" : "" ) + value
|
strong.textContent = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function render(el) {
|
function render(el) {
|
||||||
el.appendChild(label)
|
el.appendChild(label);
|
||||||
draw(el)
|
draw(el);
|
||||||
|
|
||||||
label.onclick = function () {
|
label.onclick = function () {
|
||||||
negate = !negate
|
negate = !negate;
|
||||||
|
|
||||||
draw(el)
|
draw(el);
|
||||||
|
|
||||||
if (refresh)
|
if (refresh) {
|
||||||
refresh()
|
refresh();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { run: run,
|
return {
|
||||||
setRefresh: setRefresh,
|
run: run,
|
||||||
render: render
|
setRefresh: setRefresh,
|
||||||
}
|
render: render
|
||||||
}
|
};
|
||||||
})
|
};
|
||||||
|
});
|
||||||
|
|
|
@ -1,33 +1,38 @@
|
||||||
define([], function () {
|
define(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return function (filter) {
|
return function (filter) {
|
||||||
return function (data) {
|
return function (data) {
|
||||||
var n = Object.create(data)
|
var n = Object.create(data);
|
||||||
n.nodes = {}
|
n.nodes = {};
|
||||||
|
|
||||||
for (var key in data.nodes)
|
for (var key in data.nodes) {
|
||||||
n.nodes[key] = data.nodes[key].filter(filter)
|
n.nodes[key] = data.nodes[key].filter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
var filteredIds = new Set()
|
var filteredIds = new Set();
|
||||||
|
|
||||||
n.graph = {}
|
n.graph = {};
|
||||||
n.graph.nodes = data.graph.nodes.filter( function (d) {
|
n.graph.nodes = data.graph.nodes.filter(function (d) {
|
||||||
var r
|
var r;
|
||||||
if (d.node)
|
if (d.node) {
|
||||||
r = filter(d.node)
|
r = filter(d.node);
|
||||||
else
|
} else {
|
||||||
r = filter({})
|
r = filter({});
|
||||||
|
}
|
||||||
|
|
||||||
if (r)
|
if (r) {
|
||||||
filteredIds.add(d.id)
|
filteredIds.add(d.id);
|
||||||
|
}
|
||||||
|
|
||||||
return r
|
return r;
|
||||||
})
|
});
|
||||||
|
|
||||||
n.graph.links = data.graph.links.filter( function (d) {
|
n.graph.links = data.graph.links.filter(function (d) {
|
||||||
return filteredIds.has(d.source.id) && filteredIds.has(d.target.id)
|
return filteredIds.has(d.source.id) && filteredIds.has(d.target.id);
|
||||||
})
|
});
|
||||||
|
|
||||||
return n
|
return n;
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
1076
lib/forcegraph.js
1076
lib/forcegraph.js
File diff suppressed because it is too large
Load diff
199
lib/gui.js
199
lib/gui.js
|
@ -1,122 +1,129 @@
|
||||||
define([ "chroma-js", "map", "sidebar", "tabs", "container", "meshstats",
|
define(["chroma-js", "map", "sidebar", "tabs", "container", "meshstats",
|
||||||
"legend", "linklist", "nodelist", "simplenodelist", "infobox/main",
|
"legend", "linklist", "nodelist", "simplenodelist", "infobox/main",
|
||||||
"proportions", "forcegraph", "title", "about", "datadistributor",
|
"proportions", "forcegraph", "title", "about", "datadistributor",
|
||||||
"filters/filtergui" ],
|
"filters/filtergui"],
|
||||||
function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
|
function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
|
||||||
Nodelist, SimpleNodelist, Infobox, Proportions, ForceGraph,
|
Nodelist, SimpleNodelist, Infobox, Proportions, ForceGraph,
|
||||||
Title, About, DataDistributor, FilterGUI) {
|
Title, About, DataDistributor, FilterGUI) {
|
||||||
return function (config, router) {
|
"use strict";
|
||||||
var self = this
|
|
||||||
var content
|
|
||||||
var contentDiv
|
|
||||||
|
|
||||||
var linkScale = chroma.scale(chroma.interpolate.bezier(["#04C714", "#FF5500", "#F02311"])).domain([1, 5])
|
return function (config, router) {
|
||||||
var sidebar
|
var self = this;
|
||||||
|
var content;
|
||||||
|
var contentDiv;
|
||||||
|
|
||||||
var buttons = document.createElement("div")
|
var linkScale = chroma.scale(chroma.bezier(["#04C714", "#FF5500", "#F02311"])).domain([1, 5]);
|
||||||
buttons.classList.add("buttons")
|
var sidebar;
|
||||||
|
|
||||||
var fanout = new DataDistributor()
|
var buttons = document.createElement("div");
|
||||||
var fanoutUnfiltered = new DataDistributor()
|
buttons.classList.add("buttons");
|
||||||
fanoutUnfiltered.add(fanout)
|
|
||||||
|
|
||||||
function removeContent() {
|
var fanout = new DataDistributor();
|
||||||
if (!content)
|
var fanoutUnfiltered = new DataDistributor();
|
||||||
return
|
fanoutUnfiltered.add(fanout);
|
||||||
|
|
||||||
router.removeTarget(content)
|
function removeContent() {
|
||||||
fanout.remove(content)
|
if (!content) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
content.destroy()
|
router.removeTarget(content);
|
||||||
|
fanout.remove(content);
|
||||||
|
|
||||||
content = null
|
content.destroy();
|
||||||
}
|
|
||||||
|
|
||||||
function addContent(K) {
|
content = null;
|
||||||
removeContent()
|
|
||||||
|
|
||||||
content = new K(config, linkScale, sidebar.getWidth, router, buttons)
|
|
||||||
content.render(contentDiv)
|
|
||||||
|
|
||||||
fanout.add(content)
|
|
||||||
router.addTarget(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
function mkView(K) {
|
|
||||||
return function () {
|
|
||||||
addContent(K)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
contentDiv = document.createElement("div")
|
function addContent(K) {
|
||||||
contentDiv.classList.add("content")
|
removeContent();
|
||||||
document.body.appendChild(contentDiv)
|
|
||||||
|
|
||||||
sidebar = new Sidebar(document.body)
|
content = new K(config, linkScale, sidebar.getWidth, router, buttons);
|
||||||
|
content.render(contentDiv);
|
||||||
|
|
||||||
contentDiv.appendChild(buttons)
|
fanout.add(content);
|
||||||
|
router.addTarget(content);
|
||||||
|
}
|
||||||
|
|
||||||
var buttonToggle = document.createElement("button")
|
function mkView(K) {
|
||||||
buttonToggle.textContent = "\uF133"
|
return function () {
|
||||||
buttonToggle.onclick = function () {
|
addContent(K);
|
||||||
if (content.constructor === Map)
|
};
|
||||||
router.view("g")
|
}
|
||||||
else
|
|
||||||
router.view("m")
|
|
||||||
}
|
|
||||||
|
|
||||||
buttons.appendChild(buttonToggle)
|
var loader = document.getElementsByClassName("loader")[0];
|
||||||
|
loader.classList.add("hide");
|
||||||
|
|
||||||
var title = new Title(config)
|
contentDiv = document.createElement("div");
|
||||||
|
contentDiv.classList.add("content");
|
||||||
|
document.body.appendChild(contentDiv);
|
||||||
|
|
||||||
var header = new Container("header")
|
sidebar = new Sidebar(document.body);
|
||||||
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)
|
contentDiv.appendChild(buttons);
|
||||||
fanoutUnfiltered.add(newnodeslist)
|
|
||||||
fanoutUnfiltered.add(lostnodeslist)
|
|
||||||
fanout.add(nodelist)
|
|
||||||
fanout.add(linklist)
|
|
||||||
fanout.add(statistics)
|
|
||||||
|
|
||||||
sidebar.add(header)
|
var buttonToggle = document.createElement("button");
|
||||||
header.add(meshstats)
|
buttonToggle.classList.add("ion-eye", "shadow");
|
||||||
header.add(legend)
|
buttonToggle.onclick = function () {
|
||||||
|
if (content.constructor === Map) {
|
||||||
|
router.view("g");
|
||||||
|
} else {
|
||||||
|
router.view("m");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
overview.add(newnodeslist)
|
buttons.appendChild(buttonToggle);
|
||||||
overview.add(lostnodeslist)
|
|
||||||
|
|
||||||
var filterGUI = new FilterGUI(fanout)
|
var title = new Title(config);
|
||||||
fanout.watchFilters(filterGUI)
|
|
||||||
header.add(filterGUI)
|
|
||||||
|
|
||||||
sidebar.add(tabs)
|
var header = new Container("header");
|
||||||
tabs.add("Aktuelles", overview)
|
var infobox = new Infobox(config, sidebar, router);
|
||||||
tabs.add("Knoten", nodelist)
|
var tabs = new Tabs();
|
||||||
tabs.add("Verbindungen", linklist)
|
var overview = new Container();
|
||||||
tabs.add("Statistiken", statistics)
|
var meshstats = new Meshstats(config);
|
||||||
tabs.add("Über", about)
|
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();
|
||||||
|
|
||||||
router.addTarget(title)
|
fanoutUnfiltered.add(meshstats);
|
||||||
router.addTarget(infobox)
|
fanoutUnfiltered.add(newnodeslist);
|
||||||
|
fanoutUnfiltered.add(lostnodeslist);
|
||||||
|
fanout.add(nodelist);
|
||||||
|
fanout.add(linklist);
|
||||||
|
fanout.add(statistics);
|
||||||
|
|
||||||
router.addView("m", mkView(Map))
|
sidebar.add(header);
|
||||||
router.addView("g", mkView(ForceGraph))
|
header.add(meshstats);
|
||||||
|
header.add(legend);
|
||||||
|
|
||||||
router.view("m")
|
overview.add(newnodeslist);
|
||||||
|
overview.add(lostnodeslist);
|
||||||
|
|
||||||
self.setData = fanoutUnfiltered.setData
|
var filterGUI = new FilterGUI(fanout);
|
||||||
|
fanout.watchFilters(filterGUI);
|
||||||
|
header.add(filterGUI);
|
||||||
|
|
||||||
return self
|
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.addView("m", mkView(Map));
|
||||||
|
router.addView("g", mkView(ForceGraph));
|
||||||
|
|
||||||
|
router.view("m");
|
||||||
|
|
||||||
|
self.setData = fanoutUnfiltered.setData;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
|
@ -1,48 +1,58 @@
|
||||||
define(function () {
|
define(["helper"], function (helper) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
function showStatImg(o, d) {
|
function showStatImg(o, d) {
|
||||||
var subst = {}
|
var subst = {};
|
||||||
subst["{SOURCE}"] = d.source.node_id
|
subst["{SOURCE}"] = d.source.node_id;
|
||||||
subst["{SOURCE_NAME}"] = d.source.node.nodeinfo.hostname ? d.source.node.nodeinfo.hostname : "unknown"
|
subst["{SOURCE_NAME}"] = d.source.node.nodeinfo.hostname ? d.source.node.nodeinfo.hostname : "unknown";
|
||||||
subst["{TARGET}"] = d.target.node_id
|
subst["{TARGET}"] = d.target.node_id;
|
||||||
subst["{TARGET_NAME}"] = d.target.node.nodeinfo.hostname ? d.target.node.nodeinfo.hostname : "unknown"
|
subst["{TARGET_NAME}"] = d.target.node.nodeinfo.hostname ? d.target.node.nodeinfo.hostname : "unknown";
|
||||||
return showStat(o, subst)
|
return helper.showStat(o, subst);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function (config, el, router, d) {
|
return function (config, el, router, d) {
|
||||||
var unknown = !(d.source.node)
|
var unknown = !d.source.node;
|
||||||
var h2 = document.createElement("h2")
|
var h2 = document.createElement("h2");
|
||||||
var a1 = document.createElement("a")
|
var a1;
|
||||||
if (!unknown) {
|
if (!unknown) {
|
||||||
a1.href = "#"
|
a1 = document.createElement("a");
|
||||||
a1.onclick = router.node(d.source.node)
|
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
|
a1.textContent = unknown ? d.source.id : d.source.node.nodeinfo.hostname;
|
||||||
h2.appendChild(a1)
|
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)
|
|
||||||
|
|
||||||
var attributes = document.createElement("table")
|
var arrow = document.createElement("spam");
|
||||||
attributes.classList.add("attributes")
|
arrow.classList.add("ion-ios-arrow-thin-right");
|
||||||
|
h2.appendChild(arrow);
|
||||||
|
|
||||||
attributeEntry(attributes, "TQ", showTq(d))
|
var a2 = document.createElement("a");
|
||||||
attributeEntry(attributes, "Entfernung", showDistance(d))
|
a2.href = "#";
|
||||||
attributeEntry(attributes, "Typ", d.type)
|
a2.onclick = router.node(d.target.node);
|
||||||
var hw1 = unknown ? null : dictGet(d.source.node.nodeinfo, ["hardware", "model"])
|
a2.textContent = d.target.node.nodeinfo.hostname;
|
||||||
var hw2 = dictGet(d.target.node.nodeinfo, ["hardware", "model"])
|
h2.appendChild(a2);
|
||||||
attributeEntry(attributes, "Hardware", (hw1 != null ? hw1 : "unbekannt") + " – " + (hw2 != null ? hw2 : "unbekannt"))
|
el.appendChild(h2);
|
||||||
el.appendChild(attributes)
|
|
||||||
|
|
||||||
if (config.linkInfos)
|
var attributes = document.createElement("table");
|
||||||
config.linkInfos.forEach( function (linkInfo) {
|
attributes.classList.add("attributes");
|
||||||
var h4 = document.createElement("h4")
|
|
||||||
h4.textContent = linkInfo.name
|
helper.attributeEntry(attributes, "TQ", helper.showTq(d));
|
||||||
el.appendChild(h4)
|
helper.attributeEntry(attributes, "Entfernung", helper.showDistance(d));
|
||||||
el.appendChild(showStatImg(linkInfo, 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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
|
@ -1,100 +1,84 @@
|
||||||
define(function () {
|
define(["helper"], function (helper) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return function (config, el, router, d) {
|
return function (config, el, router, d) {
|
||||||
var sidebarTitle = document.createElement("h2")
|
var sidebarTitle = document.createElement("h2");
|
||||||
sidebarTitle.textContent = "Location: " + d.toString()
|
sidebarTitle.textContent = "Location: " + d.toString();
|
||||||
el.appendChild(sidebarTitle)
|
el.appendChild(sidebarTitle);
|
||||||
|
|
||||||
getJSON("https://nominatim.openstreetmap.org/reverse?format=json&lat=" + d.lat + "&lon=" + d.lng + "&zoom=18&addressdetails=0")
|
helper.getJSON("https://nominatim.openstreetmap.org/reverse?format=json&lat=" + d.lat + "&lon=" + d.lng + "&zoom=18&addressdetails=0")
|
||||||
.then(function(result) {
|
.then(function (result) {
|
||||||
if(result.display_name)
|
if (result.display_name) {
|
||||||
sidebarTitle.textContent = result.display_name
|
sidebarTitle.textContent = result.display_name;
|
||||||
})
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var editLat = document.createElement("input")
|
var editLat = document.createElement("input");
|
||||||
editLat.type = "text"
|
editLat.type = "text";
|
||||||
editLat.value = d.lat.toFixed(9)
|
editLat.value = d.lat.toFixed(9);
|
||||||
el.appendChild(createBox("lat", "Breitengrad", editLat))
|
el.appendChild(createBox("lat", "Breitengrad", editLat));
|
||||||
|
|
||||||
var editLng = document.createElement("input")
|
var editLng = document.createElement("input");
|
||||||
editLng.type = "text"
|
editLng.type = "text";
|
||||||
editLng.value = d.lng.toFixed(9)
|
editLng.value = d.lng.toFixed(9);
|
||||||
el.appendChild(createBox("lng", "Längengrad", editLng))
|
el.appendChild(createBox("lng", "Längengrad", editLng));
|
||||||
|
|
||||||
var editUci = document.createElement("textarea")
|
var editUci = document.createElement("textarea");
|
||||||
editUci.value =
|
editUci.value =
|
||||||
"uci set gluon-node-info.@location[0]='location'; " +
|
"uci set gluon-node-info.@location[0]='location'; " +
|
||||||
"uci set gluon-node-info.@location[0].share_location='1';" +
|
"uci set gluon-node-info.@location[0].share_location='1';" +
|
||||||
"uci set gluon-node-info.@location[0].latitude='" + d.lat.toFixed(9) + "';" +
|
"uci set gluon-node-info.@location[0].latitude='" + d.lat.toFixed(9) + "';" +
|
||||||
"uci set gluon-node-info.@location[0].longitude='" + d.lng.toFixed(9) + "';" +
|
"uci set gluon-node-info.@location[0].longitude='" + d.lng.toFixed(9) + "';" +
|
||||||
"uci commit gluon-node-info"
|
"uci commit gluon-node-info";
|
||||||
|
|
||||||
el.appendChild(createBox("uci", "Befehl", editUci, false))
|
el.appendChild(createBox("uci", "Uci", editUci));
|
||||||
|
|
||||||
var linkPlain = document.createElement("a")
|
var linkPlain = document.createElement("a");
|
||||||
linkPlain.textContent = "plain"
|
linkPlain.textContent = "plain";
|
||||||
linkPlain.onclick = function() {
|
linkPlain.onclick = function () {
|
||||||
switch2plain()
|
switch2plain();
|
||||||
return false
|
return false;
|
||||||
}
|
};
|
||||||
linkPlain.href = "#"
|
linkPlain.href = "#";
|
||||||
|
|
||||||
var linkUci = document.createElement("a")
|
var linkUci = document.createElement("a");
|
||||||
linkUci.textContent = "uci"
|
linkUci.textContent = "uci";
|
||||||
linkUci.onclick = function() {
|
linkUci.onclick = function () {
|
||||||
switch2uci()
|
switch2uci();
|
||||||
return false
|
return false;
|
||||||
}
|
};
|
||||||
linkUci.href = "#"
|
linkUci.href = "#";
|
||||||
|
|
||||||
var hintText = document.createElement("p")
|
function createBox(name, title, inputElem) {
|
||||||
hintText.appendChild(document.createTextNode("Du kannst zwischen "))
|
var box = document.createElement("div");
|
||||||
hintText.appendChild(linkPlain)
|
var heading = document.createElement("h3");
|
||||||
hintText.appendChild(document.createTextNode(" und "))
|
heading.textContent = title;
|
||||||
hintText.appendChild(linkUci)
|
box.appendChild(heading);
|
||||||
hintText.appendChild(document.createTextNode(" wechseln."))
|
var btn = document.createElement("button");
|
||||||
el.appendChild(hintText)
|
btn.classList.add("ion-ios-copy");
|
||||||
|
btn.title = "Kopieren";
|
||||||
function createBox(name, title, inputElem, isVisible) {
|
btn.onclick = function () {
|
||||||
var visible = typeof isVisible !== "undefined" ? isVisible : true
|
copy2clip(inputElem.id);
|
||||||
var box = document.createElement("div")
|
};
|
||||||
var heading = document.createElement("h3")
|
inputElem.id = "location-" + name;
|
||||||
heading.textContent = title
|
inputElem.readOnly = true;
|
||||||
box.appendChild(heading)
|
var line = document.createElement("p");
|
||||||
var btn = document.createElement("button")
|
line.appendChild(inputElem);
|
||||||
btn.className = "ion-ios-copy"
|
line.appendChild(btn);
|
||||||
btn.title = "Kopieren"
|
box.appendChild(line);
|
||||||
btn.onclick = function() { copy2clip(inputElem.id) }
|
box.id = "box-" + name;
|
||||||
inputElem.id = "location-" + name
|
return box;
|
||||||
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 copy2clip(id) {
|
function copy2clip(id) {
|
||||||
var copyField = document.querySelector("#" + id)
|
var copyField = document.querySelector("#" + id);
|
||||||
copyField.select()
|
copyField.select();
|
||||||
try {
|
try {
|
||||||
document.execCommand("copy")
|
document.execCommand("copy");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.warn(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function switch2plain() {
|
};
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,51 +1,53 @@
|
||||||
define(["infobox/link", "infobox/node", "infobox/location"], function (Link, Node, Location) {
|
define(["infobox/link", "infobox/node", "infobox/location"], function (Link, Node, Location) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return function (config, sidebar, router) {
|
return function (config, sidebar, router) {
|
||||||
var self = this
|
var self = this;
|
||||||
var el
|
var el;
|
||||||
|
|
||||||
function destroy() {
|
function destroy() {
|
||||||
if (el && el.parentNode) {
|
if (el && el.parentNode) {
|
||||||
el.parentNode.removeChild(el)
|
el.parentNode.removeChild(el);
|
||||||
el = undefined
|
el = undefined;
|
||||||
sidebar.reveal()
|
sidebar.reveal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function create() {
|
function create() {
|
||||||
destroy()
|
destroy();
|
||||||
sidebar.ensureVisible()
|
sidebar.ensureVisible();
|
||||||
sidebar.hide()
|
sidebar.hide();
|
||||||
|
|
||||||
el = document.createElement("div")
|
el = document.createElement("div");
|
||||||
sidebar.container.insertBefore(el, sidebar.container.firstChild)
|
sidebar.container.insertBefore(el, sidebar.container.firstChild);
|
||||||
|
|
||||||
el.scrollIntoView(false)
|
el.scrollIntoView(false);
|
||||||
el.classList.add("infobox")
|
el.classList.add("infobox");
|
||||||
el.destroy = destroy
|
el.destroy = destroy;
|
||||||
|
|
||||||
var closeButton = document.createElement("button")
|
var closeButton = document.createElement("button");
|
||||||
closeButton.classList.add("close")
|
closeButton.classList.add("close", "ion-android-close");
|
||||||
closeButton.onclick = router.reset
|
closeButton.onclick = router.reset;
|
||||||
el.appendChild(closeButton)
|
el.appendChild(closeButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.resetView = destroy
|
self.resetView = destroy;
|
||||||
|
|
||||||
self.gotoNode = function (d) {
|
self.gotoNode = function (d) {
|
||||||
create()
|
create();
|
||||||
new Node(config, el, router, d)
|
Node(config, el, router, d);
|
||||||
}
|
};
|
||||||
|
|
||||||
self.gotoLink = function (d) {
|
self.gotoLink = function (d) {
|
||||||
create()
|
create();
|
||||||
new Link(config, el, router, d)
|
Link(config, el, router, d);
|
||||||
}
|
};
|
||||||
|
|
||||||
self.gotoLocation = function (d) {
|
self.gotoLocation = function (d) {
|
||||||
create()
|
create();
|
||||||
new Location(config, el, router, d)
|
Location(config, el, router, d);
|
||||||
}
|
};
|
||||||
|
|
||||||
return self
|
return self;
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
1200
lib/infobox/node.js
1200
lib/infobox/node.js
File diff suppressed because it is too large
Load diff
|
@ -1,41 +1,43 @@
|
||||||
define(function () {
|
define(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
var self = this
|
var self = this;
|
||||||
|
|
||||||
self.render = function (el) {
|
self.render = function (el) {
|
||||||
var p = document.createElement("p")
|
var p = document.createElement("p");
|
||||||
p.setAttribute("class", "legend")
|
p.setAttribute("class", "legend");
|
||||||
el.appendChild(p)
|
el.appendChild(p);
|
||||||
|
|
||||||
var spanNew = document.createElement("span")
|
var spanNew = document.createElement("span");
|
||||||
spanNew.setAttribute("class", "legend-new")
|
spanNew.setAttribute("class", "legend-new");
|
||||||
var symbolNew = document.createElement("span")
|
var symbolNew = document.createElement("span");
|
||||||
symbolNew.setAttribute("class", "symbol")
|
symbolNew.setAttribute("class", "symbol");
|
||||||
var textNew = document.createTextNode(" Neuer Knoten")
|
var textNew = document.createTextNode(" Neuer Knoten");
|
||||||
spanNew.appendChild(symbolNew)
|
spanNew.appendChild(symbolNew);
|
||||||
spanNew.appendChild(textNew)
|
spanNew.appendChild(textNew);
|
||||||
p.appendChild(spanNew)
|
p.appendChild(spanNew);
|
||||||
|
|
||||||
var spanOnline = document.createElement("span")
|
var spanOnline = document.createElement("span");
|
||||||
spanOnline.setAttribute("class", "legend-online")
|
spanOnline.setAttribute("class", "legend-online");
|
||||||
var symbolOnline = document.createElement("span")
|
var symbolOnline = document.createElement("span");
|
||||||
symbolOnline.setAttribute("class", "symbol")
|
symbolOnline.setAttribute("class", "symbol");
|
||||||
var textOnline = document.createTextNode(" Knoten ist online")
|
var textOnline = document.createTextNode(" Knoten ist online");
|
||||||
spanOnline.appendChild(symbolOnline)
|
spanOnline.appendChild(symbolOnline);
|
||||||
spanOnline.appendChild(textOnline)
|
spanOnline.appendChild(textOnline);
|
||||||
p.appendChild(spanOnline)
|
p.appendChild(spanOnline);
|
||||||
|
|
||||||
var spanOffline = document.createElement("span")
|
var spanOffline = document.createElement("span");
|
||||||
spanOffline.setAttribute("class", "legend-offline")
|
spanOffline.setAttribute("class", "legend-offline");
|
||||||
var symbolOffline = document.createElement("span")
|
var symbolOffline = document.createElement("span");
|
||||||
symbolOffline.setAttribute("class", "symbol")
|
symbolOffline.setAttribute("class", "symbol");
|
||||||
var textOffline = document.createTextNode(" Knoten ist offline")
|
var textOffline = document.createTextNode(" Knoten ist offline");
|
||||||
spanOffline.appendChild(symbolOffline)
|
spanOffline.appendChild(symbolOffline);
|
||||||
spanOffline.appendChild(textOffline)
|
spanOffline.appendChild(textOffline);
|
||||||
p.appendChild(spanOffline)
|
p.appendChild(spanOffline);
|
||||||
}
|
};
|
||||||
|
|
||||||
return self
|
return self;
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
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 = [{
|
||||||
sort: function (a, b) {
|
name: "Knoten",
|
||||||
return linkName(a).localeCompare(linkName(b))
|
sort: function (a, b) {
|
||||||
},
|
return linkName(a).localeCompare(linkName(b));
|
||||||
reverse: false
|
},
|
||||||
},
|
reverse: false
|
||||||
{ name: "TQ",
|
},
|
||||||
sort: function (a, b) { return a.tq - b.tq},
|
{
|
||||||
reverse: true
|
name: "TQ",
|
||||||
},
|
sort: function (a, b) {
|
||||||
{ name: "Typ",
|
return a.tq - b.tq;
|
||||||
sort: function (a, b) {
|
},
|
||||||
return a.type.localeCompare(b.type)
|
reverse: true
|
||||||
},
|
},
|
||||||
reverse: false
|
{
|
||||||
},
|
name: "Typ",
|
||||||
{ name: "Entfernung",
|
sort: function (a, b) {
|
||||||
sort: function (a, b) {
|
return a.type.localeCompare(b.type);
|
||||||
return (a.distance === undefined ? -1 : a.distance) -
|
},
|
||||||
(b.distance === undefined ? -1 : b.distance)
|
reverse: false
|
||||||
},
|
},
|
||||||
reverse: true
|
{
|
||||||
}]
|
name: "Entfernung",
|
||||||
|
sort: function (a, b) {
|
||||||
|
return (a.distance === undefined ? -1 : a.distance) -
|
||||||
|
(b.distance === undefined ? -1 : b.distance);
|
||||||
|
},
|
||||||
|
reverse: true
|
||||||
|
}];
|
||||||
|
|
||||||
return function(linkScale, router) {
|
return function (linkScale, router) {
|
||||||
var table = new SortTable(headings, 2, renderRow)
|
var table = new SortTable(headings, 2, renderRow);
|
||||||
|
table.el.classList.add("link-list");
|
||||||
|
|
||||||
function renderRow(d) {
|
function renderRow(d) {
|
||||||
var td1Content = [V.h("a", {href: "#", onclick: router.link(d)}, linkName(d))]
|
var td1Content = [V.h("a", {href: "#", onclick: router.link(d)}, linkName(d))];
|
||||||
|
|
||||||
var td1 = V.h("td", td1Content)
|
var td1 = V.h("td", td1Content);
|
||||||
var td2 = V.h("td", {style: {color: linkScale(d.tq).hex()}}, showTq(d))
|
var td2 = V.h("td", {style: {color: linkScale(d.tq).hex()}}, helper.showTq(d));
|
||||||
var td3 = V.h("td", d.type)
|
var td3 = V.h("td", d.type);
|
||||||
var td4 = V.h("td", showDistance(d))
|
var td4 = V.h("td", helper.showDistance(d));
|
||||||
|
|
||||||
return V.h("tr", [td1, td2, td3, td4])
|
return V.h("tr", [td1, td2, td3, td4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.render = function (d) {
|
this.render = function (d) {
|
||||||
var el = document.createElement("div")
|
var h2 = document.createElement("h2");
|
||||||
el.last = V.h("div")
|
h2.textContent = "Verbindungen";
|
||||||
d.appendChild(el)
|
d.appendChild(h2);
|
||||||
|
|
||||||
var h2 = document.createElement("h2")
|
d.appendChild(table.el);
|
||||||
h2.textContent = "Verbindungen"
|
};
|
||||||
el.appendChild(h2)
|
|
||||||
|
|
||||||
el.appendChild(table.el)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setData = function (d) {
|
this.setData = function (d) {
|
||||||
table.setData(d.graph.links)
|
table.setData(d.graph.links);
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
define(["leaflet"], function (L) {
|
define(["leaflet"], function (L) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return L.CircleMarker.extend({
|
return L.CircleMarker.extend({
|
||||||
outerCircle: {
|
outerCircle: {
|
||||||
stroke: false,
|
stroke: false,
|
||||||
|
@ -29,31 +31,31 @@ define(["leaflet"], function (L) {
|
||||||
fillOpacity: 0.2
|
fillOpacity: 0.2
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function(latlng) {
|
initialize: function (latlng) {
|
||||||
this.accuracyCircle = L.circle(latlng, 0, this.accuracyCircle)
|
this.accuracyCircle = L.circle(latlng, 0, this.accuracyCircle);
|
||||||
this.outerCircle = L.circleMarker(latlng, this.outerCircle)
|
this.outerCircle = L.circleMarker(latlng, this.outerCircle);
|
||||||
L.CircleMarker.prototype.initialize.call(this, latlng, this.innerCircle)
|
L.CircleMarker.prototype.initialize.call(this, latlng, this.innerCircle);
|
||||||
|
|
||||||
this.on("remove", function() {
|
this.on("remove", function () {
|
||||||
this._map.removeLayer(this.accuracyCircle)
|
this._map.removeLayer(this.accuracyCircle);
|
||||||
this._map.removeLayer(this.outerCircle)
|
this._map.removeLayer(this.outerCircle);
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setLatLng: function(latlng) {
|
setLatLng: function (latlng) {
|
||||||
this.accuracyCircle.setLatLng(latlng)
|
this.accuracyCircle.setLatLng(latlng);
|
||||||
this.outerCircle.setLatLng(latlng)
|
this.outerCircle.setLatLng(latlng);
|
||||||
L.CircleMarker.prototype.setLatLng.call(this, latlng)
|
L.CircleMarker.prototype.setLatLng.call(this, latlng);
|
||||||
},
|
},
|
||||||
|
|
||||||
setAccuracy: function(accuracy) {
|
setAccuracy: function (accuracy) {
|
||||||
this.accuracyCircle.setRadius(accuracy)
|
this.accuracyCircle.setRadius(accuracy);
|
||||||
},
|
},
|
||||||
|
|
||||||
onAdd: function(map) {
|
onAdd: function (map) {
|
||||||
this.accuracyCircle.addTo(map).bringToBack()
|
this.accuracyCircle.addTo(map).bringToBack();
|
||||||
this.outerCircle.addTo(map)
|
this.outerCircle.addTo(map);
|
||||||
L.CircleMarker.prototype.onAdd.call(this, map)
|
L.CircleMarker.prototype.onAdd.call(this, map);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
414
lib/main.js
414
lib/main.js
|
@ -1,232 +1,242 @@
|
||||||
define(["moment", "router", "leaflet", "gui", "numeral"],
|
define(["moment", "router", "leaflet", "gui", "helper", "moment.de"],
|
||||||
function (moment, Router, L, GUI, numeral) {
|
function (moment, Router, L, GUI, helper) {
|
||||||
return function (config) {
|
"use strict";
|
||||||
function handleData(data) {
|
|
||||||
var dataNodes = {}
|
return function (config) {
|
||||||
dataNodes.nodes = []
|
function handleData(data) {
|
||||||
dataNodes.nodeIds = []
|
var dataNodes = {};
|
||||||
var dataGraph = {}
|
dataNodes.nodes = [];
|
||||||
dataGraph.batadv = {}
|
dataNodes.nodeIds = [];
|
||||||
dataGraph.batadv.nodes = []
|
var dataGraph = {};
|
||||||
dataGraph.batadv.links = []
|
dataGraph.batadv = {};
|
||||||
|
dataGraph.batadv.nodes = [];
|
||||||
|
dataGraph.batadv.links = [];
|
||||||
|
|
||||||
|
|
||||||
|
function rearrangeLinks(d) {
|
||||||
function rearrangeLinks(d) {
|
d.source += dataGraph.batadv.nodes.length;
|
||||||
d.source += dataGraph.batadv.nodes.length
|
d.target += dataGraph.batadv.nodes.length;
|
||||||
d.target += dataGraph.batadv.nodes.length
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < data.length; ++i) {
|
|
||||||
var vererr
|
|
||||||
if(i % 2)
|
|
||||||
if (data[i].version !== 1) {
|
|
||||||
vererr = "Unsupported graph version: " + data[i].version
|
|
||||||
console.log(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
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if (data[i].version !== 2) {
|
|
||||||
vererr = "Unsupported nodes version: " + data[i].version
|
|
||||||
console.log(vererr) //silent fail
|
|
||||||
} else {
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
if(node.flags.online === true)
|
|
||||||
dataNodes.nodes[position] = node
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodes = dataNodes.nodes.filter( function (d) {
|
for (var i = 0; i < data.length; ++i) {
|
||||||
return "firstseen" in d && "lastseen" in d
|
var vererr;
|
||||||
})
|
if (i % 2) {
|
||||||
|
if (data[i].version !== 1) {
|
||||||
nodes.forEach( function(node) {
|
vererr = "Unsupported graph version: " + data[i].version;
|
||||||
node.firstseen = moment.utc(node.firstseen).local()
|
console.error(vererr); //silent fail
|
||||||
node.lastseen = moment.utc(node.lastseen).local()
|
} else {
|
||||||
})
|
data[i].batadv.links.forEach(rearrangeLinks);
|
||||||
|
dataGraph.batadv.nodes = dataGraph.batadv.nodes.concat(data[i].batadv.nodes);
|
||||||
var now = moment()
|
dataGraph.batadv.links = dataGraph.batadv.links.concat(data[i].batadv.links);
|
||||||
var age = moment(now).subtract(config.maxAge, "days")
|
dataGraph.timestamp = data[i].timestamp;
|
||||||
|
}
|
||||||
var newnodes = limit("firstseen", age, sortByKey("firstseen", nodes).filter(online))
|
} else if (data[i].version !== 2) {
|
||||||
var lostnodes = limit("lastseen", age, sortByKey("lastseen", nodes).filter(offline))
|
vererr = "Unsupported nodes version: " + data[i].version;
|
||||||
|
console.error(vererr); //silent fail
|
||||||
var graphnodes = {}
|
} else {
|
||||||
|
data[i].nodes.forEach(fillData);
|
||||||
dataNodes.nodes.forEach( function (d) {
|
dataNodes.timestamp = data[i].timestamp;
|
||||||
graphnodes[d.nodeinfo.node_id] = d
|
|
||||||
})
|
|
||||||
|
|
||||||
var graph = dataGraph.batadv
|
|
||||||
|
|
||||||
graph.nodes.forEach( function (d) {
|
|
||||||
if (d.node_id in graphnodes) {
|
|
||||||
d.node = graphnodes[d.node_id]
|
|
||||||
if (d.unseen) {
|
|
||||||
d.node.flags.online = true
|
|
||||||
d.node.flags.unseen = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
graph.links.forEach( function (d) {
|
function fillData(node) {
|
||||||
d.source = graph.nodes[d.source]
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (graph.nodes[d.target].node)
|
var nodes = dataNodes.nodes.filter(function (d) {
|
||||||
d.target = graph.nodes[d.target]
|
return "firstseen" in d && "lastseen" in d;
|
||||||
else
|
});
|
||||||
d.target = undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
var links = graph.links.filter( function (d) {
|
nodes.forEach(function (node) {
|
||||||
return d.target !== undefined
|
node.firstseen = moment.utc(node.firstseen).local();
|
||||||
})
|
node.lastseen = moment.utc(node.lastseen).local();
|
||||||
|
});
|
||||||
|
|
||||||
links.forEach( function (d) {
|
var now = moment();
|
||||||
var unknown = (d.source.node === undefined)
|
var age = moment(now).subtract(config.maxAge, "days");
|
||||||
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 ||
|
var newnodes = helper.limit("firstseen", age, helper.sortByKey("firstseen", nodes).filter(helper.online));
|
||||||
!d.source.node.nodeinfo.location ||
|
var lostnodes = helper.limit("lastseen", age, helper.sortByKey("lastseen", nodes).filter(helper.offline));
|
||||||
!d.target.node.nodeinfo.location ||
|
|
||||||
|
var graphnodes = {};
|
||||||
|
|
||||||
|
dataNodes.nodes.forEach(function (d) {
|
||||||
|
graphnodes[d.nodeinfo.node_id] = d;
|
||||||
|
});
|
||||||
|
|
||||||
|
var graph = dataGraph.batadv;
|
||||||
|
|
||||||
|
graph.nodes.forEach(function (d) {
|
||||||
|
if (d.node_id in graphnodes) {
|
||||||
|
d.node = graphnodes[d.node_id];
|
||||||
|
if (d.unseen) {
|
||||||
|
d.node.flags.online = true;
|
||||||
|
d.node.flags.unseen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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("-");
|
||||||
|
|
||||||
|
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.latitude) ||
|
||||||
isNaN(d.source.node.nodeinfo.location.longitude) ||
|
isNaN(d.source.node.nodeinfo.location.longitude) ||
|
||||||
isNaN(d.target.node.nodeinfo.location.latitude) ||
|
isNaN(d.target.node.nodeinfo.location.latitude) ||
|
||||||
isNaN(d.target.node.nodeinfo.location.longitude))
|
isNaN(d.target.node.nodeinfo.location.longitude)) {
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
d.latlngs = []
|
d.latlngs = [];
|
||||||
d.latlngs.push(L.latLng(d.source.node.nodeinfo.location.latitude, d.source.node.nodeinfo.location.longitude))
|
d.latlngs.push(L.latLng(d.source.node.nodeinfo.location.latitude, d.source.node.nodeinfo.location.longitude));
|
||||||
d.latlngs.push(L.latLng(d.target.node.nodeinfo.location.latitude, d.target.node.nodeinfo.location.longitude))
|
d.latlngs.push(L.latLng(d.target.node.nodeinfo.location.latitude, d.target.node.nodeinfo.location.longitude));
|
||||||
|
|
||||||
d.distance = d.latlngs[0].distanceTo(d.latlngs[1])
|
d.distance = d.latlngs[0].distanceTo(d.latlngs[1]);
|
||||||
})
|
});
|
||||||
|
|
||||||
nodes.forEach( function (d) {
|
nodes.forEach(function (d) {
|
||||||
d.neighbours = []
|
d.neighbours = [];
|
||||||
if (d.statistics) {
|
if (d.statistics) {
|
||||||
/*eslint camelcase:0*/
|
/*eslint camelcase:0*/
|
||||||
if ("gateway" in d.statistics && d.statistics.gateway in graphnodes)
|
if ("gateway" in d.statistics && d.statistics.gateway in graphnodes) {
|
||||||
d.statistics.gateway = {"node": graphnodes[d.statistics.gateway], "id": d.statistics.gateway}
|
d.statistics.gateway = {"node": graphnodes[d.statistics.gateway], "id": d.statistics.gateway};
|
||||||
if ("nexthop" in d.statistics && d.statistics.nexthop in graphnodes)
|
}
|
||||||
d.statistics.nexthop = {"node": graphnodes[d.statistics.nexthop], "id": d.statistics.nexthop}
|
if ("nexthop" in d.statistics && d.statistics.nexthop in graphnodes) {
|
||||||
if ("gateway_nexthop" in d.statistics && d.statistics.gateway_nexthop in graphnodes)
|
d.statistics.nexthop = {"node": graphnodes[d.statistics.nexthop], "id": d.statistics.nexthop};
|
||||||
d.statistics.gateway_nexthop = {"node": graphnodes[d.statistics.gateway_nexthop], "id": d.statistics.gateway_nexthop}
|
}
|
||||||
}
|
if ("gateway_nexthop" in d.statistics && d.statistics.gateway_nexthop in graphnodes) {
|
||||||
})
|
d.statistics.gateway_nexthop = {
|
||||||
|
"node": graphnodes[d.statistics.gateway_nexthop],
|
||||||
|
"id": d.statistics.gateway_nexthop
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
links.forEach( function (d) {
|
links.forEach(function (d) {
|
||||||
if (d.type === "tunnel" || d.vpn) {
|
if (d.type === "tunnel" || d.vpn) {
|
||||||
d.type = "VPN"
|
d.type = "VPN";
|
||||||
d.isVPN = true
|
d.isVPN = true;
|
||||||
} else if (d.type === "fastd") {
|
} else if (d.type === "fastd") {
|
||||||
d.type = "fastd"
|
d.type = "fastd";
|
||||||
d.isVPN = true
|
d.isVPN = true;
|
||||||
} else if (d.type === "l2tp") {
|
} else if (d.type === "l2tp") {
|
||||||
d.type = "L2TP"
|
d.type = "L2TP";
|
||||||
d.isVPN = true
|
d.isVPN = true;
|
||||||
} else if (d.type === "gre") {
|
} else if (d.type === "gre") {
|
||||||
d.type = "GRE"
|
d.type = "GRE";
|
||||||
d.isVPN = true
|
d.isVPN = true;
|
||||||
} else if (d.type === "wireless") {
|
} else if (d.type === "wireless") {
|
||||||
d.type = "Wifi"
|
d.type = "Wifi";
|
||||||
d.isVPN = false
|
d.isVPN = false;
|
||||||
} else if (d.type === "other") {
|
} else if (d.type === "other") {
|
||||||
d.type = "Kabel"
|
d.type = "Kabel";
|
||||||
d.isVPN = false
|
d.isVPN = false;
|
||||||
} else {
|
} else {
|
||||||
d.type = "N/A"
|
d.type = "N/A";
|
||||||
d.isVPN = false
|
d.isVPN = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d.isVPN && d.target.node)
|
if (d.isVPN && d.target.node) {
|
||||||
d.target.node.flags.uplink = true
|
d.target.node.flags.uplink = true;
|
||||||
|
}
|
||||||
|
|
||||||
var unknown = (d.source.node === undefined)
|
var unknown = (d.source.node === undefined);
|
||||||
if (unknown) {
|
if (unknown) {
|
||||||
d.target.node.neighbours.push({ id: d.source.id, link: d, incoming: true })
|
d.target.node.neighbours.push({id: d.source.id, link: d, incoming: true});
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
d.source.node.neighbours.push({ node: d.target.node, link: d, incoming: false })
|
d.source.node.neighbours.push({node: d.target.node, link: d, incoming: false});
|
||||||
d.target.node.neighbours.push({ node: d.source.node, link: d, incoming: true })
|
d.target.node.neighbours.push({node: d.source.node, link: d, incoming: true});
|
||||||
if (!d.isVPN)
|
if (!d.isVPN) {
|
||||||
d.source.node.meshlinks = d.source.node.meshlinks ? d.source.node.meshlinks + 1 : 1
|
d.source.node.meshlinks = d.source.node.meshlinks ? d.source.node.meshlinks + 1 : 1;
|
||||||
})
|
}
|
||||||
|
});
|
||||||
|
|
||||||
links.sort( function (a, b) {
|
links.sort(function (a, b) {
|
||||||
return b.tq - a.tq
|
return b.tq - a.tq;
|
||||||
})
|
});
|
||||||
|
|
||||||
return { now: now,
|
return {
|
||||||
timestamp: moment.utc(dataNodes.timestamp).local(),
|
now: now,
|
||||||
nodes: {
|
timestamp: moment.utc(dataNodes.timestamp).local(),
|
||||||
all: nodes,
|
nodes: {
|
||||||
new: newnodes,
|
all: nodes,
|
||||||
lost: lostnodes
|
new: newnodes,
|
||||||
},
|
lost: lostnodes
|
||||||
graph: {
|
},
|
||||||
links: links,
|
graph: {
|
||||||
nodes: graph.nodes
|
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)
|
if (typeof config.dataPath === "string") {
|
||||||
config.dataPath = [config.dataPath]
|
config.dataPath = [config.dataPath];
|
||||||
|
}
|
||||||
|
|
||||||
for (var i in config.dataPath) {
|
for (var i in config.dataPath) {
|
||||||
urls.push(config.dataPath[i] + "nodes.json")
|
urls.push(config.dataPath[i] + "nodes.json");
|
||||||
urls.push(config.dataPath[i] + "graph.json")
|
urls.push(config.dataPath[i] + "graph.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
return Promise.all(urls.map(getJSON))
|
return Promise.all(urls.map(helper.getJSON))
|
||||||
.then(handleData)
|
.then(handleData);
|
||||||
}
|
}
|
||||||
|
|
||||||
update()
|
update()
|
||||||
.then(function (d) {
|
.then(function (d) {
|
||||||
var gui = new GUI(config, router)
|
var gui = new GUI(config, router);
|
||||||
gui.setData(d)
|
gui.setData(d);
|
||||||
router.setData(d)
|
router.setData(d);
|
||||||
router.start()
|
router.start();
|
||||||
|
|
||||||
window.setInterval(function () {
|
window.setInterval(function () {
|
||||||
update().then(function (d) {
|
update().then(function (d) {
|
||||||
gui.setData(d)
|
gui.setData(d);
|
||||||
router.setData(d)
|
router.setData(d);
|
||||||
})
|
});
|
||||||
}, 60000)
|
}, 60000);
|
||||||
})
|
})
|
||||||
.catch(function (e) {
|
.catch(function (e) {
|
||||||
document.body.textContent = e
|
document.body.textContent = e;
|
||||||
console.log(e)
|
console.warn(e);
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
730
lib/map.js
730
lib/map.js
|
@ -1,74 +1,74 @@
|
||||||
define(["map/clientlayer", "map/labelslayer",
|
define(["map/clientlayer", "map/labelslayer",
|
||||||
"d3", "leaflet", "moment", "locationmarker", "rbush",
|
"d3", "leaflet", "moment", "locationmarker", "rbush", "helper",
|
||||||
"leaflet.label", "leaflet.providers"],
|
"leaflet.label", "leaflet.providers", "moment.de"],
|
||||||
function (ClientLayer, LabelsLayer, d3, L, moment, LocationMarker, rbush) {
|
function (ClientLayer, LabelsLayer, d3, L, moment, LocationMarker, rbush, helper) {
|
||||||
var options = { worldCopyJump: true,
|
"use strict";
|
||||||
zoomControl: false
|
|
||||||
}
|
var options = {
|
||||||
|
worldCopyJump: true,
|
||||||
|
zoomControl: false
|
||||||
|
};
|
||||||
|
|
||||||
var AddLayerButton = L.Control.extend({
|
var AddLayerButton = L.Control.extend({
|
||||||
options: {
|
options: {
|
||||||
position: "bottomright"
|
position: "bottomright"
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function (f, options) {
|
initialize: function (f, options) {
|
||||||
L.Util.setOptions(this, options)
|
L.Util.setOptions(this, options);
|
||||||
this.f = f
|
this.f = f;
|
||||||
},
|
},
|
||||||
|
|
||||||
onAdd: function () {
|
onAdd: function () {
|
||||||
var button = L.DomUtil.create("button", "add-layer")
|
var button = L.DomUtil.create("button", "add-layer ion-android-add shadow");
|
||||||
button.textContent = "\uF2C7"
|
|
||||||
|
|
||||||
// L.DomEvent.disableClickPropagation(button)
|
// L.DomEvent.disableClickPropagation(button)
|
||||||
// Click propagation isn't disabled as this causes problems with the
|
// Click propagation isn't disabled as this causes problems with the
|
||||||
// location picking mode; instead propagation is stopped in onClick().
|
// location picking mode; instead propagation is stopped in onClick().
|
||||||
L.DomEvent.addListener(button, "click", this.f, this)
|
L.DomEvent.addListener(button, "click", this.f, this);
|
||||||
|
|
||||||
this.button = button
|
this.button = button;
|
||||||
|
|
||||||
return button
|
return button;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
var LocateButton = L.Control.extend({
|
var LocateButton = L.Control.extend({
|
||||||
options: {
|
options: {
|
||||||
position: "bottomright"
|
position: "bottomright"
|
||||||
},
|
},
|
||||||
|
|
||||||
active: false,
|
active: false,
|
||||||
button: undefined,
|
button: undefined,
|
||||||
|
|
||||||
initialize: function (f, options) {
|
initialize: function (f, options) {
|
||||||
L.Util.setOptions(this, options)
|
L.Util.setOptions(this, options);
|
||||||
this.f = f
|
this.f = f;
|
||||||
},
|
},
|
||||||
|
|
||||||
onAdd: function () {
|
onAdd: function () {
|
||||||
var button = L.DomUtil.create("button", "locate-user")
|
var button = L.DomUtil.create("button", "locate-user ion-android-locate shadow");
|
||||||
button.textContent = "\uF2E9"
|
L.DomEvent.disableClickPropagation(button);
|
||||||
|
L.DomEvent.addListener(button, "click", this.onClick, this);
|
||||||
|
|
||||||
L.DomEvent.disableClickPropagation(button)
|
this.button = button;
|
||||||
L.DomEvent.addListener(button, "click", this.onClick, this)
|
|
||||||
|
|
||||||
this.button = button
|
return button;
|
||||||
|
},
|
||||||
|
|
||||||
return button
|
update: function () {
|
||||||
},
|
this.button.classList.toggle("active", this.active);
|
||||||
|
},
|
||||||
|
|
||||||
update: function() {
|
set: function (v) {
|
||||||
this.button.classList.toggle("active", this.active)
|
this.active = v;
|
||||||
},
|
this.update();
|
||||||
|
},
|
||||||
|
|
||||||
set: function(v) {
|
onClick: function () {
|
||||||
this.active = v
|
this.f(!this.active);
|
||||||
this.update()
|
}
|
||||||
},
|
});
|
||||||
|
|
||||||
onClick: function () {
|
|
||||||
this.f(!this.active)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var CoordsPickerButton = L.Control.extend({
|
var CoordsPickerButton = L.Control.extend({
|
||||||
options: {
|
options: {
|
||||||
|
@ -79,470 +79,546 @@ define(["map/clientlayer", "map/labelslayer",
|
||||||
button: undefined,
|
button: undefined,
|
||||||
|
|
||||||
initialize: function (f, options) {
|
initialize: function (f, options) {
|
||||||
L.Util.setOptions(this, options)
|
L.Util.setOptions(this, options);
|
||||||
this.f = f
|
this.f = f;
|
||||||
},
|
},
|
||||||
|
|
||||||
onAdd: function () {
|
onAdd: function () {
|
||||||
var button = L.DomUtil.create("button", "coord-picker")
|
var button = L.DomUtil.create("button", "coord-picker ion-pin shadow");
|
||||||
button.textContent = "\uF2A6"
|
|
||||||
|
|
||||||
// Click propagation isn't disabled as this causes problems with the
|
// Click propagation isn't disabled as this causes problems with the
|
||||||
// location picking mode; instead propagation is stopped in onClick().
|
// location picking mode; instead propagation is stopped in onClick().
|
||||||
L.DomEvent.addListener(button, "click", this.onClick, this)
|
L.DomEvent.addListener(button, "click", this.onClick, this);
|
||||||
|
|
||||||
this.button = button
|
this.button = button;
|
||||||
|
|
||||||
return button
|
return button;
|
||||||
},
|
},
|
||||||
|
|
||||||
update: function() {
|
update: function () {
|
||||||
this.button.classList.toggle("active", this.active)
|
this.button.classList.toggle("active", this.active);
|
||||||
},
|
},
|
||||||
|
|
||||||
set: function(v) {
|
set: function (v) {
|
||||||
this.active = v
|
this.active = v;
|
||||||
this.update()
|
this.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick: function (e) {
|
onClick: function (e) {
|
||||||
L.DomEvent.stopPropagation(e)
|
L.DomEvent.stopPropagation(e);
|
||||||
this.f(!this.active)
|
this.f(!this.active);
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
});
|
||||||
|
|
||||||
function mkMarker(dict, iconFunc, router) {
|
function mkMarker(dict, iconFunc, router) {
|
||||||
return function (d) {
|
return function (d) {
|
||||||
var m = L.circleMarker([d.nodeinfo.location.latitude, d.nodeinfo.location.longitude], iconFunc(d))
|
var m = L.circleMarker([d.nodeinfo.location.latitude, d.nodeinfo.location.longitude], iconFunc(d));
|
||||||
|
|
||||||
m.resetStyle = function () {
|
m.resetStyle = function () {
|
||||||
m.setStyle(iconFunc(d))
|
m.setStyle(iconFunc(d));
|
||||||
}
|
};
|
||||||
|
|
||||||
m.on("click", router.node(d))
|
m.on("click", router.node(d));
|
||||||
m.bindLabel(d.nodeinfo.hostname)
|
m.bindLabel(d.nodeinfo.hostname);
|
||||||
|
|
||||||
dict[d.nodeinfo.node_id] = m
|
dict[d.nodeinfo.node_id] = m;
|
||||||
|
|
||||||
return m
|
return m;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLinksToMap(dict, linkScale, graph, router) {
|
function addLinksToMap(dict, linkScale, graph, router) {
|
||||||
graph = graph.filter( function (d) {
|
graph = graph.filter(function (d) {
|
||||||
return "distance" in d && d.type !== "VPN"
|
return "distance" in d && d.type !== "VPN";
|
||||||
})
|
});
|
||||||
|
|
||||||
var lines = graph.map( function (d) {
|
return graph.map(function (d) {
|
||||||
var opts = { color: d.type === "Kabel" ? "#50B0F0" : linkScale(d.tq).hex(),
|
var opts = {
|
||||||
weight: 4,
|
color: d.type === "Kabel" ? "#50B0F0" : linkScale(d.tq).hex(),
|
||||||
opacity: 0.5,
|
weight: 4,
|
||||||
dashArray: "none"
|
opacity: 0.5,
|
||||||
}
|
dashArray: "none"
|
||||||
|
};
|
||||||
|
|
||||||
var line = L.polyline(d.latlngs, opts)
|
var line = L.polyline(d.latlngs, opts);
|
||||||
|
|
||||||
line.resetStyle = function () {
|
line.resetStyle = function () {
|
||||||
line.setStyle(opts)
|
line.setStyle(opts);
|
||||||
}
|
};
|
||||||
|
|
||||||
line.bindLabel(d.source.node.nodeinfo.hostname + " – " + d.target.node.nodeinfo.hostname + "<br><strong>" + showDistance(d) + " / " + showTq(d) + "</strong>")
|
line.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))
|
line.on("click", router.link(d));
|
||||||
|
|
||||||
dict[d.id] = line
|
dict[d.id] = line;
|
||||||
|
|
||||||
return 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 iconOnline = {
|
||||||
var iconOffline = { color: "#D43E2A", fillColor: "#D43E2A", radius: 3, fillOpacity: 0.5, opacity: 0.5, weight: 1, className: "stroke-first" }
|
color: "#1566A9",
|
||||||
var iconLost = { color: "#D43E2A", fillColor: "#D43E2A", radius: 6, fillOpacity: 0.8, opacity: 0.8, weight: 1, className: "stroke-first" }
|
fillColor: "#1566A9",
|
||||||
var iconAlert = { color: "#D43E2A", fillColor: "#D43E2A", radius: 6, fillOpacity: 0.8, opacity: 0.8, weight: 2, className: "stroke-first node-alert" }
|
radius: 6,
|
||||||
var iconNew = { color: "#1566A9", fillColor: "#93E929", radius: 6, fillOpacity: 1.0, opacity: 0.5, weight: 2 }
|
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) {
|
return function (config, linkScale, sidebar, router, buttons) {
|
||||||
var self = this
|
var self = this;
|
||||||
var barycenter
|
var barycenter;
|
||||||
var groupOnline, groupOffline, groupNew, groupLost, groupLines
|
var groupOnline, groupOffline, groupNew, groupLost, groupLines;
|
||||||
var savedView
|
var savedView;
|
||||||
|
|
||||||
var map, userLocation
|
var map, userLocation;
|
||||||
var layerControl
|
var layerControl;
|
||||||
var customLayers = {}
|
var customLayers = {};
|
||||||
var baseLayers = {}
|
var baseLayers = {};
|
||||||
|
|
||||||
var locateUserButton = new LocateButton(function (d) {
|
var locateUserButton = new LocateButton(function (d) {
|
||||||
if (d)
|
if (d) {
|
||||||
enableTracking()
|
enableTracking();
|
||||||
else
|
} else {
|
||||||
disableTracking()
|
disableTracking();
|
||||||
})
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var mybuttons = []
|
var mybuttons = [];
|
||||||
|
|
||||||
function addButton(button) {
|
function addButton(button) {
|
||||||
var el = button.onAdd()
|
var el = button.onAdd();
|
||||||
mybuttons.push(el)
|
mybuttons.push(el);
|
||||||
buttons.appendChild(el)
|
buttons.appendChild(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearButtons() {
|
function clearButtons() {
|
||||||
mybuttons.forEach( function (d) {
|
mybuttons.forEach(function (d) {
|
||||||
buttons.removeChild(d)
|
buttons.removeChild(d);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var showCoordsPickerButton = new CoordsPickerButton(function (d) {
|
var showCoordsPickerButton = new CoordsPickerButton(function (d) {
|
||||||
if (d)
|
if (d) {
|
||||||
enableCoords()
|
enableCoords();
|
||||||
else
|
} else {
|
||||||
disableCoords()
|
disableCoords();
|
||||||
})
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function saveView() {
|
function saveView() {
|
||||||
savedView = {center: map.getCenter(),
|
savedView = {
|
||||||
zoom: map.getZoom()}
|
center: map.getCenter(),
|
||||||
|
zoom: map.getZoom()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableTracking() {
|
function enableTracking() {
|
||||||
map.locate({watch: true,
|
map.locate({
|
||||||
enableHighAccuracy: true,
|
watch: true,
|
||||||
setView: true
|
enableHighAccuracy: true,
|
||||||
})
|
setView: true
|
||||||
locateUserButton.set(true)
|
});
|
||||||
|
locateUserButton.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableTracking() {
|
function disableTracking() {
|
||||||
map.stopLocate()
|
map.stopLocate();
|
||||||
locationError()
|
locationError();
|
||||||
locateUserButton.set(false)
|
locateUserButton.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableCoords() {
|
function enableCoords() {
|
||||||
map.getContainer().classList.add("pick-coordinates")
|
map.getContainer().classList.add("pick-coordinates");
|
||||||
map.on("click", showCoordinates)
|
map.on("click", showCoordinates);
|
||||||
showCoordsPickerButton.set(true)
|
showCoordsPickerButton.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableCoords() {
|
function disableCoords() {
|
||||||
map.getContainer().classList.remove("pick-coordinates")
|
map.getContainer().classList.remove("pick-coordinates");
|
||||||
map.off("click", showCoordinates)
|
map.off("click", showCoordinates);
|
||||||
showCoordsPickerButton.set(false)
|
showCoordsPickerButton.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showCoordinates(e) {
|
function showCoordinates(e) {
|
||||||
router.gotoLocation(e.latlng)
|
router.gotoLocation(e.latlng);
|
||||||
// window.prompt("Koordinaten (Lat, Lng)", e.latlng.lat.toFixed(9) + ", " + e.latlng.lng.toFixed(9))
|
// window.prompt("Koordinaten (Lat, Lng)", e.latlng.lat.toFixed(9) + ", " + e.latlng.lng.toFixed(9))
|
||||||
disableCoords()
|
disableCoords();
|
||||||
}
|
}
|
||||||
|
|
||||||
function locationFound(e) {
|
function locationFound(e) {
|
||||||
if (!userLocation)
|
if (!userLocation) {
|
||||||
userLocation = new LocationMarker(e.latlng).addTo(map)
|
userLocation = new LocationMarker(e.latlng).addTo(map);
|
||||||
|
}
|
||||||
|
|
||||||
userLocation.setLatLng(e.latlng)
|
userLocation.setLatLng(e.latlng);
|
||||||
userLocation.setAccuracy(e.accuracy)
|
userLocation.setAccuracy(e.accuracy);
|
||||||
}
|
}
|
||||||
|
|
||||||
function locationError() {
|
function locationError() {
|
||||||
if (userLocation) {
|
if (userLocation) {
|
||||||
map.removeLayer(userLocation)
|
map.removeLayer(userLocation);
|
||||||
userLocation = null
|
userLocation = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLayer(layerName) {
|
function addLayer(layerName) {
|
||||||
if (layerName in baseLayers)
|
if (layerName in baseLayers) {
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (layerName in customLayers)
|
if (layerName in customLayers) {
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var layer = L.tileLayer.provider(layerName)
|
var layer = L.tileLayer.provider(layerName);
|
||||||
layerControl.addBaseLayer(layer, layerName)
|
layerControl.addBaseLayer(layer, layerName);
|
||||||
customLayers[layerName] = layer
|
customLayers[layerName] = layer;
|
||||||
|
|
||||||
if (localStorageTest())
|
if (helper.localStorageTest()) {
|
||||||
localStorage.setItem("map/customLayers", JSON.stringify(Object.keys(customLayers)))
|
localStorage.setItem("map/customLayers", JSON.stringify(Object.keys(customLayers)));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function contextMenuGotoLocation(e) {
|
function contextMenuGotoLocation(e) {
|
||||||
router.gotoLocation(e.latlng)
|
router.gotoLocation(e.latlng);
|
||||||
}
|
}
|
||||||
|
|
||||||
var el = document.createElement("div")
|
var el = document.createElement("div");
|
||||||
el.classList.add("map")
|
el.classList.add("map");
|
||||||
|
|
||||||
map = L.map(el, options)
|
map = L.map(el, options);
|
||||||
|
|
||||||
var layers = config.mapLayers.map( function (d) {
|
var layers = config.mapLayers.map(function (d) {
|
||||||
return {
|
return {
|
||||||
"name": d.name,
|
"name": d.name,
|
||||||
"layer": "url" in d ? L.tileLayer(d.url, d.config) : L.tileLayer.provider(d.name)
|
"layer": "url" in d ? L.tileLayer(d.url, d.config) : L.tileLayer.provider(d.name)
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
layers[0].layer.addTo(map)
|
layers[0].layer.addTo(map);
|
||||||
|
|
||||||
layers.forEach( function (d) {
|
layers.forEach(function (d) {
|
||||||
baseLayers[d.name] = d.layer
|
baseLayers[d.name] = d.layer;
|
||||||
})
|
});
|
||||||
|
|
||||||
map.on("locationfound", locationFound)
|
map.on("locationfound", locationFound);
|
||||||
map.on("locationerror", locationError)
|
map.on("locationerror", locationError);
|
||||||
map.on("dragend", saveView)
|
map.on("dragend", saveView);
|
||||||
map.on("contextmenu", contextMenuGotoLocation)
|
map.on("contextmenu", contextMenuGotoLocation);
|
||||||
|
|
||||||
addButton(locateUserButton)
|
addButton(locateUserButton);
|
||||||
addButton(showCoordsPickerButton)
|
addButton(showCoordsPickerButton);
|
||||||
|
|
||||||
addButton(new AddLayerButton(function () {
|
addButton(new AddLayerButton(function () {
|
||||||
/*eslint no-alert:0*/
|
/*eslint no-alert:0*/
|
||||||
var layerName = prompt("Leaflet Provider:")
|
var layerName = prompt("Leaflet Provider:");
|
||||||
addLayer(layerName)
|
addLayer(layerName);
|
||||||
}))
|
}));
|
||||||
|
|
||||||
layerControl = L.control.layers(baseLayers, [], {position: "bottomright"})
|
layerControl = L.control.layers(baseLayers, [], {position: "bottomright"});
|
||||||
layerControl.addTo(map)
|
layerControl.addTo(map);
|
||||||
|
|
||||||
if (localStorageTest()) {
|
if (helper.localStorageTest()) {
|
||||||
var d = JSON.parse(localStorage.getItem("map/customLayers"))
|
var d = JSON.parse(localStorage.getItem("map/customLayers"));
|
||||||
|
|
||||||
if (d)
|
|
||||||
d.forEach(addLayer)
|
|
||||||
|
|
||||||
d = JSON.parse(localStorage.getItem("map/selectedLayer"))
|
|
||||||
d = d && d.name in baseLayers ? baseLayers[d.name] : d && d.name in customLayers ? customLayers[d.name] : false
|
|
||||||
|
|
||||||
if (d) {
|
if (d) {
|
||||||
map.removeLayer(layers[0].layer)
|
d.forEach(addLayer);
|
||||||
map.addLayer(d)
|
}
|
||||||
|
|
||||||
|
d = JSON.parse(localStorage.getItem("map/selectedLayer"));
|
||||||
|
d = d && d.name in baseLayers ? baseLayers[d.name] : d && d.name in customLayers ? customLayers[d.name] : false;
|
||||||
|
|
||||||
|
if (d) {
|
||||||
|
map.removeLayer(layers[0].layer);
|
||||||
|
map.addLayer(d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientLayer = new ClientLayer({minZoom: 15})
|
var clientLayer = new ClientLayer({minZoom: 15});
|
||||||
clientLayer.addTo(map)
|
clientLayer.addTo(map);
|
||||||
clientLayer.setZIndex(5)
|
clientLayer.setZIndex(5);
|
||||||
|
|
||||||
var labelsLayer = new LabelsLayer({})
|
var labelsLayer = new LabelsLayer({});
|
||||||
labelsLayer.addTo(map)
|
labelsLayer.addTo(map);
|
||||||
labelsLayer.setZIndex(6)
|
labelsLayer.setZIndex(6);
|
||||||
|
|
||||||
map.on("baselayerchange", function(e) {
|
map.on("baselayerchange", function (e) {
|
||||||
map.options.maxZoom = e.layer.options.maxZoom
|
map.options.maxZoom = e.layer.options.maxZoom;
|
||||||
clientLayer.options.maxZoom = map.options.maxZoom
|
clientLayer.options.maxZoom = map.options.maxZoom;
|
||||||
labelsLayer.options.maxZoom = map.options.maxZoom
|
labelsLayer.options.maxZoom = map.options.maxZoom;
|
||||||
if (map.getZoom() > map.options.maxZoom) map.setZoom(map.options.maxZoom)
|
if (map.getZoom() > map.options.maxZoom) {
|
||||||
if (localStorageTest())
|
map.setZoom(map.options.maxZoom);
|
||||||
localStorage.setItem("map/selectedLayer", JSON.stringify({name: e.name}))
|
}
|
||||||
})
|
if (helper.localStorageTest()) {
|
||||||
|
localStorage.setItem("map/selectedLayer", JSON.stringify({name: e.name}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var nodeDict = {}
|
var nodeDict = {};
|
||||||
var linkDict = {}
|
var linkDict = {};
|
||||||
var highlight
|
var highlight;
|
||||||
|
|
||||||
function resetMarkerStyles(nodes, links) {
|
function resetMarkerStyles(nodes, links) {
|
||||||
Object.keys(nodes).forEach( function (d) {
|
Object.keys(nodes).forEach(function (d) {
|
||||||
nodes[d].resetStyle()
|
nodes[d].resetStyle();
|
||||||
})
|
});
|
||||||
|
|
||||||
Object.keys(links).forEach( function (d) {
|
Object.keys(links).forEach(function (d) {
|
||||||
links[d].resetStyle()
|
links[d].resetStyle();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setView(bounds) {
|
function setView(bounds) {
|
||||||
map.fitBounds(bounds, {paddingTopLeft: [sidebar(), 0]})
|
map.fitBounds(bounds, {paddingTopLeft: [sidebar(), 0]});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetZoom() {
|
function resetZoom() {
|
||||||
if (barycenter)
|
if (barycenter) {
|
||||||
setView(barycenter.getBounds())
|
setView(barycenter.getBounds());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function goto(m) {
|
function goto(m) {
|
||||||
var bounds
|
var bounds;
|
||||||
|
|
||||||
if ("getBounds" in m)
|
if ("getBounds" in m) {
|
||||||
bounds = m.getBounds()
|
bounds = m.getBounds();
|
||||||
else
|
} else {
|
||||||
bounds = L.latLngBounds([m.getLatLng()])
|
bounds = L.latLngBounds([m.getLatLng()]);
|
||||||
|
}
|
||||||
|
|
||||||
setView(bounds)
|
setView(bounds);
|
||||||
|
|
||||||
return m
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateView(nopanzoom) {
|
function updateView(nopanzoom) {
|
||||||
resetMarkerStyles(nodeDict, linkDict)
|
resetMarkerStyles(nodeDict, linkDict);
|
||||||
var m
|
var m;
|
||||||
|
|
||||||
if (highlight !== undefined)
|
if (highlight !== undefined) {
|
||||||
if (highlight.type === "node") {
|
if (highlight.type === "node") {
|
||||||
m = nodeDict[highlight.o.nodeinfo.node_id]
|
m = nodeDict[highlight.o.nodeinfo.node_id];
|
||||||
|
|
||||||
if (m)
|
if (m) {
|
||||||
m.setStyle({ color: "orange", weight: 20, fillOpacity: 1, opacity: 0.7, className: "stroke-first" })
|
m.setStyle({color: "orange", weight: 20, fillOpacity: 1, opacity: 0.7, className: "stroke-first"});
|
||||||
|
}
|
||||||
} else if (highlight.type === "link") {
|
} else if (highlight.type === "link") {
|
||||||
m = linkDict[highlight.o.id]
|
m = linkDict[highlight.o.id];
|
||||||
|
|
||||||
if (m)
|
if (m) {
|
||||||
m.setStyle({ weight: 7, opacity: 1, dashArray: "10, 10" })
|
m.setStyle({weight: 4, opacity: 1, dashArray: "5, 10"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!nopanzoom)
|
if (!nopanzoom) {
|
||||||
if (m)
|
if (m) {
|
||||||
goto(m)
|
goto(m);
|
||||||
else if (savedView)
|
} else if (savedView) {
|
||||||
map.setView(savedView.center, savedView.zoom)
|
map.setView(savedView.center, savedView.zoom);
|
||||||
else
|
} else {
|
||||||
resetZoom()
|
resetZoom();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcBarycenter(nodes) {
|
function calcBarycenter(nodes) {
|
||||||
nodes = nodes.map(function (d) { return d.nodeinfo.location })
|
nodes = nodes.map(function (d) {
|
||||||
|
return d.nodeinfo.location;
|
||||||
|
});
|
||||||
|
|
||||||
if (nodes.length === 0)
|
if (nodes.length === 0) {
|
||||||
return undefined
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
var lats = nodes.map(function (d) { return d.latitude })
|
var lats = nodes.map(function (d) {
|
||||||
var lngs = nodes.map(function (d) { return d.longitude })
|
return d.latitude;
|
||||||
|
});
|
||||||
|
var lngs = nodes.map(function (d) {
|
||||||
|
return d.longitude;
|
||||||
|
});
|
||||||
|
|
||||||
var barycenter = L.latLng(d3.median(lats), d3.median(lngs))
|
var barycenter = L.latLng(d3.median(lats), d3.median(lngs));
|
||||||
var barycenterDev = [d3.deviation(lats), d3.deviation(lngs)]
|
var barycenterDev = [d3.deviation(lats), d3.deviation(lngs)];
|
||||||
|
|
||||||
if (barycenterDev[0] === undefined)
|
if (barycenterDev[0] === undefined) {
|
||||||
barycenterDev[0] = 0
|
barycenterDev[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (barycenterDev[1] === undefined)
|
if (barycenterDev[1] === undefined) {
|
||||||
barycenterDev[1] = 0
|
barycenterDev[1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
var barycenterCircle = L.latLng(barycenter.lat + barycenterDev[0],
|
var barycenterCircle = L.latLng(barycenter.lat + barycenterDev[0],
|
||||||
barycenter.lng + barycenterDev[1])
|
barycenter.lng + barycenterDev[1]);
|
||||||
|
|
||||||
var r = barycenter.distanceTo(barycenterCircle)
|
var r = barycenter.distanceTo(barycenterCircle);
|
||||||
|
|
||||||
return L.circle(barycenter, r * config.mapSigmaScale)
|
return L.circle(barycenter, r * config.mapSigmaScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapRTree(d) {
|
function mapRTree(d) {
|
||||||
var o = [ d.nodeinfo.location.latitude, d.nodeinfo.location.longitude,
|
var o = [d.nodeinfo.location.latitude, d.nodeinfo.location.longitude,
|
||||||
d.nodeinfo.location.latitude, d.nodeinfo.location.longitude]
|
d.nodeinfo.location.latitude, d.nodeinfo.location.longitude];
|
||||||
|
|
||||||
o.node = d
|
o.node = d;
|
||||||
|
|
||||||
return o
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setData = function (data) {
|
self.setData = function (data) {
|
||||||
nodeDict = {}
|
nodeDict = {};
|
||||||
linkDict = {}
|
linkDict = {};
|
||||||
|
|
||||||
if (groupOffline)
|
if (groupOffline) {
|
||||||
groupOffline.clearLayers()
|
groupOffline.clearLayers();
|
||||||
|
}
|
||||||
|
|
||||||
if (groupOnline)
|
if (groupOnline) {
|
||||||
groupOnline.clearLayers()
|
groupOnline.clearLayers();
|
||||||
|
}
|
||||||
|
|
||||||
if (groupNew)
|
if (groupNew) {
|
||||||
groupNew.clearLayers()
|
groupNew.clearLayers();
|
||||||
|
}
|
||||||
|
|
||||||
if (groupLost)
|
if (groupLost) {
|
||||||
groupLost.clearLayers()
|
groupLost.clearLayers();
|
||||||
|
}
|
||||||
|
|
||||||
if (groupLines)
|
if (groupLines) {
|
||||||
groupLines.clearLayers()
|
groupLines.clearLayers();
|
||||||
|
}
|
||||||
|
|
||||||
var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router)
|
var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router);
|
||||||
groupLines = L.featureGroup(lines).addTo(map)
|
groupLines = L.featureGroup(lines).addTo(map);
|
||||||
|
|
||||||
if (typeof config.fixedCenter === "undefined")
|
if (typeof config.fixedCenter === "undefined") {
|
||||||
barycenter = calcBarycenter(data.nodes.all.filter(has_location))
|
barycenter = calcBarycenter(data.nodes.all.filter(helper.hasLocation));
|
||||||
else
|
} else {
|
||||||
barycenter = L.circle(L.latLng(new L.LatLng(config.fixedCenter.lat, config.fixedCenter.lng)), config.fixedCenter.radius * 1000)
|
barycenter = L.circle(L.latLng(new L.LatLng(config.fixedCenter.lat, config.fixedCenter.lng)), config.fixedCenter.radius * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
var nodesOnline = subtract(data.nodes.all.filter(online), data.nodes.new)
|
var nodesOnline = helper.subtract(data.nodes.all.filter(helper.online), data.nodes.new);
|
||||||
var nodesOffline = subtract(data.nodes.all.filter(offline), data.nodes.lost)
|
var nodesOffline = helper.subtract(data.nodes.all.filter(helper.offline), data.nodes.lost);
|
||||||
|
|
||||||
var markersOnline = nodesOnline.filter(has_location)
|
var markersOnline = nodesOnline.filter(helper.hasLocation)
|
||||||
.map(mkMarker(nodeDict, function () { return iconOnline }, router))
|
.map(mkMarker(nodeDict, function () {
|
||||||
|
return iconOnline;
|
||||||
|
}, router));
|
||||||
|
|
||||||
var markersOffline = nodesOffline.filter(has_location)
|
var markersOffline = nodesOffline.filter(helper.hasLocation)
|
||||||
.map(mkMarker(nodeDict, function () { return iconOffline }, router))
|
.map(mkMarker(nodeDict, function () {
|
||||||
|
return iconOffline;
|
||||||
|
}, router));
|
||||||
|
|
||||||
var markersNew = data.nodes.new.filter(has_location)
|
var markersNew = data.nodes.new.filter(helper.hasLocation)
|
||||||
.map(mkMarker(nodeDict, function () { return iconNew }, router))
|
.map(mkMarker(nodeDict, function () {
|
||||||
|
return iconNew;
|
||||||
|
}, router));
|
||||||
|
|
||||||
var markersLost = data.nodes.lost.filter(has_location)
|
var markersLost = data.nodes.lost.filter(helper.hasLocation)
|
||||||
.map(mkMarker(nodeDict, function (d) {
|
.map(mkMarker(nodeDict, function (d) {
|
||||||
if (d.lastseen.isAfter(moment(data.now).subtract(3, "days")))
|
if (d.lastseen.isAfter(moment(data.now).subtract(3, "days"))) {
|
||||||
return iconAlert
|
return iconAlert;
|
||||||
|
}
|
||||||
|
|
||||||
return iconLost
|
if (d.lastseen.isAfter(moment(data.now).subtract(14, "days"))) {
|
||||||
}, router))
|
return iconLost;
|
||||||
|
}
|
||||||
|
}, router));
|
||||||
|
|
||||||
groupOffline = L.featureGroup(markersOffline).addTo(map)
|
groupOffline = L.featureGroup(markersOffline).addTo(map);
|
||||||
groupOnline = L.featureGroup(markersOnline).addTo(map)
|
groupLost = L.featureGroup(markersLost).addTo(map);
|
||||||
groupLost = L.featureGroup(markersLost).addTo(map)
|
groupOnline = L.featureGroup(markersOnline).addTo(map);
|
||||||
groupNew = L.featureGroup(markersNew).addTo(map)
|
groupNew = L.featureGroup(markersNew).addTo(map);
|
||||||
|
|
||||||
var rtreeOnlineAll = rbush(9)
|
var rtreeOnlineAll = rbush(9);
|
||||||
|
|
||||||
rtreeOnlineAll.load(data.nodes.all.filter(online).filter(has_location).map(mapRTree))
|
rtreeOnlineAll.load(data.nodes.all.filter(helper.online).filter(helper.hasLocation).map(mapRTree));
|
||||||
|
|
||||||
clientLayer.setData(rtreeOnlineAll)
|
clientLayer.setData(rtreeOnlineAll);
|
||||||
labelsLayer.setData({online: nodesOnline.filter(has_location),
|
labelsLayer.setData({
|
||||||
offline: nodesOffline.filter(has_location),
|
online: nodesOnline.filter(helper.hasLocation),
|
||||||
new: data.nodes.new.filter(has_location),
|
offline: nodesOffline.filter(helper.hasLocation),
|
||||||
lost: data.nodes.lost.filter(has_location)
|
new: data.nodes.new.filter(helper.hasLocation),
|
||||||
})
|
lost: data.nodes.lost.filter(helper.hasLocation)
|
||||||
|
});
|
||||||
|
|
||||||
updateView(true)
|
updateView(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
self.resetView = function () {
|
self.resetView = function () {
|
||||||
disableTracking()
|
disableTracking();
|
||||||
highlight = undefined
|
highlight = undefined;
|
||||||
updateView()
|
updateView();
|
||||||
}
|
};
|
||||||
|
|
||||||
self.gotoNode = function (d) {
|
self.gotoNode = function (d) {
|
||||||
disableTracking()
|
disableTracking();
|
||||||
highlight = {type: "node", o: d}
|
highlight = {type: "node", o: d};
|
||||||
updateView()
|
updateView();
|
||||||
}
|
};
|
||||||
|
|
||||||
self.gotoLink = function (d) {
|
self.gotoLink = function (d) {
|
||||||
disableTracking()
|
disableTracking();
|
||||||
highlight = {type: "link", o: d}
|
highlight = {type: "link", o: d};
|
||||||
updateView()
|
updateView();
|
||||||
}
|
};
|
||||||
|
|
||||||
self.gotoLocation = function () {
|
self.gotoLocation = function () {
|
||||||
//ignore
|
//ignore
|
||||||
}
|
};
|
||||||
|
|
||||||
self.destroy = function () {
|
self.destroy = function () {
|
||||||
clearButtons()
|
clearButtons();
|
||||||
map.remove()
|
map.remove();
|
||||||
|
|
||||||
if (el.parentNode)
|
if (el.parentNode) {
|
||||||
el.parentNode.removeChild(el)
|
el.parentNode.removeChild(el);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.render = function (d) {
|
self.render = function (d) {
|
||||||
d.appendChild(el)
|
d.appendChild(el);
|
||||||
map.invalidateSize()
|
map.invalidateSize();
|
||||||
}
|
};
|
||||||
|
|
||||||
return self
|
return self;
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,76 +1,78 @@
|
||||||
define(["leaflet", "jshashes"],
|
define(["leaflet"],
|
||||||
function (L, jsHashes) {
|
function (L) {
|
||||||
var MD5 = new jsHashes.MD5()
|
"use strict";
|
||||||
|
|
||||||
return L.TileLayer.Canvas.extend({
|
return L.TileLayer.Canvas.extend({
|
||||||
setData: function (d) {
|
setData: function (d) {
|
||||||
this.data = d
|
this.data = d;
|
||||||
|
|
||||||
//pre-calculate start angles
|
//pre-calculate start angles
|
||||||
this.data.all().forEach(function (d) {
|
this.data.all().forEach(function (d) {
|
||||||
var hash = MD5.hex(d.node.nodeinfo.node_id)
|
d.startAngle = (parseInt(d.node.nodeinfo.node_id.substr(10, 2), 16) / 255) * 2 * Math.PI;
|
||||||
d.startAngle = (parseInt(hash.substr(0, 2), 16) / 255) * 2 * Math.PI
|
});
|
||||||
})
|
this.redraw();
|
||||||
this.redraw()
|
|
||||||
},
|
},
|
||||||
drawTile: function (canvas, tilePoint) {
|
drawTile: function (canvas, tilePoint) {
|
||||||
function getTileBBox(s, map, tileSize, margin) {
|
function getTileBBox(s, map, tileSize, margin) {
|
||||||
var tl = map.unproject([s.x - margin, s.y - margin])
|
var tl = map.unproject([s.x - margin, s.y - margin]);
|
||||||
var br = map.unproject([s.x + margin + tileSize, s.y + margin + tileSize])
|
var br = map.unproject([s.x + margin + tileSize, s.y + margin + tileSize]);
|
||||||
|
|
||||||
return [br.lat, tl.lng, tl.lat, br.lng]
|
return [br.lat, tl.lng, tl.lat, br.lng];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.data)
|
if (!this.data) {
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var tileSize = this.options.tileSize
|
var tileSize = this.options.tileSize;
|
||||||
var s = tilePoint.multiplyBy(tileSize)
|
var s = tilePoint.multiplyBy(tileSize);
|
||||||
var map = this._map
|
var map = this._map;
|
||||||
|
|
||||||
var margin = 50
|
var margin = 50;
|
||||||
var bbox = getTileBBox(s, map, tileSize, margin)
|
var bbox = getTileBBox(s, map, tileSize, margin);
|
||||||
|
|
||||||
var nodes = this.data.search(bbox)
|
var nodes = this.data.search(bbox);
|
||||||
|
|
||||||
if (nodes.length === 0)
|
if (nodes.length === 0) {
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var ctx = canvas.getContext("2d")
|
var ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
var radius = 3
|
var radius = 3;
|
||||||
var a = 1.2
|
var a = 1.2;
|
||||||
var startDistance = 12
|
var startDistance = 12;
|
||||||
|
|
||||||
ctx.beginPath()
|
ctx.beginPath();
|
||||||
nodes.forEach(function (d) {
|
nodes.forEach(function (d) {
|
||||||
var p = map.project([d.node.nodeinfo.location.latitude, d.node.nodeinfo.location.longitude])
|
var p = map.project([d.node.nodeinfo.location.latitude, d.node.nodeinfo.location.longitude]);
|
||||||
var clients = d.node.statistics.clients
|
var clients = d.node.statistics.clients;
|
||||||
|
|
||||||
if (clients === 0)
|
if (clients === 0) {
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
p.x -= s.x
|
p.x -= s.x;
|
||||||
p.y -= s.y
|
p.y -= s.y;
|
||||||
|
|
||||||
for (var orbit = 0, i = 0; i < clients; orbit++) {
|
for (var orbit = 0, i = 0; i < clients; orbit++) {
|
||||||
var distance = startDistance + orbit * 2 * radius * a
|
var distance = startDistance + orbit * 2 * radius * a;
|
||||||
var n = Math.floor((Math.PI * distance) / (a * radius))
|
var n = Math.floor((Math.PI * distance) / (a * radius));
|
||||||
var delta = clients - i
|
var delta = clients - i;
|
||||||
|
|
||||||
for (var j = 0; j < Math.min(delta, n); i++, j++) {
|
for (var j = 0; j < Math.min(delta, n); i++, j++) {
|
||||||
var angle = 2 * Math.PI / n * j
|
var angle = 2 * Math.PI / n * j;
|
||||||
var x = p.x + distance * Math.cos(angle + d.startAngle)
|
var x = p.x + distance * Math.cos(angle + d.startAngle);
|
||||||
var y = p.y + distance * Math.sin(angle + d.startAngle)
|
var y = p.y + distance * Math.sin(angle + d.startAngle);
|
||||||
|
|
||||||
ctx.moveTo(x, y)
|
ctx.moveTo(x, y);
|
||||||
ctx.arc(x, y, radius, 0, 2 * Math.PI)
|
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
ctx.fillStyle = "rgba(220, 0, 103, 0.7)"
|
ctx.fillStyle = "rgba(220, 0, 103, 0.7)";
|
||||||
ctx.fill()
|
ctx.fill();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,88 +1,95 @@
|
||||||
define(["leaflet", "rbush"],
|
define(["leaflet", "rbush"],
|
||||||
function (L, rbush) {
|
function (L, rbush) {
|
||||||
var labelLocations = [["left", "middle", 0 / 8],
|
"use strict";
|
||||||
["center", "top", 6 / 8],
|
|
||||||
["right", "middle", 4 / 8],
|
|
||||||
["left", "top", 7 / 8],
|
|
||||||
["left", "ideographic", 1 / 8],
|
|
||||||
["right", "top", 5 / 8],
|
|
||||||
["center", "ideographic", 2 / 8],
|
|
||||||
["right", "ideographic", 3 / 8]]
|
|
||||||
|
|
||||||
var fontFamily = "Roboto"
|
var labelLocations = [["left", "middle", 0 / 8],
|
||||||
var nodeRadius = 4
|
["center", "top", 6 / 8],
|
||||||
|
["right", "middle", 4 / 8],
|
||||||
|
["left", "top", 7 / 8],
|
||||||
|
["left", "ideographic", 1 / 8],
|
||||||
|
["right", "top", 5 / 8],
|
||||||
|
["center", "ideographic", 2 / 8],
|
||||||
|
["right", "ideographic", 3 / 8]];
|
||||||
|
|
||||||
var ctx = document.createElement("canvas").getContext("2d")
|
var fontFamily = "Roboto";
|
||||||
|
var nodeRadius = 4;
|
||||||
|
|
||||||
|
var ctx = document.createElement("canvas").getContext("2d");
|
||||||
|
|
||||||
function measureText(font, text) {
|
function measureText(font, text) {
|
||||||
ctx.font = font
|
ctx.font = font;
|
||||||
return ctx.measureText(text)
|
return ctx.measureText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapRTree(d) {
|
function mapRTree(d) {
|
||||||
var o = [d.position.lat, d.position.lng, d.position.lat, d.position.lng]
|
var o = [d.position.lat, d.position.lng, d.position.lat, d.position.lng];
|
||||||
|
|
||||||
o.label = d
|
o.label = d;
|
||||||
|
|
||||||
return o
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareLabel(fillStyle, fontSize, offset, stroke, minZoom) {
|
function prepareLabel(fillStyle, fontSize, offset, stroke, minZoom) {
|
||||||
return function (d) {
|
return function (d) {
|
||||||
var font = fontSize + "px " + fontFamily
|
var font = fontSize + "px " + fontFamily;
|
||||||
return { position: L.latLng(d.nodeinfo.location.latitude, d.nodeinfo.location.longitude),
|
return {
|
||||||
label: d.nodeinfo.hostname,
|
position: L.latLng(d.nodeinfo.location.latitude, d.nodeinfo.location.longitude),
|
||||||
offset: offset,
|
label: d.nodeinfo.hostname,
|
||||||
fillStyle: fillStyle,
|
offset: offset,
|
||||||
height: fontSize * 1.2,
|
fillStyle: fillStyle,
|
||||||
font: font,
|
height: fontSize * 1.2,
|
||||||
stroke: stroke,
|
font: font,
|
||||||
minZoom: minZoom,
|
stroke: stroke,
|
||||||
width: measureText(font, d.nodeinfo.hostname).width
|
minZoom: minZoom,
|
||||||
}
|
width: measureText(font, d.nodeinfo.hostname).width
|
||||||
}
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function calcOffset(offset, loc) {
|
function calcOffset(offset, loc) {
|
||||||
return [ offset * Math.cos(loc[2] * 2 * Math.PI),
|
return [offset * Math.cos(loc[2] * 2 * Math.PI),
|
||||||
-offset * Math.sin(loc[2] * 2 * Math.PI)]
|
-offset * Math.sin(loc[2] * 2 * Math.PI)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function labelRect(p, offset, anchor, label, minZoom, maxZoom, z) {
|
function labelRect(p, offset, anchor, label, minZoom, maxZoom, z) {
|
||||||
var margin = 1 + 1.41 * (1 - (z - minZoom) / (maxZoom - minZoom))
|
var margin = 1 + 1.41 * (1 - (z - minZoom) / (maxZoom - minZoom));
|
||||||
|
|
||||||
var width = label.width * margin
|
var width = label.width * margin;
|
||||||
var height = label.height * margin
|
var height = label.height * margin;
|
||||||
|
|
||||||
var dx = { left: 0,
|
var dx = {
|
||||||
right: -width,
|
left: 0,
|
||||||
center: -width / 2
|
right: -width,
|
||||||
}
|
center: -width / 2
|
||||||
|
};
|
||||||
|
|
||||||
var dy = { top: 0,
|
var dy = {
|
||||||
ideographic: -height,
|
top: 0,
|
||||||
middle: -height / 2
|
ideographic: -height,
|
||||||
}
|
middle: -height / 2
|
||||||
|
};
|
||||||
|
|
||||||
var x = p.x + offset[0] + dx[anchor[0]]
|
var x = p.x + offset[0] + dx[anchor[0]];
|
||||||
var y = p.y + offset[1] + dy[anchor[1]]
|
var y = p.y + offset[1] + dy[anchor[1]];
|
||||||
|
|
||||||
return [x, y, x + width, y + height]
|
return [x, y, x + width, y + height];
|
||||||
}
|
}
|
||||||
|
|
||||||
var c = L.TileLayer.Canvas.extend({
|
return L.TileLayer.Canvas.extend({
|
||||||
onAdd: function (map) {
|
onAdd: function (map) {
|
||||||
L.TileLayer.Canvas.prototype.onAdd.call(this, map)
|
L.TileLayer.Canvas.prototype.onAdd.call(this, map);
|
||||||
if (this.data)
|
if (this.data) {
|
||||||
this.prepareLabels()
|
this.prepareLabels();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setData: function (d) {
|
setData: function (d) {
|
||||||
this.data = d
|
this.data = d;
|
||||||
if (this._map)
|
if (this._map) {
|
||||||
this.prepareLabels()
|
this.prepareLabels();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
prepareLabels: function () {
|
prepareLabels: function () {
|
||||||
var d = this.data
|
var d = this.data;
|
||||||
|
|
||||||
// label:
|
// label:
|
||||||
// - position (WGS84 coords)
|
// - position (WGS84 coords)
|
||||||
|
@ -92,137 +99,142 @@ define(["leaflet", "rbush"],
|
||||||
// - label (string)
|
// - label (string)
|
||||||
// - color (string)
|
// - color (string)
|
||||||
|
|
||||||
var labelsOnline = d.online.map(prepareLabel("rgba(0, 0, 0, 0.9)", 10, 8, true, 13))
|
var labelsOnline = d.online.map(prepareLabel("rgba(0, 0, 0, 0.9)", 10, 8, true, 13));
|
||||||
var labelsOffline = d.offline.map(prepareLabel("rgba(212, 62, 42, 0.9)", 9, 5, false, 16))
|
var labelsOffline = d.offline.map(prepareLabel("rgba(212, 62, 42, 0.9)", 9, 5, false, 16));
|
||||||
var labelsNew = d.new.map(prepareLabel("rgba(48, 99, 20, 0.9)", 11, 8, true, 0))
|
var labelsNew = d.new.map(prepareLabel("rgba(48, 99, 20, 0.9)", 11, 8, true, 0));
|
||||||
var labelsLost = d.lost.map(prepareLabel("rgba(212, 62, 42, 0.9)", 11, 8, true, 0))
|
var labelsLost = d.lost.map(prepareLabel("rgba(212, 62, 42, 0.9)", 11, 8, true, 0));
|
||||||
|
|
||||||
var labels = []
|
var labels = []
|
||||||
.concat(labelsNew)
|
.concat(labelsNew)
|
||||||
.concat(labelsLost)
|
.concat(labelsLost)
|
||||||
.concat(labelsOnline)
|
.concat(labelsOnline)
|
||||||
.concat(labelsOffline)
|
.concat(labelsOffline);
|
||||||
|
|
||||||
var minZoom = this.options.minZoom
|
var minZoom = this.options.minZoom;
|
||||||
var maxZoom = this.options.maxZoom
|
var maxZoom = this.options.maxZoom;
|
||||||
|
|
||||||
var trees = []
|
var trees = [];
|
||||||
|
|
||||||
var map = this._map
|
var map = this._map;
|
||||||
|
|
||||||
function nodeToRect(z) {
|
function nodeToRect(z) {
|
||||||
return function (d) {
|
return function (d) {
|
||||||
var p = map.project(d.position, z)
|
var p = map.project(d.position, z);
|
||||||
return [p.x - nodeRadius, p.y - nodeRadius,
|
return [p.x - nodeRadius, p.y - nodeRadius,
|
||||||
p.x + nodeRadius, p.y + nodeRadius]
|
p.x + nodeRadius, p.y + nodeRadius];
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var z = minZoom; z <= maxZoom; z++) {
|
for (var z = minZoom; z <= maxZoom; z++) {
|
||||||
trees[z] = rbush(9)
|
trees[z] = rbush(9);
|
||||||
trees[z].load(labels.map(nodeToRect(z)))
|
trees[z].load(labels.map(nodeToRect(z)));
|
||||||
}
|
}
|
||||||
|
|
||||||
labels = labels.map(function (d) {
|
labels = labels.map(function (d) {
|
||||||
var best = labelLocations.map(function (loc) {
|
var best = labelLocations.map(function (loc) {
|
||||||
var offset = calcOffset(d.offset, loc)
|
var offset = calcOffset(d.offset, loc);
|
||||||
var z
|
var z;
|
||||||
|
|
||||||
for (z = maxZoom; z >= d.minZoom; z--) {
|
for (z = maxZoom; z >= d.minZoom; z--) {
|
||||||
var p = map.project(d.position, z)
|
var p = map.project(d.position, z);
|
||||||
var rect = labelRect(p, offset, loc, d, minZoom, maxZoom, z)
|
var rect = labelRect(p, offset, loc, d, minZoom, maxZoom, z);
|
||||||
var candidates = trees[z].search(rect)
|
var candidates = trees[z].search(rect);
|
||||||
|
|
||||||
if (candidates.length > 0)
|
if (candidates.length > 0) {
|
||||||
break
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {loc: loc, z: z + 1}
|
return {loc: loc, z: z + 1};
|
||||||
}).filter(function (d) {
|
}).filter(function (d) {
|
||||||
return d.z <= maxZoom
|
return d.z <= maxZoom;
|
||||||
}).sort(function (a, b) {
|
}).sort(function (a, b) {
|
||||||
return a.z - b.z
|
return a.z - b.z;
|
||||||
})[0]
|
})[0];
|
||||||
|
|
||||||
if (best !== undefined) {
|
if (best !== undefined) {
|
||||||
d.offset = calcOffset(d.offset, best.loc)
|
d.offset = calcOffset(d.offset, best.loc);
|
||||||
d.minZoom = best.z
|
d.minZoom = best.z;
|
||||||
d.anchor = best.loc
|
d.anchor = best.loc;
|
||||||
|
|
||||||
for (var z = maxZoom; z >= best.z; z--) {
|
for (var z = maxZoom; z >= best.z; z--) {
|
||||||
var p = map.project(d.position, z)
|
var p = map.project(d.position, z);
|
||||||
var rect = labelRect(p, d.offset, best.loc, d, minZoom, maxZoom, z)
|
var rect = labelRect(p, d.offset, best.loc, d, minZoom, maxZoom, z);
|
||||||
trees[z].insert(rect)
|
trees[z].insert(rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
return d
|
return d;
|
||||||
} else
|
} else {
|
||||||
return undefined
|
return undefined;
|
||||||
}).filter(function (d) { return d !== undefined })
|
}
|
||||||
|
}).filter(function (d) {
|
||||||
|
return d !== undefined;
|
||||||
|
});
|
||||||
|
|
||||||
this.margin = 16
|
this.margin = 16;
|
||||||
|
|
||||||
if (labels.length > 0)
|
if (labels.length > 0) {
|
||||||
this.margin += labels.map(function (d) {
|
this.margin += labels.map(function (d) {
|
||||||
return d.width
|
return d.width;
|
||||||
}).sort().reverse()[0]
|
}).sort().reverse()[0];
|
||||||
|
}
|
||||||
|
|
||||||
this.labels = rbush(9)
|
this.labels = rbush(9);
|
||||||
this.labels.load(labels.map(mapRTree))
|
this.labels.load(labels.map(mapRTree));
|
||||||
|
|
||||||
this.redraw()
|
this.redraw();
|
||||||
},
|
},
|
||||||
drawTile: function (canvas, tilePoint, zoom) {
|
drawTile: function (canvas, tilePoint, zoom) {
|
||||||
function getTileBBox(s, map, tileSize, margin) {
|
function getTileBBox(s, map, tileSize, margin) {
|
||||||
var tl = map.unproject([s.x - margin, s.y - margin])
|
var tl = map.unproject([s.x - margin, s.y - margin]);
|
||||||
var br = map.unproject([s.x + margin + tileSize, s.y + margin + tileSize])
|
var br = map.unproject([s.x + margin + tileSize, s.y + margin + tileSize]);
|
||||||
|
|
||||||
return [br.lat, tl.lng, tl.lat, br.lng]
|
return [br.lat, tl.lng, tl.lat, br.lng];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.labels)
|
if (!this.labels) {
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var tileSize = this.options.tileSize
|
var tileSize = this.options.tileSize;
|
||||||
var s = tilePoint.multiplyBy(tileSize)
|
var s = tilePoint.multiplyBy(tileSize);
|
||||||
var map = this._map
|
var map = this._map;
|
||||||
|
|
||||||
function projectNodes(d) {
|
function projectNodes(d) {
|
||||||
var p = map.project(d.label.position)
|
var p = map.project(d.label.position);
|
||||||
|
|
||||||
p.x -= s.x
|
p.x -= s.x;
|
||||||
p.y -= s.y
|
p.y -= s.y;
|
||||||
|
|
||||||
return {p: p, label: d.label}
|
return {p: p, label: d.label};
|
||||||
}
|
}
|
||||||
|
|
||||||
var bbox = getTileBBox(s, map, tileSize, this.margin)
|
var bbox = getTileBBox(s, map, tileSize, this.margin);
|
||||||
|
|
||||||
var labels = this.labels.search(bbox).map(projectNodes)
|
var labels = this.labels.search(bbox).map(projectNodes);
|
||||||
|
|
||||||
var ctx = canvas.getContext("2d")
|
var ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
ctx.lineWidth = 5
|
ctx.lineWidth = 5;
|
||||||
ctx.strokeStyle = "rgba(255, 255, 255, 0.8)"
|
ctx.strokeStyle = "rgba(255, 255, 255, 0.8)";
|
||||||
ctx.miterLimit = 2
|
ctx.miterLimit = 2;
|
||||||
|
|
||||||
function drawLabel(d) {
|
function drawLabel(d) {
|
||||||
ctx.font = d.label.font
|
ctx.font = d.label.font;
|
||||||
ctx.textAlign = d.label.anchor[0]
|
ctx.textAlign = d.label.anchor[0];
|
||||||
ctx.textBaseline = d.label.anchor[1]
|
ctx.textBaseline = d.label.anchor[1];
|
||||||
ctx.fillStyle = d.label.fillStyle
|
ctx.fillStyle = d.label.fillStyle;
|
||||||
|
|
||||||
if (d.label.stroke)
|
if (d.label.stroke) {
|
||||||
ctx.strokeText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1])
|
ctx.strokeText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1]);
|
||||||
|
}
|
||||||
|
|
||||||
ctx.fillText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1])
|
ctx.fillText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
labels.filter(function (d) {
|
labels.filter(function (d) {
|
||||||
return zoom >= d.label.minZoom
|
return zoom >= d.label.minZoom;
|
||||||
}).forEach(drawLabel)
|
}).forEach(drawLabel);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
});
|
||||||
return c
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,55 +1,63 @@
|
||||||
define(function () {
|
define(["helper"], function (helper) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return function (config) {
|
return function (config) {
|
||||||
var self = this
|
var self = this;
|
||||||
var stats, timestamp
|
var stats, timestamp;
|
||||||
|
|
||||||
self.setData = function (d) {
|
self.setData = function (d) {
|
||||||
var totalNodes = sum(d.nodes.all.map(one))
|
var totalNodes = helper.sum(d.nodes.all.map(helper.one));
|
||||||
var totalOnlineNodes = sum(d.nodes.all.filter(online).map(one))
|
var totalOnlineNodes = helper.sum(d.nodes.all.filter(helper.online).map(helper.one));
|
||||||
var totalOfflineNodes = sum(d.nodes.all.filter(function (node) {return !node.flags.online}).map(one))
|
var totalOfflineNodes = helper.sum(d.nodes.all.filter(function (node) {
|
||||||
var totalNewNodes = sum(d.nodes.new.map(one))
|
return !node.flags.online;
|
||||||
var totalLostNodes = sum(d.nodes.lost.map(one))
|
}).map(helper.one));
|
||||||
var totalClients = sum(d.nodes.all.filter(online).map( function (d) {
|
var totalNewNodes = helper.sum(d.nodes.new.map(helper.one));
|
||||||
return d.statistics.clients ? d.statistics.clients : 0
|
var totalLostNodes = helper.sum(d.nodes.lost.map(helper.one));
|
||||||
}))
|
var totalClients = helper.sum(d.nodes.all.filter(helper.online).map(function (d) {
|
||||||
var totalGateways = sum(Array.from(new Set(d.nodes.all.filter(online).map( function(d) {
|
return d.statistics.clients ? d.statistics.clients : 0;
|
||||||
return ("gateway" in d.statistics && d.statistics.gateway.id) ? d.statistics.gateway.id : d.statistics.gateway
|
}));
|
||||||
}).concat(d.nodes.all.filter( function (d) {
|
var totalGateways = helper.sum(Array.from(new Set(d.nodes.all.filter(helper.online).map(function (d) {
|
||||||
return d.flags.gateway
|
return ("gateway" in d.statistics && d.statistics.gateway.id) ? d.statistics.gateway.id : d.statistics.gateway;
|
||||||
})))).map(function(d) {
|
}).concat(d.nodes.all.filter(function (d) {
|
||||||
return (typeof d === "string") ? 1 : 0
|
return d.flags.gateway;
|
||||||
}))
|
})))).map(function (d) {
|
||||||
|
return (typeof d === "string") ? 1 : 0;
|
||||||
|
}));
|
||||||
|
|
||||||
var nodetext = [{ count: totalOnlineNodes, label: "online" },
|
var nodetext = [{count: totalOnlineNodes, label: "online"},
|
||||||
{ count: totalOfflineNodes, label: "offline" },
|
{count: totalOfflineNodes, label: "offline"},
|
||||||
{ count: totalNewNodes, label: "neu" },
|
{count: totalNewNodes, label: "neu"},
|
||||||
{ count: totalLostNodes, label: "verschwunden" }
|
{count: totalLostNodes, label: "verschwunden"}
|
||||||
].filter( function (d) { return d.count > 0 } )
|
].filter(function (d) {
|
||||||
.map( function (d) { return [d.count, d.label].join(" ") } )
|
return d.count > 0;
|
||||||
.join(", ")
|
})
|
||||||
|
.map(function (d) {
|
||||||
|
return [d.count, d.label].join(" ");
|
||||||
|
})
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
stats.textContent = totalNodes + " Knoten " +
|
stats.textContent = totalNodes + " Knoten " +
|
||||||
"(" + nodetext + "), " +
|
"(" + nodetext + "), " +
|
||||||
totalClients + " Client" + ( totalClients === 1 ? ", " : "s, " ) +
|
totalClients + " Client" + ( totalClients === 1 ? ", " : "s, " ) +
|
||||||
totalGateways + " Gateways"
|
totalGateways + " Gateways";
|
||||||
|
|
||||||
timestamp.textContent = "Diese Daten sind von " + d.timestamp.format("LLLL") + "."
|
timestamp.textContent = "Diese Daten sind von " + d.timestamp.format("LLLL") + ".";
|
||||||
}
|
};
|
||||||
|
|
||||||
self.render = function (el) {
|
self.render = function (el) {
|
||||||
var h2 = document.createElement("h2")
|
var h2 = document.createElement("h2");
|
||||||
h2.textContent = config.siteName
|
h2.textContent = config.siteName;
|
||||||
el.appendChild(h2)
|
el.appendChild(h2);
|
||||||
|
|
||||||
var p = document.createElement("p")
|
var p = document.createElement("p");
|
||||||
el.appendChild(p)
|
el.appendChild(p);
|
||||||
stats = document.createTextNode("")
|
stats = document.createTextNode("");
|
||||||
p.appendChild(stats)
|
p.appendChild(stats);
|
||||||
p.appendChild(document.createElement("br"))
|
p.appendChild(document.createElement("br"));
|
||||||
timestamp = document.createTextNode("")
|
timestamp = document.createTextNode("");
|
||||||
p.appendChild(timestamp)
|
p.appendChild(timestamp);
|
||||||
}
|
};
|
||||||
|
|
||||||
return self
|
return self;
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
162
lib/nodelist.js
162
lib/nodelist.js
|
@ -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) {
|
function getUptime(now, d) {
|
||||||
if (d.flags.online && "uptime" in d.statistics)
|
if (d.flags.online && "uptime" in d.statistics) {
|
||||||
return Math.round(d.statistics.uptime)
|
return Math.round(d.statistics.uptime);
|
||||||
else if (!d.flags.online && "lastseen" in d)
|
} else if (!d.flags.online && "lastseen" in d) {
|
||||||
return Math.round(-(now.unix() - d.lastseen.unix()))
|
return Math.round(-(now.unix() - d.lastseen.unix()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showUptime(uptime) {
|
function showUptime(uptime) {
|
||||||
var s = ""
|
var s = "";
|
||||||
uptime /= 3600
|
uptime /= 3600;
|
||||||
|
|
||||||
if (uptime !== undefined)
|
if (uptime !== undefined) {
|
||||||
if (Math.abs(uptime) >= 24)
|
if (Math.abs(uptime) >= 24) {
|
||||||
s = Math.round(uptime / 24) + "d"
|
s = Math.round(uptime / 24) + "d";
|
||||||
else
|
} else {
|
||||||
s = Math.round(uptime) + "h"
|
s = Math.round(uptime) + "h";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return s
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
var headings = [{ name: "Knoten",
|
var headings = [{
|
||||||
sort: function (a, b) {
|
name: ''
|
||||||
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
|
name: "Knoten",
|
||||||
if (typeof aname === "string" && typeof bname === "string")
|
sort: function (a, b) {
|
||||||
return aname.localeCompare(bname)
|
var aname = typeof a.nodeinfo.hostname === "string" ? a.nodeinfo.hostname : a.nodeinfo.node_id;
|
||||||
return typeof aname === "string" ? 1 : typeof bname === "string" ? -1 : 0
|
var bname = typeof b.nodeinfo.hostname === "string" ? b.nodeinfo.hostname : b.nodeinfo.node_id;
|
||||||
},
|
if (typeof aname === "string" && typeof bname === "string") {
|
||||||
reverse: false
|
return aname.localeCompare(bname);
|
||||||
},
|
}
|
||||||
{ name: "Uptime",
|
return typeof aname === "string" ? 1 : typeof bname === "string" ? -1 : 0;
|
||||||
sort: function (a, b) {
|
},
|
||||||
return a.uptime - b.uptime
|
reverse: false
|
||||||
},
|
},
|
||||||
reverse: true
|
{
|
||||||
},
|
name: "Uptime",
|
||||||
{ name: "#Links",
|
sort: function (a, b) {
|
||||||
sort: function (a, b) {
|
return a.uptime - b.uptime;
|
||||||
return a.meshlinks - b.meshlinks
|
},
|
||||||
},
|
reverse: true
|
||||||
reverse: true
|
},
|
||||||
},
|
{
|
||||||
{ name: "Clients",
|
name: "#Links",
|
||||||
sort: function (a, b) {
|
sort: function (a, b) {
|
||||||
return ("clients" in a.statistics ? a.statistics.clients : -1) -
|
return a.meshlinks - b.meshlinks;
|
||||||
("clients" in b.statistics ? b.statistics.clients : -1)
|
},
|
||||||
},
|
reverse: true
|
||||||
reverse: true
|
},
|
||||||
}]
|
{
|
||||||
|
name: "Clients",
|
||||||
|
sort: function (a, b) {
|
||||||
|
return ("clients" in a.statistics ? a.statistics.clients : -1) -
|
||||||
|
("clients" in b.statistics ? b.statistics.clients : -1);
|
||||||
|
},
|
||||||
|
reverse: true
|
||||||
|
}];
|
||||||
|
|
||||||
return function(router) {
|
return function (router) {
|
||||||
function renderRow(d) {
|
function renderRow(d) {
|
||||||
var td1Content = []
|
var td0Content = [];
|
||||||
var aClass = ["hostname", d.flags.online ? "online" : "offline"]
|
var td1Content = [];
|
||||||
|
var aClass = ["hostname", d.flags.online ? "online" : "offline"];
|
||||||
|
|
||||||
td1Content.push(V.h("a", { className: aClass.join(" "),
|
td1Content.push(V.h("a", {
|
||||||
onclick: router.node(d),
|
className: aClass.join(" "),
|
||||||
href: "#!n:" + d.nodeinfo.node_id
|
onclick: router.node(d),
|
||||||
}, d.nodeinfo.hostname))
|
href: "#!n:" + d.nodeinfo.node_id
|
||||||
|
}, d.nodeinfo.hostname));
|
||||||
|
|
||||||
if (has_location(d))
|
if (helper.hasLocation(d)) {
|
||||||
td1Content.push(V.h("span", {className: "icon ion-location"}))
|
td0Content.push(V.h("span", {className: "icon ion-location"}));
|
||||||
|
}
|
||||||
|
|
||||||
var td1 = V.h("td", td1Content)
|
var td0 = V.h("td", td0Content);
|
||||||
var td2 = V.h("td", showUptime(d.uptime))
|
var td1 = V.h("td", td1Content);
|
||||||
var td3 = V.h("td", d.meshlinks.toString())
|
var td2 = V.h("td", showUptime(d.uptime));
|
||||||
var td4 = V.h("td", numeral("clients" in d.statistics ? d.statistics.clients : "").format("0,0"))
|
var td3 = V.h("td", d.meshlinks.toString());
|
||||||
|
var td4 = V.h("td", ("clients" in d.statistics ? d.statistics.clients : 0).toFixed(0));
|
||||||
|
|
||||||
return V.h("tr", [td1, td2, td3, td4])
|
return V.h("tr", [td0, td1, td2, td3, td4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var table = new SortTable(headings, 0, renderRow)
|
var table = new SortTable(headings, 1, renderRow);
|
||||||
|
table.el.classList.add('node-list');
|
||||||
|
|
||||||
this.render = function (d) {
|
this.render = function (d) {
|
||||||
var el = document.createElement("div")
|
var h2 = document.createElement("h2");
|
||||||
d.appendChild(el)
|
h2.textContent = "Alle Knoten";
|
||||||
|
d.appendChild(h2);
|
||||||
|
|
||||||
var h2 = document.createElement("h2")
|
d.appendChild(table.el);
|
||||||
h2.textContent = "Alle Knoten"
|
};
|
||||||
el.appendChild(h2)
|
|
||||||
|
|
||||||
el.appendChild(table.el)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setData = function (d) {
|
this.setData = function (d) {
|
||||||
var data = d.nodes.all.map(function (e) {
|
var data = d.nodes.all.map(function (e) {
|
||||||
var n = Object.create(e)
|
var n = Object.create(e);
|
||||||
n.uptime = getUptime(d.now, e) || 0
|
n.uptime = getUptime(d.now, e) || 0;
|
||||||
n.meshlinks = e.meshlinks || 0
|
n.meshlinks = e.meshlinks || 0;
|
||||||
return n
|
return n;
|
||||||
})
|
});
|
||||||
|
|
||||||
table.setData(data)
|
table.setData(data);
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,236 +1,284 @@
|
||||||
define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "vercomp" ],
|
define(["chroma-js", "virtual-dom", "filters/genericnode", "helper"],
|
||||||
function (Chroma, V, numeral, Filter, vercomp) {
|
function (Chroma, V, Filter, helper) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return function (config, filterManager) {
|
return function (config, filterManager) {
|
||||||
var self = this
|
var self = this;
|
||||||
var scale = Chroma.scale("YlGnBu").mode("lab")
|
var scale = Chroma.scale("YlGnBu").mode("lab");
|
||||||
|
|
||||||
var statusTable = document.createElement("table")
|
var statusTable = document.createElement("table");
|
||||||
statusTable.classList.add("proportion")
|
statusTable.classList.add("proportion");
|
||||||
|
|
||||||
var fwTable = document.createElement("table")
|
var fwTable = document.createElement("table");
|
||||||
fwTable.classList.add("proportion")
|
fwTable.classList.add("proportion");
|
||||||
|
|
||||||
var hwTable = document.createElement("table")
|
var hwTable = document.createElement("table");
|
||||||
hwTable.classList.add("proportion")
|
hwTable.classList.add("proportion");
|
||||||
|
|
||||||
var geoTable = document.createElement("table")
|
var geoTable = document.createElement("table");
|
||||||
geoTable.classList.add("proportion")
|
geoTable.classList.add("proportion");
|
||||||
|
|
||||||
var autoTable = document.createElement("table")
|
var autoTable = document.createElement("table");
|
||||||
autoTable.classList.add("proportion")
|
autoTable.classList.add("proportion");
|
||||||
|
|
||||||
var uplinkTable = document.createElement("table")
|
var uplinkTable = document.createElement("table");
|
||||||
uplinkTable.classList.add("proportion")
|
uplinkTable.classList.add("proportion");
|
||||||
|
|
||||||
var gwNodesTable = document.createElement("table")
|
var gwNodesTable = document.createElement("table");
|
||||||
gwNodesTable.classList.add("proportion")
|
gwNodesTable.classList.add("proportion");
|
||||||
|
|
||||||
var gwClientsTable = document.createElement("table")
|
var gwClientsTable = document.createElement("table");
|
||||||
gwClientsTable.classList.add("proportion")
|
gwClientsTable.classList.add("proportion");
|
||||||
|
|
||||||
var siteTable = document.createElement("table")
|
var siteTable = document.createElement("table");
|
||||||
siteTable.classList.add("proportion")
|
siteTable.classList.add("proportion");
|
||||||
|
|
||||||
function showStatGlobal(o) {
|
function showStatGlobal(o) {
|
||||||
return showStat(o)
|
return helper.showStat(o);
|
||||||
}
|
|
||||||
|
|
||||||
function count(nodes, key, f) {
|
|
||||||
var dict = {}
|
|
||||||
|
|
||||||
nodes.forEach( function (d) {
|
|
||||||
var v = dictGet(d, key.slice(0))
|
|
||||||
|
|
||||||
if (f !== undefined)
|
|
||||||
v = f(v)
|
|
||||||
|
|
||||||
if (v === null)
|
|
||||||
return
|
|
||||||
|
|
||||||
dict[v] = 1 + (v in dict ? dict[v] : 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
return Object.keys(dict).map(function (d) { return [d, dict[d], key, f] })
|
|
||||||
}
|
|
||||||
|
|
||||||
function countClients(nodes, key, f) {
|
|
||||||
var dict = {}
|
|
||||||
|
|
||||||
nodes.forEach( function (d) {
|
|
||||||
var v = 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] })
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function addFilter(filter) {
|
|
||||||
return function () {
|
|
||||||
filterManager.addFilter(filter)
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function fillTable(name, table, data) {
|
function count(nodes, key, f) {
|
||||||
if (!table.last)
|
var dict = {};
|
||||||
table.last = V.h("table")
|
|
||||||
|
|
||||||
var max = 0
|
nodes.forEach(function (d) {
|
||||||
data.forEach(function (d) {
|
var v = helper.dictGet(d, key.slice(0));
|
||||||
if (d[1] > max)
|
|
||||||
max = d[1]
|
|
||||||
})
|
|
||||||
|
|
||||||
var items = data.map(function (d) {
|
if (f !== undefined) {
|
||||||
var v = d[1] / max
|
v = f(v);
|
||||||
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])
|
if (v === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var a = V.h("a", { href: "#", onclick: addFilter(filter) }, d[0])
|
dict[v] = 1 + (v in dict ? dict[v] : 0);
|
||||||
|
});
|
||||||
|
|
||||||
var th = V.h("th", a)
|
return Object.keys(dict).map(function (d) {
|
||||||
var td = V.h("td", V.h("span", {style: {
|
return [d, dict[d], key, f];
|
||||||
width: Math.round(v * 100) + "%",
|
});
|
||||||
backgroundColor: scale(v).hex(),
|
}
|
||||||
color: c1 > c2 ? "white" : "black"
|
|
||||||
}}, numeral(d[1]).format("0,0")))
|
|
||||||
|
|
||||||
return V.h("tr", [th, td])
|
function countClients(nodes, key, f) {
|
||||||
})
|
var dict = {};
|
||||||
|
|
||||||
var tableNew = V.h("table", items)
|
nodes.forEach(function (d) {
|
||||||
table = V.patch(table, V.diff(table.last, tableNew))
|
var v = helper.dictGet(d, key.slice(0));
|
||||||
table.last = tableNew
|
|
||||||
}
|
|
||||||
|
|
||||||
self.setData = function (data) {
|
if (f !== undefined) {
|
||||||
var onlineNodes = data.nodes.all.filter(online)
|
v = f(v);
|
||||||
var nodes = onlineNodes.concat(data.nodes.lost)
|
}
|
||||||
var nodeDict = {}
|
|
||||||
|
|
||||||
data.nodes.all.forEach(function (d) {
|
if (v === null) {
|
||||||
nodeDict[d.nodeinfo.node_id] = d
|
return;
|
||||||
})
|
}
|
||||||
|
|
||||||
var statusDict = count(nodes, ["flags", "online"], function (d) {
|
dict[v] = d.statistics.clients + (v in dict ? dict[v] : 0);
|
||||||
return d ? "online" : "offline"
|
});
|
||||||
})
|
|
||||||
var fwDict = count(nodes, ["nodeinfo", "software", "firmware", "release"])
|
return Object.keys(dict).map(function (d) {
|
||||||
var hwDict = count(nodes, ["nodeinfo", "hardware", "model"], function (d) {
|
return [d, dict[d], key, f];
|
||||||
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("@"))
|
|
||||||
|
function addFilter(filter) {
|
||||||
|
return function () {
|
||||||
|
filterManager.addFilter(filter);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillTable(name, table, data) {
|
||||||
|
if (!table.last) {
|
||||||
|
table.last = V.h("table");
|
||||||
}
|
}
|
||||||
return d
|
|
||||||
})
|
|
||||||
var geoDict = count(nodes, ["nodeinfo", "location"], function (d) {
|
|
||||||
return d && d.longitude && d.latitude ? "ja" : "nein"
|
|
||||||
})
|
|
||||||
|
|
||||||
var autoDict = count(nodes, ["nodeinfo", "software", "autoupdater"], function (d) {
|
var max = 0;
|
||||||
if (d === null)
|
data.forEach(function (d) {
|
||||||
return null
|
if (d[1] > max) {
|
||||||
else if (d.enabled)
|
max = d[1];
|
||||||
return d.branch
|
}
|
||||||
else
|
});
|
||||||
return "(deaktiviert)"
|
|
||||||
})
|
|
||||||
|
|
||||||
var uplinkDict = count(nodes, ["flags", "uplink"], function (d) {
|
var items = data.map(function (d) {
|
||||||
return d ? "ja" : "nein"
|
var v = d[1] / max;
|
||||||
})
|
var c1 = Chroma.contrast(scale(v), "white");
|
||||||
|
var c2 = Chroma.contrast(scale(v), "black");
|
||||||
|
|
||||||
var gwNodesDict = count(onlineNodes, ["statistics", "gateway"], function (d) {
|
var filter = new Filter(name, d[2], d[0], d[3]);
|
||||||
if (d === null)
|
|
||||||
return null
|
|
||||||
|
|
||||||
if (d.node)
|
var a = V.h("a", {href: "#", onclick: addFilter(filter)}, d[0]);
|
||||||
return d.node.nodeinfo.hostname
|
|
||||||
|
|
||||||
if (d.id)
|
var th = V.h("th", a);
|
||||||
return d.id
|
var td = V.h("td", V.h("span", {
|
||||||
|
style: {
|
||||||
|
width: Math.round(v * 100) + "%",
|
||||||
|
backgroundColor: scale(v).hex(),
|
||||||
|
color: c1 > c2 ? "white" : "black"
|
||||||
|
}
|
||||||
|
}, d[1].toFixed(0)));
|
||||||
|
|
||||||
return d
|
return V.h("tr", [th, td]);
|
||||||
})
|
});
|
||||||
|
|
||||||
var gwClientsDict = countClients(onlineNodes, ["statistics", "gateway"], function (d) {
|
var tableNew = V.h("table", items);
|
||||||
if (d === null)
|
table = V.patch(table, V.diff(table.last, tableNew));
|
||||||
return null
|
table.last = tableNew;
|
||||||
|
|
||||||
if (d.node)
|
|
||||||
return d.node.nodeinfo.hostname
|
|
||||||
|
|
||||||
if (d.id)
|
|
||||||
return d.id
|
|
||||||
|
|
||||||
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] }))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if (config.globalInfos)
|
|
||||||
config.globalInfos.forEach(function (globalInfo) {
|
|
||||||
h2 = document.createElement("h2")
|
|
||||||
h2.textContent = globalInfo.name
|
|
||||||
el.appendChild(h2)
|
|
||||||
el.appendChild(showStatGlobal(globalInfo))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.renderSingle = function (el, heading, table) {
|
self.setData = function (data) {
|
||||||
var h2
|
var onlineNodes = data.nodes.all.filter(helper.online);
|
||||||
h2 = document.createElement("h2")
|
var nodes = onlineNodes.concat(data.nodes.lost);
|
||||||
h2.textContent = heading
|
var nodeDict = {};
|
||||||
h2.onclick = function () {
|
|
||||||
table.classList.toggle("hidden")
|
data.nodes.all.forEach(function (d) {
|
||||||
}
|
nodeDict[d.nodeinfo.node_id] = d;
|
||||||
el.appendChild(h2)
|
});
|
||||||
el.appendChild(table)
|
|
||||||
}
|
var statusDict = count(nodes, ["flags", "online"], function (d) {
|
||||||
return self
|
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("@"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
});
|
||||||
|
var geoDict = count(nodes, ["nodeinfo", "location"], function (d) {
|
||||||
|
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)";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var uplinkDict = count(nodes, ["flags", "uplink"], function (d) {
|
||||||
|
return d ? "ja" : "nein";
|
||||||
|
});
|
||||||
|
|
||||||
|
var gwNodesDict = count(onlineNodes, ["statistics", "gateway"], function (d) {
|
||||||
|
if (d === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d.node) {
|
||||||
|
return d.node.nodeinfo.hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d.id) {
|
||||||
|
return d.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
});
|
||||||
|
|
||||||
|
var gwClientsDict = countClients(onlineNodes, ["statistics", "gateway"], function (d) {
|
||||||
|
if (d === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d.node) {
|
||||||
|
return d.node.nodeinfo.hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d.id) {
|
||||||
|
return d.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (config.globalInfos) {
|
||||||
|
config.globalInfos.forEach(function (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;
|
||||||
|
h2.onclick = function () {
|
||||||
|
table.classList.toggle("hidden");
|
||||||
|
};
|
||||||
|
el.appendChild(h2);
|
||||||
|
el.appendChild(table);
|
||||||
|
};
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
262
lib/router.js
262
lib/router.js
|
@ -1,214 +1,232 @@
|
||||||
define(function () {
|
define(["helper"], function (helper) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
var self = this
|
var self = this;
|
||||||
var objects = { nodes: {}, links: {} }
|
var objects = {nodes: {}, links: {}};
|
||||||
var targets = []
|
var targets = [];
|
||||||
var views = {}
|
var views = {};
|
||||||
var currentView
|
var currentView;
|
||||||
var currentObject
|
var currentObject;
|
||||||
var running = false
|
var running = false;
|
||||||
|
|
||||||
function saveState() {
|
function saveState() {
|
||||||
var e = []
|
var e = [];
|
||||||
|
|
||||||
if (currentView)
|
if (currentView) {
|
||||||
e.push("v:" + currentView)
|
e.push("v:" + currentView);
|
||||||
|
|
||||||
if (currentObject) {
|
|
||||||
if ("node" in currentObject)
|
|
||||||
e.push("n:" + encodeURIComponent(currentObject.node.nodeinfo.node_id))
|
|
||||||
|
|
||||||
if ("link" in currentObject)
|
|
||||||
e.push("l:" + encodeURIComponent(currentObject.link.id))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var s = "#!" + e.join(";")
|
if (currentObject) {
|
||||||
|
if ("node" in currentObject) {
|
||||||
|
e.push("n:" + encodeURIComponent(currentObject.node.nodeinfo.node_id));
|
||||||
|
}
|
||||||
|
|
||||||
window.history.pushState(s, undefined, s)
|
if ("link" in currentObject) {
|
||||||
|
e.push("l:" + encodeURIComponent(currentObject.link.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = "#!" + e.join(";");
|
||||||
|
|
||||||
|
window.history.pushState(s, undefined, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetView(push) {
|
function resetView(push) {
|
||||||
push = trueDefault(push)
|
push = helper.trueDefault(push);
|
||||||
|
|
||||||
targets.forEach( function (t) {
|
targets.forEach(function (t) {
|
||||||
t.resetView()
|
t.resetView();
|
||||||
})
|
});
|
||||||
|
|
||||||
if (push) {
|
if (push) {
|
||||||
currentObject = undefined
|
currentObject = undefined;
|
||||||
saveState()
|
saveState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function gotoNode(d) {
|
function gotoNode(d) {
|
||||||
if (!d)
|
if (!d) {
|
||||||
return false
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
targets.forEach( function (t) {
|
targets.forEach(function (t) {
|
||||||
t.gotoNode(d)
|
t.gotoNode(d);
|
||||||
})
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function gotoLink(d) {
|
function gotoLink(d) {
|
||||||
if (!d)
|
if (!d) {
|
||||||
return false
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
targets.forEach( function (t) {
|
targets.forEach(function (t) {
|
||||||
t.gotoLink(d)
|
t.gotoLink(d);
|
||||||
})
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function gotoLocation(d) {
|
function gotoLocation(d) {
|
||||||
if (!d)
|
if (!d) {
|
||||||
return false
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
targets.forEach( function (t) {
|
targets.forEach(function (t) {
|
||||||
if(!t.gotoLocation)console.warn("has no gotoLocation", t)
|
if (!t.gotoLocation) {
|
||||||
t.gotoLocation(d)
|
console.warn("has no gotoLocation", t);
|
||||||
})
|
}
|
||||||
|
t.gotoLocation(d);
|
||||||
|
});
|
||||||
|
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadState(s) {
|
function loadState(s) {
|
||||||
if (!s)
|
if (!s) {
|
||||||
return false
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
s = decodeURIComponent(s)
|
s = decodeURIComponent(s);
|
||||||
|
|
||||||
if (!s.startsWith("#!"))
|
if (!s.startsWith("#!")) {
|
||||||
return false
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var targetSet = false
|
var targetSet = false;
|
||||||
|
|
||||||
s.slice(2).split(";").forEach(function (d) {
|
s.slice(2).split(";").forEach(function (d) {
|
||||||
var args = d.split(":")
|
var args = d.split(":");
|
||||||
|
|
||||||
if (args[0] === "v" && args[1] in views) {
|
if (args[0] === "v" && args[1] in views) {
|
||||||
currentView = args[1]
|
currentView = args[1];
|
||||||
views[args[1]]()
|
views[args[1]]();
|
||||||
}
|
}
|
||||||
|
|
||||||
var id
|
var id;
|
||||||
|
|
||||||
if (args[0] === "n") {
|
if (args[0] === "n") {
|
||||||
id = args[1]
|
id = args[1];
|
||||||
if (id in objects.nodes) {
|
if (id in objects.nodes) {
|
||||||
currentObject = { node: objects.nodes[id] }
|
currentObject = {node: objects.nodes[id]};
|
||||||
gotoNode(objects.nodes[id])
|
gotoNode(objects.nodes[id]);
|
||||||
targetSet = true
|
targetSet = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args[0] === "l") {
|
if (args[0] === "l") {
|
||||||
id = args[1]
|
id = args[1];
|
||||||
if (id in objects.links) {
|
if (id in objects.links) {
|
||||||
currentObject = { link: objects.links[id] }
|
currentObject = {link: objects.links[id]};
|
||||||
gotoLink(objects.links[id])
|
gotoLink(objects.links[id]);
|
||||||
targetSet = true
|
targetSet = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return targetSet
|
return targetSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.start = function () {
|
self.start = function () {
|
||||||
running = true
|
running = true;
|
||||||
|
|
||||||
if (!loadState(window.location.hash))
|
if (!loadState(window.location.hash)) {
|
||||||
resetView(false)
|
resetView(false);
|
||||||
|
}
|
||||||
|
|
||||||
window.onpopstate = function (d) {
|
window.onpopstate = function (d) {
|
||||||
if (!loadState(d.state))
|
if (!loadState(d.state)) {
|
||||||
resetView(false)
|
resetView(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
};
|
||||||
|
|
||||||
self.view = function (d) {
|
self.view = function (d) {
|
||||||
if (d in views) {
|
if (d in views) {
|
||||||
views[d]()
|
views[d]();
|
||||||
|
|
||||||
if (!currentView || running)
|
if (!currentView || running) {
|
||||||
currentView = d
|
currentView = d;
|
||||||
|
|
||||||
if (!running)
|
|
||||||
return
|
|
||||||
|
|
||||||
saveState()
|
|
||||||
|
|
||||||
if (!currentObject) {
|
|
||||||
resetView(false)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("node" in currentObject)
|
if (!running) {
|
||||||
gotoNode(currentObject.node)
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ("link" in currentObject)
|
saveState();
|
||||||
gotoLink(currentObject.link)
|
|
||||||
|
if (!currentObject) {
|
||||||
|
resetView(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("node" in currentObject) {
|
||||||
|
gotoNode(currentObject.node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("link" in currentObject) {
|
||||||
|
gotoLink(currentObject.link);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
self.node = function (d) {
|
self.node = function (d) {
|
||||||
return function () {
|
return function () {
|
||||||
if (gotoNode(d)) {
|
if (gotoNode(d)) {
|
||||||
currentObject = { node: d }
|
currentObject = {node: d};
|
||||||
saveState()
|
saveState();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false;
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
self.link = function (d) {
|
self.link = function (d) {
|
||||||
return function () {
|
return function () {
|
||||||
if (gotoLink(d)) {
|
if (gotoLink(d)) {
|
||||||
currentObject = { link: d }
|
currentObject = {link: d};
|
||||||
saveState()
|
saveState();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false;
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
self.gotoLocation = gotoLocation
|
self.gotoLocation = gotoLocation;
|
||||||
|
|
||||||
self.reset = function () {
|
self.reset = function () {
|
||||||
resetView()
|
resetView();
|
||||||
}
|
};
|
||||||
|
|
||||||
self.addTarget = function (d) {
|
self.addTarget = function (d) {
|
||||||
targets.push(d)
|
targets.push(d);
|
||||||
}
|
};
|
||||||
|
|
||||||
self.removeTarget = function (d) {
|
self.removeTarget = function (d) {
|
||||||
targets = targets.filter( function (e) {
|
targets = targets.filter(function (e) {
|
||||||
return d !== e
|
return d !== e;
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
self.addView = function (k, d) {
|
self.addView = function (k, d) {
|
||||||
views[k] = d
|
views[k] = d;
|
||||||
}
|
};
|
||||||
|
|
||||||
self.setData = function (data) {
|
self.setData = function (data) {
|
||||||
objects.nodes = {}
|
objects.nodes = {};
|
||||||
objects.links = {}
|
objects.links = {};
|
||||||
|
|
||||||
data.nodes.all.forEach( function (d) {
|
data.nodes.all.forEach(function (d) {
|
||||||
objects.nodes[d.nodeinfo.node_id] = d
|
objects.nodes[d.nodeinfo.node_id] = d;
|
||||||
})
|
});
|
||||||
|
|
||||||
data.graph.links.forEach( function (d) {
|
data.graph.links.forEach(function (d) {
|
||||||
objects.links[d.id] = d
|
objects.links[d.id] = d;
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
return self
|
return self;
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,49 +1,52 @@
|
||||||
define([], function () {
|
define(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return function (el) {
|
return function (el) {
|
||||||
var self = this
|
var self = this;
|
||||||
|
|
||||||
var sidebar = document.createElement("div")
|
var sidebar = document.createElement("div");
|
||||||
sidebar.classList.add("sidebar")
|
sidebar.classList.add("sidebar");
|
||||||
el.appendChild(sidebar)
|
el.appendChild(sidebar);
|
||||||
|
|
||||||
var button = document.createElement("button")
|
var button = document.createElement("button");
|
||||||
sidebar.appendChild(button)
|
sidebar.appendChild(button);
|
||||||
|
|
||||||
button.classList.add("sidebarhandle")
|
button.classList.add("sidebarhandle", "shadow");
|
||||||
button.onclick = function () {
|
button.onclick = function () {
|
||||||
sidebar.classList.toggle("hidden")
|
sidebar.classList.toggle("hidden");
|
||||||
}
|
};
|
||||||
|
|
||||||
var container = document.createElement("div")
|
var container = document.createElement("div");
|
||||||
container.classList.add("container")
|
container.classList.add("container");
|
||||||
sidebar.appendChild(container)
|
sidebar.appendChild(container);
|
||||||
|
|
||||||
self.getWidth = function () {
|
self.getWidth = function () {
|
||||||
if (sidebar.classList.contains("hidden"))
|
if (sidebar.classList.contains("hidden")) {
|
||||||
return 0
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
var small = window.matchMedia("(max-width: 630pt)")
|
var small = window.matchMedia("(max-width: 630pt)");
|
||||||
return small.matches ? 0 : sidebar.offsetWidth
|
return small.matches ? 0 : sidebar.offsetWidth;
|
||||||
}
|
};
|
||||||
|
|
||||||
self.add = function (d) {
|
self.add = function (d) {
|
||||||
d.render(container)
|
d.render(container);
|
||||||
}
|
};
|
||||||
|
|
||||||
self.ensureVisible = function () {
|
self.ensureVisible = function () {
|
||||||
sidebar.classList.remove("hidden")
|
sidebar.classList.remove("hidden");
|
||||||
}
|
};
|
||||||
|
|
||||||
self.hide = function () {
|
self.hide = function () {
|
||||||
container.classList.add("hidden")
|
container.classList.add("hidden");
|
||||||
}
|
};
|
||||||
|
|
||||||
self.reveal = function () {
|
self.reveal = function () {
|
||||||
container.classList.remove("hidden")
|
container.classList.remove("hidden");
|
||||||
}
|
};
|
||||||
|
|
||||||
self.container = sidebar
|
self.container = sidebar;
|
||||||
|
|
||||||
return self
|
return self;
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,63 +1,70 @@
|
||||||
define(["moment", "virtual-dom"], function (moment, V) {
|
define(["moment", "virtual-dom", "helper", "moment.de"], function (moment, V, helper) {
|
||||||
return function(nodes, field, router, title) {
|
"use strict";
|
||||||
var self = this
|
|
||||||
var el, tbody
|
return function (nodes, field, router, title) {
|
||||||
|
var self = this;
|
||||||
|
var el, tbody;
|
||||||
|
|
||||||
self.render = function (d) {
|
self.render = function (d) {
|
||||||
el = document.createElement("div")
|
el = d;
|
||||||
d.appendChild(el)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
self.setData = function (data) {
|
self.setData = function (data) {
|
||||||
var list = data.nodes[nodes]
|
var list = data.nodes[nodes];
|
||||||
|
|
||||||
if (list.length === 0) {
|
if (list.length === 0) {
|
||||||
while (el.firstChild)
|
while (el.firstChild) {
|
||||||
el.removeChild(el.firstChild)
|
el.removeChild(el.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
tbody = null
|
tbody = null;
|
||||||
|
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tbody) {
|
if (!tbody) {
|
||||||
var h2 = document.createElement("h2")
|
var h2 = document.createElement("h2");
|
||||||
h2.textContent = title
|
h2.textContent = title;
|
||||||
el.appendChild(h2)
|
el.appendChild(h2);
|
||||||
|
|
||||||
var table = document.createElement("table")
|
var table = document.createElement("table");
|
||||||
el.appendChild(table)
|
table.classList.add("node-list");
|
||||||
|
el.appendChild(table);
|
||||||
|
|
||||||
tbody = document.createElement("tbody")
|
tbody = document.createElement("tbody");
|
||||||
tbody.last = V.h("tbody")
|
tbody.last = V.h("tbody");
|
||||||
table.appendChild(tbody)
|
table.appendChild(tbody);
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = list.map( function (d) {
|
var items = list.map(function (d) {
|
||||||
var time = moment(d[field]).from(data.now)
|
var time = moment(d[field]).from(data.now);
|
||||||
var td1Content = []
|
var 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", {
|
||||||
onclick: router.node(d),
|
className: aClass.join(" "),
|
||||||
href: "#!n:" + d.nodeinfo.node_id
|
onclick: router.node(d),
|
||||||
}, d.nodeinfo.hostname))
|
href: "#!n:" + d.nodeinfo.node_id
|
||||||
|
}, d.nodeinfo.hostname));
|
||||||
|
|
||||||
if (has_location(d))
|
if (helper.hasLocation(d)) {
|
||||||
td1Content.push(V.h("span", {className: "icon ion-location"}))
|
td0Content.push(V.h("span", {className: "icon ion-location"}));
|
||||||
|
}
|
||||||
|
|
||||||
var td1 = V.h("td", td1Content)
|
var td0 = V.h("td", td0Content);
|
||||||
var td2 = V.h("td", time)
|
var td1 = V.h("td", td1Content);
|
||||||
|
var td2 = V.h("td", time);
|
||||||
|
|
||||||
return V.h("tr", [td1, td2])
|
return V.h("tr", [td0, td1, td2]);
|
||||||
})
|
});
|
||||||
|
|
||||||
var tbodyNew = V.h("tbody", items)
|
var tbodyNew = V.h("tbody", items);
|
||||||
tbody = V.patch(tbody, V.diff(tbody.last, tbodyNew))
|
tbody = V.patch(tbody, V.diff(tbody.last, tbodyNew));
|
||||||
tbody.last = tbodyNew
|
tbody.last = tbodyNew;
|
||||||
}
|
};
|
||||||
|
|
||||||
return self
|
return self;
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,57 +1,64 @@
|
||||||
define(["virtual-dom"], function (V) {
|
define(["virtual-dom"], function (V) {
|
||||||
return function(headings, sortIndex, renderRow) {
|
"use strict";
|
||||||
var data
|
|
||||||
var sortReverse = false
|
return function (headings, sortIndex, renderRow) {
|
||||||
var el = document.createElement("table")
|
var data;
|
||||||
var elLast = V.h("table")
|
var sortReverse = false;
|
||||||
|
var el = document.createElement("table");
|
||||||
|
var elLast = V.h("table");
|
||||||
|
|
||||||
function sortTable(i) {
|
function sortTable(i) {
|
||||||
sortReverse = i === sortIndex ? !sortReverse : false
|
sortReverse = i === sortIndex ? !sortReverse : false;
|
||||||
sortIndex = i
|
sortIndex = i;
|
||||||
|
|
||||||
updateView()
|
updateView();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortTableHandler(i) {
|
function sortTableHandler(i) {
|
||||||
return function () { sortTable(i) }
|
return function () {
|
||||||
|
sortTable(i);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateView() {
|
function updateView() {
|
||||||
var children = []
|
var children = [];
|
||||||
|
|
||||||
if (data.length !== 0) {
|
if (data.length !== 0) {
|
||||||
var th = headings.map(function (d, i) {
|
var th = headings.map(function (d, i) {
|
||||||
var properties = { onclick: sortTableHandler(i),
|
var properties = {
|
||||||
className: "sort-header"
|
onclick: sortTableHandler(i),
|
||||||
}
|
className: "sort-header"
|
||||||
|
};
|
||||||
|
|
||||||
if (sortIndex === i)
|
if (sortIndex === i) {
|
||||||
properties.className += sortReverse ? " sort-up" : " sort-down"
|
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)
|
if (headings[sortIndex].reverse ? !sortReverse : sortReverse) {
|
||||||
links = links.reverse()
|
links = links.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
children.push(V.h("thead", V.h("tr", th)))
|
children.push(V.h("thead", V.h("tr", th)));
|
||||||
children.push(V.h("tbody", links.map(renderRow)))
|
children.push(V.h("tbody", links.map(renderRow)));
|
||||||
}
|
}
|
||||||
|
|
||||||
var elNew = V.h("table", children)
|
var elNew = V.h("table", children);
|
||||||
el = V.patch(el, V.diff(elLast, elNew))
|
el = V.patch(el, V.diff(elLast, elNew));
|
||||||
elLast = elNew
|
elLast = elNew;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setData = function (d) {
|
this.setData = function (d) {
|
||||||
data = d
|
data = d;
|
||||||
updateView()
|
updateView();
|
||||||
}
|
};
|
||||||
|
|
||||||
this.el = el
|
this.el = el;
|
||||||
|
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
74
lib/tabs.js
74
lib/tabs.js
|
@ -1,57 +1,63 @@
|
||||||
define([], function () {
|
define(function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
return function () {
|
return function () {
|
||||||
var self = this
|
var self = this;
|
||||||
|
|
||||||
var tabs = document.createElement("ul")
|
var tabs = document.createElement("ul");
|
||||||
tabs.classList.add("tabs")
|
tabs.classList.add("tabs");
|
||||||
|
|
||||||
var container = document.createElement("div")
|
var container = document.createElement("div");
|
||||||
|
|
||||||
function gotoTab(li) {
|
function gotoTab(li) {
|
||||||
for (var i = 0; i < tabs.children.length; i++)
|
for (var i = 0; i < tabs.children.length; i++) {
|
||||||
tabs.children[i].classList.remove("visible")
|
tabs.children[i].classList.remove("visible");
|
||||||
|
}
|
||||||
|
|
||||||
while (container.firstChild)
|
while (container.firstChild) {
|
||||||
container.removeChild(container.firstChild)
|
container.removeChild(container.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
li.classList.add("visible")
|
li.classList.add("visible");
|
||||||
|
|
||||||
var tab = document.createElement("div")
|
var tab = document.createElement("div");
|
||||||
tab.classList.add("tab")
|
tab.classList.add("tab");
|
||||||
container.appendChild(tab)
|
container.appendChild(tab);
|
||||||
li.child.render(tab)
|
li.child.render(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchTab() {
|
function switchTab() {
|
||||||
gotoTab(this)
|
gotoTab(this);
|
||||||
|
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.add = function (title, d) {
|
self.add = function (title, d) {
|
||||||
var li = document.createElement("li")
|
var li = document.createElement("li");
|
||||||
li.textContent = title
|
li.textContent = title;
|
||||||
li.onclick = switchTab
|
li.onclick = switchTab;
|
||||||
li.child = d
|
li.child = d;
|
||||||
tabs.appendChild(li)
|
tabs.appendChild(li);
|
||||||
|
|
||||||
var anyVisible = false
|
var anyVisible = false;
|
||||||
|
|
||||||
for (var i = 0; i < tabs.children.length; i++)
|
for (var i = 0; i < tabs.children.length; i++) {
|
||||||
if (tabs.children[i].classList.contains("visible")) {
|
if (tabs.children[i].classList.contains("visible")) {
|
||||||
anyVisible = true
|
anyVisible = true;
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!anyVisible)
|
if (!anyVisible) {
|
||||||
gotoTab(li)
|
gotoTab(li);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.render = function (el) {
|
self.render = function (el) {
|
||||||
el.appendChild(tabs)
|
el.appendChild(tabs);
|
||||||
el.appendChild(container)
|
el.appendChild(container);
|
||||||
}
|
};
|
||||||
|
|
||||||
return self
|
return self;
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
43
lib/title.js
43
lib/title.js
|
@ -1,35 +1,40 @@
|
||||||
define(function () {
|
define(function () {
|
||||||
return function (config) {
|
"use strict";
|
||||||
|
|
||||||
|
return function (config) {
|
||||||
function setTitle(d) {
|
function setTitle(d) {
|
||||||
var title = [config.siteName]
|
var title = [config.siteName];
|
||||||
|
|
||||||
if (d !== undefined)
|
if (d !== undefined) {
|
||||||
title.push(d)
|
title.push(d);
|
||||||
|
}
|
||||||
|
|
||||||
document.title = title.join(": ")
|
document.title = title.join(": ");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resetView = function () {
|
this.resetView = function () {
|
||||||
setTitle()
|
setTitle();
|
||||||
}
|
};
|
||||||
|
|
||||||
this.gotoNode = function (d) {
|
this.gotoNode = function (d) {
|
||||||
if (d)
|
if (d) {
|
||||||
setTitle(d.nodeinfo.hostname)
|
setTitle(d.nodeinfo.hostname);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.gotoLink = function (d) {
|
this.gotoLink = function (d) {
|
||||||
if (d)
|
if (d) {
|
||||||
setTitle((d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + " – " + d.target.node.nodeinfo.hostname)
|
setTitle((d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + " – " + d.target.node.nodeinfo.hostname);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.gotoLocation = function() {
|
this.gotoLocation = function () {
|
||||||
//ignore
|
//ignore
|
||||||
}
|
};
|
||||||
|
|
||||||
this.destroy = function () {
|
this.destroy = function () {
|
||||||
}
|
};
|
||||||
|
|
||||||
return this
|
return this;
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
243
lib/utils/helper.js
Normal file
243
lib/utils/helper.js
Normal 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;
|
||||||
|
}
|
||||||
|
});
|
|
@ -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
|
|
||||||
})
|
|
58
package.json
58
package.json
|
@ -1,24 +1,34 @@
|
||||||
{
|
{
|
||||||
"name": "hopglass",
|
"name": "hopglass",
|
||||||
|
"license": "AGPL-3.0",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/hopglass/hopglass.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/hopglass/hopglass/issues"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node -e \"require('grunt').cli()\" '' clean lint"
|
"test": "node -e \"require('grunt').cli()\" '' clean lint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^6.3.3",
|
"autoprefixer": "^6.3.6",
|
||||||
"grunt": "^0.4.5",
|
"eslint": "^2.11.0",
|
||||||
"grunt-check-dependencies": "^0.6.0",
|
"eslint-config-defaults": "^9.0.0",
|
||||||
"grunt-contrib-clean": "^0.6.0",
|
"grunt": "^1.0.1",
|
||||||
"grunt-contrib-connect": "^0.8.0",
|
"grunt-bower-install-simple": "^1.2.3",
|
||||||
"grunt-contrib-copy": "^0.5.0",
|
"grunt-check-dependencies": "^0.12.0",
|
||||||
"grunt-contrib-cssmin": "^0.12.2",
|
"grunt-contrib-clean": "^1.0.0",
|
||||||
"grunt-contrib-requirejs": "^0.4.4",
|
"grunt-contrib-connect": "^1.0.2",
|
||||||
"grunt-sass": "^1.1.0",
|
"grunt-contrib-copy": "^1.0.0",
|
||||||
"grunt-postcss": "^0.7.2",
|
"grunt-contrib-requirejs": "^1.0.0",
|
||||||
"grunt-contrib-uglify": "^0.5.1",
|
"grunt-contrib-uglify": "^1.0.1",
|
||||||
"grunt-contrib-watch": "^0.6.1",
|
"grunt-contrib-watch": "^1.0.0",
|
||||||
"grunt-eslint": "^10.0.0",
|
"grunt-eslint": "^18.1.0",
|
||||||
"grunt-bower-install-simple": "^1.1.2",
|
"grunt-inline": "^0.3.6",
|
||||||
"grunt-git-describe": "^2.3.2"
|
"grunt-postcss": "^0.8.0",
|
||||||
|
"grunt-sass": "^1.2.0",
|
||||||
|
"grunt-sass-lint": "^0.2.0"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"env": {
|
"env": {
|
||||||
|
@ -26,24 +36,6 @@
|
||||||
"amd": true,
|
"amd": true,
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"node": true
|
"node": true
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"showStat": false,
|
|
||||||
"attributeEntry": false,
|
|
||||||
"dictGet": false,
|
|
||||||
"getJSON": false,
|
|
||||||
"has_location": false,
|
|
||||||
"limit": false,
|
|
||||||
"localStorageTest": false,
|
|
||||||
"offline": false,
|
|
||||||
"one": false,
|
|
||||||
"online": false,
|
|
||||||
"showDistance": false,
|
|
||||||
"showTq": false,
|
|
||||||
"sortByKey": false,
|
|
||||||
"subtract": false,
|
|
||||||
"sum": false,
|
|
||||||
"trueDefault": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,3 +28,7 @@ h5 {
|
||||||
h6 {
|
h6 {
|
||||||
font-size: 0.67em;
|
font-size: 0.67em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
23
scss/_loader.scss
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
10
scss/custom/_custom.scss
Normal 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
|
4
scss/custom/_variables.scss
Normal file
4
scss/custom/_variables.scss
Normal 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);
|
519
scss/main.scss
519
scss/main.scss
|
@ -1,493 +1,26 @@
|
||||||
@import '_reset';
|
// Set variables
|
||||||
@import '_shadow';
|
@import 'modules/variables';
|
||||||
@import '_base';
|
@import 'custom/variables';
|
||||||
@import '_leaflet';
|
// Add modules
|
||||||
@import '_leaflet.label';
|
@import 'mixins/shadow';
|
||||||
@import '_filters';
|
@import 'mixins/icon-mixin';
|
||||||
|
@import 'modules/reset';
|
||||||
$minscreenwidth: 630pt;
|
@import 'modules/base';
|
||||||
$sidebarwidth: 420pt;
|
@import 'modules/icon';
|
||||||
$sidebarwidthsmall: 320pt;
|
@import 'leaflet';
|
||||||
$buttondistance: 12pt;
|
@import 'leaflet.label';
|
||||||
|
@import 'modules/leaflet-layer';
|
||||||
@import '_sidebar';
|
@import 'modules/table';
|
||||||
@import '_map';
|
@import 'modules/filter';
|
||||||
@import '_forcegraph';
|
@import 'modules/sidebar';
|
||||||
@import '_legend';
|
@import 'modules/map';
|
||||||
|
@import 'modules/forcegraph';
|
||||||
.content {
|
@import 'modules/legend';
|
||||||
position: fixed;
|
@import 'modules/proportion';
|
||||||
width: 100%;
|
@import 'modules/tabs';
|
||||||
height: 100vh;
|
@import 'modules/node';
|
||||||
|
@import 'modules/infobox';
|
||||||
.buttons {
|
@import 'modules/button';
|
||||||
direction: rtl;
|
|
||||||
unicode-bidi: bidi-override;
|
// Make adjustments in custom scss
|
||||||
|
@import 'custom/custom';
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
1
scss/mixins/_icon-mixin.scss
Symbolic link
1
scss/mixins/_icon-mixin.scss
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../assets/icons/_icon-mixin.scss
|
14
scss/mixins/_shadow.scss
Normal file
14
scss/mixins/_shadow.scss
Normal 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
56
scss/modules/_base.scss
Normal 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
47
scss/modules/_button.scss
Normal 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
40
scss/modules/_filter.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
.graph {
|
.graph {
|
||||||
|
background: $color-gray-dark;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #2B2B2B;
|
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
}
|
}
|
27
scss/modules/_infobox.scss
Normal file
27
scss/modules/_infobox.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
34
scss/modules/_leaflet-layer.scss
Normal file
34
scss/modules/_leaflet-layer.scss
Normal 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
32
scss/modules/_legend.scss
Normal 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
41
scss/modules/_map.scss
Normal 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
21
scss/modules/_node.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
20
scss/modules/_proportion.scss
Normal file
20
scss/modules/_proportion.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
/**
|
// Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
|
||||||
* Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
|
// http://cssreset.com
|
||||||
* http://cssreset.com
|
|
||||||
*/
|
|
||||||
html, body, div, span, applet, object, iframe,
|
html, body, div, span, applet, object, iframe,
|
||||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||||
a, abbr, acronym, address, big, cite, code,
|
a, abbr, acronym, address, big, cite, code,
|
||||||
|
@ -12,23 +10,17 @@ dl, dt, dd, ol, ul, li,
|
||||||
fieldset, form, label, legend,
|
fieldset, form, label, legend,
|
||||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||||
article, aside, canvas, details, embed,
|
article, aside, canvas, details, embed,
|
||||||
figure, figcaption, footer, header, hgroup,
|
figure, figcaption, footer, header,
|
||||||
menu, nav, output, ruby, section, summary,
|
menu, nav, output, ruby, section, summary,
|
||||||
time, mark, audio, video {
|
time, mark, audio, video {
|
||||||
|
border: 0;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
|
||||||
font-size: 100%;
|
|
||||||
font: inherit;
|
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* HTML5 display-role reset for older browsers */
|
|
||||||
article, aside, details, figcaption, figure,
|
|
||||||
footer, header, hgroup, menu, nav, section {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
@ -41,12 +33,6 @@ blockquote, q {
|
||||||
quotes: none;
|
quotes: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote:before, blockquote:after,
|
|
||||||
q:before, q:after {
|
|
||||||
content: '';
|
|
||||||
content: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
127
scss/modules/_sidebar.scss
Normal file
127
scss/modules/_sidebar.scss
Normal 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
76
scss/modules/_table.scss
Normal 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
27
scss/modules/_tabs.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
22
scss/modules/_variables.scss
Normal file
22
scss/modules/_variables.scss
Normal 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
1
scss/modules/icon.scss
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../assets/icons/icon.scss
|
100
tasks/build.js
100
tasks/build.js
|
@ -1,13 +1,10 @@
|
||||||
module.exports = function(grunt) {
|
module.exports = function (grunt) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
grunt.config.merge({
|
grunt.config.merge({
|
||||||
bowerdir: "bower_components",
|
bowerdir: "bower_components",
|
||||||
copy: {
|
copy: {
|
||||||
html: {
|
html: {
|
||||||
options: {
|
|
||||||
process: function (content) {
|
|
||||||
return content.replace("#revision#", grunt.option("gitRevision"))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
src: ["*.html"],
|
src: ["*.html"],
|
||||||
expand: true,
|
expand: true,
|
||||||
cwd: "html/",
|
cwd: "html/",
|
||||||
|
@ -26,31 +23,29 @@ module.exports = function(grunt) {
|
||||||
dest: "build/vendor/"
|
dest: "build/vendor/"
|
||||||
},
|
},
|
||||||
robotoSlab: {
|
robotoSlab: {
|
||||||
src: [ "fonts/*",
|
src: ["fonts/*",
|
||||||
"roboto-slab-fontface.css"
|
"roboto-slab-fontface.css"
|
||||||
],
|
],
|
||||||
expand: true,
|
expand: true,
|
||||||
dest: "build/",
|
dest: "build/",
|
||||||
cwd: "bower_components/roboto-slab-fontface"
|
cwd: "bower_components/roboto-slab-fontface"
|
||||||
},
|
},
|
||||||
roboto: {
|
roboto: {
|
||||||
src: [ "fonts/*",
|
src: ["fonts/*",
|
||||||
"roboto-fontface.css"
|
"roboto-fontface.css"
|
||||||
],
|
],
|
||||||
expand: true,
|
expand: true,
|
||||||
dest: "build/",
|
dest: "build/",
|
||||||
cwd: "bower_components/roboto-fontface"
|
cwd: "bower_components/roboto-fontface"
|
||||||
},
|
},
|
||||||
ionicons: {
|
ionicons: {
|
||||||
src: [ "fonts/*",
|
src: ["fonts/*"],
|
||||||
"css/ionicons.min.css"
|
|
||||||
],
|
|
||||||
expand: true,
|
expand: true,
|
||||||
dest: "build/",
|
dest: "build/",
|
||||||
cwd: "bower_components/ionicons/"
|
cwd: "assets/icons/"
|
||||||
},
|
},
|
||||||
leafletImages: {
|
leafletImages: {
|
||||||
src: [ "images/*" ],
|
src: ["images/*"],
|
||||||
expand: true,
|
expand: true,
|
||||||
dest: "build/",
|
dest: "build/",
|
||||||
cwd: "bower_components/leaflet/dist/"
|
cwd: "bower_components/leaflet/dist/"
|
||||||
|
@ -80,47 +75,58 @@ module.exports = function(grunt) {
|
||||||
src: "build/style.css"
|
src: "build/style.css"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cssmin: {
|
inline: {
|
||||||
target: {
|
dist: {
|
||||||
files: {
|
options: {
|
||||||
"build/style.css": [ "bower_components/leaflet/dist/leaflet.css",
|
cssmin: true,
|
||||||
"bower_components/Leaflet.label/dist/leaflet.label.css",
|
uglify: true
|
||||||
"style.css"
|
},
|
||||||
]
|
src: "build/index.html",
|
||||||
}
|
dest: "build/index.html"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bower-install-simple": {
|
"bower-install-simple": {
|
||||||
options: {
|
options: {
|
||||||
directory: "<%=bowerdir%>",
|
directory: "<%=bowerdir%>",
|
||||||
color: true,
|
color: true,
|
||||||
interactive: false,
|
interactive: false,
|
||||||
production: true
|
production: true
|
||||||
},
|
|
||||||
"prod": {
|
|
||||||
options: {
|
|
||||||
production: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
"prod": {
|
||||||
|
options: {
|
||||||
|
production: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
requirejs: {
|
requirejs: {
|
||||||
compile: {
|
default: {
|
||||||
options: {
|
options: {
|
||||||
baseUrl: "lib",
|
baseUrl: "lib",
|
||||||
name: "../bower_components/almond/almond",
|
name: "../bower_components/almond/almond",
|
||||||
mainConfigFile: "app.js",
|
mainConfigFile: "app.js",
|
||||||
include: "../app",
|
include: "../app",
|
||||||
wrap: true,
|
out: "build/app.js",
|
||||||
optimize: "uglify",
|
build: true
|
||||||
out: "build/app.js"
|
}
|
||||||
|
},
|
||||||
|
dev: {
|
||||||
|
options: {
|
||||||
|
baseUrl: "lib",
|
||||||
|
name: "../bower_components/almond/almond",
|
||||||
|
mainConfigFile: "app.js",
|
||||||
|
include: "../app",
|
||||||
|
optimize: "none",
|
||||||
|
out: "build/app.js",
|
||||||
|
build: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
grunt.loadNpmTasks("grunt-bower-install-simple")
|
grunt.loadNpmTasks("grunt-bower-install-simple");
|
||||||
grunt.loadNpmTasks("grunt-contrib-copy")
|
grunt.loadNpmTasks("grunt-contrib-copy");
|
||||||
grunt.loadNpmTasks("grunt-contrib-requirejs")
|
grunt.loadNpmTasks("grunt-contrib-requirejs");
|
||||||
grunt.loadNpmTasks("grunt-sass")
|
grunt.loadNpmTasks("grunt-sass");
|
||||||
grunt.loadNpmTasks("grunt-postcss")
|
grunt.loadNpmTasks("grunt-postcss");
|
||||||
}
|
grunt.loadNpmTasks("grunt-inline");
|
||||||
|
};
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
module.exports = function (grunt) {
|
module.exports = function (grunt) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
grunt.config.merge({
|
grunt.config.merge({
|
||||||
clean: {
|
clean: {
|
||||||
build: ["build/**/*", "node_modules/grunt-newer/.cache"]
|
build: ["build/**/*", "node_modules/grunt-newer/.cache"]
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
grunt.loadNpmTasks("grunt-contrib-clean")
|
grunt.loadNpmTasks("grunt-contrib-clean");
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
module.exports = function (grunt) {
|
module.exports = function (grunt) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
grunt.config.merge({
|
grunt.config.merge({
|
||||||
connect: {
|
connect: {
|
||||||
server: {
|
server: {
|
||||||
options: {
|
options: {
|
||||||
base: "build/", //TODO: once grunt-contrib-connect 0.9 is released, set index file
|
base: {
|
||||||
|
path: "build",
|
||||||
|
options: {
|
||||||
|
index: "index.html"
|
||||||
|
}
|
||||||
|
},
|
||||||
livereload: true
|
livereload: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +20,8 @@ module.exports = function (grunt) {
|
||||||
options: {
|
options: {
|
||||||
livereload: true
|
livereload: true
|
||||||
},
|
},
|
||||||
files: ["*.css", "app.js", "lib/**/*.js", "*.html"],
|
files: ["*.css", "app.js", "lib/**/*.js", "*.html", "scss/**/*.scss"],
|
||||||
tasks: ["default"]
|
tasks: ["dev"]
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
options: {
|
options: {
|
||||||
|
@ -24,8 +31,8 @@ module.exports = function (grunt) {
|
||||||
tasks: []
|
tasks: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
grunt.loadNpmTasks("grunt-contrib-connect")
|
grunt.loadNpmTasks("grunt-contrib-connect");
|
||||||
grunt.loadNpmTasks("grunt-contrib-watch")
|
grunt.loadNpmTasks("grunt-contrib-watch");
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = function (grunt) {
|
module.exports = function (grunt) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
grunt.config.merge({
|
grunt.config.merge({
|
||||||
checkDependencies: {
|
checkDependencies: {
|
||||||
options: {
|
options: {
|
||||||
|
@ -11,19 +13,13 @@ module.exports = function (grunt) {
|
||||||
},
|
},
|
||||||
npm: {}
|
npm: {}
|
||||||
},
|
},
|
||||||
eslint: {
|
sasslint: {
|
||||||
options: {
|
options: {
|
||||||
rules: {
|
configFile: '.sass-lint.yml'
|
||||||
"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
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
target: ['scss/main.scss', 'scss/*/*.scss']
|
||||||
|
},
|
||||||
|
eslint: {
|
||||||
sources: {
|
sources: {
|
||||||
src: ["app.js", "!Gruntfile.js", "lib/**/*.js"]
|
src: ["app.js", "!Gruntfile.js", "lib/**/*.js"]
|
||||||
},
|
},
|
||||||
|
@ -31,8 +27,9 @@ module.exports = function (grunt) {
|
||||||
src: ["Gruntfile.js", "tasks/*.js"]
|
src: ["Gruntfile.js", "tasks/*.js"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
grunt.loadNpmTasks("grunt-check-dependencies")
|
grunt.loadNpmTasks("grunt-check-dependencies");
|
||||||
grunt.loadNpmTasks("grunt-eslint")
|
grunt.loadNpmTasks('grunt-sass-lint');
|
||||||
}
|
grunt.loadNpmTasks("grunt-eslint");
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue