Compare commits

..

14 commits

Author SHA1 Message Date
Alexander d70d3d7c5f Add Node.js LTS version in travis.yml (#105) 2019-02-01 06:52:56 +01:00
Milan Pässler fe17e32886 fix: roboto and ionicons 2019-01-26 04:55:33 +01:00
Milan Pässler 420be21fa5 add version number 2019-01-24 00:26:00 +01:00
Milan Pässler a971dcfeed update installation instructions 2019-01-20 20:21:17 +01:00
Milan Pässler 0a05523dd1 remove bower and jshashes 2019-01-20 18:15:14 +01:00
Milan Pässler f416c33498 fix(travis): update nodejs, build process 2019-01-20 15:18:59 +01:00
Milan Pässler 93e0a9c758 update deps, fix build with node v10 2019-01-20 15:08:12 +01:00
anoy d231cbd5bc
Merge pull request #102 from jjx-/master
Updated DOP-NRW WMS URL
2018-08-25 18:24:55 +02:00
JJX 11368e60de
Updated DOP-NRW WMS URL
Der alte WMS Dienst für Luftbilder des Landes wird in Kürze abgeschaltet, dies ist der Neue.
2018-08-24 11:40:27 +02:00
PetaByteBoy // Milan Pässler 587740af80 change map layers in default config 2017-04-12 02:22:47 +02:00
Simon Wüllhorst 1235d8cb46 lib/map: support wms layers. (#89) 2017-04-12 01:56:05 +02:00
Andreas Ziegler 100268f3b7 change links to new upstream project name in readme & about box (#91) 2017-04-02 16:13:05 +02:00
Milan Pässler 35589eabef fix html indention 2017-03-16 23:08:49 +01:00
H4ndl3 8f7c63bbce add loading animation
Create _loader.scss

Update main.scss

Update _base.scss

Update gui.js

Update index.html

removed trailing spaces

removed trailing white spaces for real
2017-03-16 19:54:57 +01:00
82 changed files with 7425 additions and 4578 deletions

View file

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

2
.gitignore vendored
View file

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

View file

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

View file

@ -1,7 +1,7 @@
language: node_js language: node_js
before_install: node_js:
- gem install sass - "lts/*"
- npm install -g grunt-cli - "node"
install: install:
- npm install - yarn
script: grunt script: node_modules/.bin/grunt

View file

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

View file

@ -1,8 +1,8 @@
[![Build Status](https://travis-ci.org/plumpudding/hopglass.svg?branch=master)](https://travis-ci.org/plumpudding/hopglass) [![Build Status](https://travis-ci.org/hopglass/hopglass.svg?branch=master)](https://travis-ci.org/hopglass/hopglass)
# HopGlass # HopGlass
HopGlass is a frontend for the [HopGlass Server](https://github.com/plumpudding/hopglass-server). HopGlass is a frontend for the [HopGlass Server](https://github.com/hopglass/hopglass-server).
# Screenshots # Screenshots
@ -14,29 +14,32 @@ HopGlass is a frontend for the [HopGlass Server](https://github.com/plumpudding/
# Dependencies # Dependencies
- npm - NodeJS
- bower - yarn (recommended) or npm
- grunt-cli
- Sass (>= 3.2)
# Installing dependencies # Installing dependencies
Install npm package-manager. On Debian-like systems run: Install npm package-manager. On Debian-like systems run:
sudo apt-get install npm sudo apt-get install nodejs
On Mac you have to install only npm via brew and sass **Note:** The official Debian packages for NodeJS are quite old, you might want to check at [NodeSource](https://github.com/nodesource/distributions/blob/master/README.md) for current binaries installable with your distribution's package manager.
On Mac you can install nodejs and yarn via brew:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install node brew install node
npm install node-sass brew install yarn
On Arch Linux you can install nodejs and yarn via pacman:
sudo pacman -S nodejs yarn
Execute these commands on your server as a normal user to prepare the dependencies: Execute these commands on your server as a normal user to prepare the dependencies:
git clone https://github.com/plumpudding/hopglass git clone https://github.com/hopglass/hopglass
cd hopglass cd hopglass
npm install yarn install # or `npm install`
npm install grunt-cli
# Building # Building
@ -46,22 +49,13 @@ 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/hopglass/hopglass-server) or an array containing multiple addresses.
Don't forget the trailing slash! Don't forget the trailing slash!
Also, proxying the data through a webserver will allow GZip and thus will greatly reduce bandwidth consumption. Also, proxying the data through a webserver will allow GZip and thus will greatly reduce bandwidth consumption.
It may help with firewall problems too. It may help with firewall problems too.

44
app.js
View file

@ -1,30 +1,36 @@
"use strict";
require.config({ require.config({
baseUrl: "lib", baseUrl: "lib",
paths: { paths: {
"leaflet": "../bower_components/leaflet/dist/leaflet", "leaflet": "../node_modules/leaflet/dist/leaflet",
"leaflet.label": "../bower_components/Leaflet.label/dist/leaflet.label", "leaflet.label": "../node_modules/leaflet-label/dist/leaflet.label",
"leaflet.providers": "../bower_components/leaflet-providers/leaflet-providers", "leaflet.providers": "../node_modules/leaflet-providers/leaflet-providers",
"chroma-js": "../bower_components/chroma-js/chroma.min", "chroma-js": "../node_modules/chroma-js/chroma.min",
"moment": "../bower_components/moment/min/moment.min", "moment": "../node_modules/moment/min/moment-with-locales.min",
"moment.de": "../bower_components/moment/locale/de", "tablesort": "../node_modules/tablesort/tablesort.min",
"tablesort": "../bower_components/tablesort/src/tablesort", "tablesort.numeric": "../node_modules/tablesort/src/sorts/tablesort.numeric",
"d3": "../bower_components/d3/d3.min", "d3": "../node_modules/d3/d3.min",
"virtual-dom": "../bower_components/virtual-dom/dist/virtual-dom", "numeral": "../node_modules/numeraljs/min/numeral.min",
"rbush": "../bower_components/rbush/rbush", "numeral-intl": "../node_modules/numeraljs/min/languages.min",
"helper": "utils/helper" "virtual-dom": "../node_modules/virtual-dom/dist/virtual-dom",
"rbush": "../node_modules/rbush/rbush",
"helper": "../helper",
"jshashes": "../node_modules/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, helper) { require(["main", "helper"], function (main) {
helper.getJSON("config.json").then(main); getJSON("config.json").then(main)
}); })

View file

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

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

View file

@ -1,32 +0,0 @@
{
"name": "hopglass",
"ignore": [
"node_modules",
"bower_components",
"**/.*",
"test",
"tests"
],
"dependencies": {
"Leaflet.label": "~0.2.1",
"chroma-js": "~1.1.1",
"leaflet": "~0.7.7",
"moment": "~2.13.0",
"requirejs": "~2.2.0",
"tablesort": "https://github.com/tristen/tablesort.git#v4.0.1",
"roboto-slab-fontface": "*",
"es6-shim": "~0.35.1",
"almond": "~0.3.2",
"d3": "~3.5.17",
"roboto-fontface": "~0.4.5",
"virtual-dom": "~2.1.1",
"leaflet-providers": "~1.1.10",
"rbush": "https://github.com/mourner/rbush.git#~1.4.3"
},
"authors": [
"Milan Pässler <me@petabyteboy.de>",
"Nils Schneider <nils@nilsschneider.net>"
],
"license": "AGPL3",
"private": true
}

9
build.js Normal file
View file

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

View file

@ -5,11 +5,25 @@
"showContact": true, "showContact": true,
"maxAge": 14, "maxAge": 14,
"mapLayers": [ "mapLayers": [
{ "name": "CartoDB",
"url": "https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png",
"config": {
"maxZoom": 18,
"attribution": "&copy; <a href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a>, &copy; | <a href=\"https://carto.com/attribution\">CARTO</a>"
}
},
{ {
"name": "OpenStreetMap.HOT" "name": "OpenStreetMap.HOT"
}, },
{ {
"name": "Stamen.TonerLite" "name": "Luftbilder NRW",
"url": "https://www.wms.nrw.de/geobasis/wms_nw_dop?",
"config": {
"maxZoom": 20,
"attribution": "<a href=\"http://www.bezreg-koeln.nrw.de/brk_internet/geobasis/luftbilderzeugnisse/digitale_orthophotos/index.html\">DOP20</a>, Land NRW (2017), Datenlizenz Deutschland - Namensnennung - Version 2.0 (<a href=\"https://www.govdata.de/dl-de/by-2-0\">www.govdata.de/dl-de/by-2-0</a>)",
"format": "image/jpeg",
"layers": "nw_dop_rgb"
}
} }
], ],
"nodeInfos": [ "nodeInfos": [

233
helper.js
View file

@ -1,241 +1,228 @@
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 d.distance.toFixed(0) + " m" return numeral(d.distance).format("0,0") + " m"
} }
function showTq(d) { function showTq(d) {
return (1 / d.tq * 100).toFixed(0) + "%" return numeral(1/d.tq).format("0%")
} }
/* Infobox stuff (XXX: move to module) */ /* Infobox stuff (XXX: move to module) */
function attributeEntry(el, label, value) { function attributeEntry(el, label, value) {
if (value === null || value == undefined) { if (value === null || value == undefined)
return; return
var tr = document.createElement("tr")
var th = document.createElement("th")
if (typeof label === "string")
th.textContent = label
else {
th.appendChild(label)
tr.className = "routerpic"
} }
var tr = document.createElement("tr"); tr.appendChild(th)
var th = document.createElement("th");
if (typeof label === "string") {
th.textContent = label;
} else {
th.appendChild(label);
tr.className = "routerpic";
}
tr.appendChild(th); var td = document.createElement("td")
var td = document.createElement("td"); if (typeof value == "function")
value(td)
else
td.appendChild(document.createTextNode(value))
if (typeof value == "function") { tr.appendChild(td)
value(td);
} else {
td.appendChild(document.createTextNode(value));
}
tr.appendChild(td); el.appendChild(tr)
el.appendChild(tr); return td
return td;
} }
function createIframe(opt, width, height) { function createIframe(opt, width, height) {
el = document.createElement("iframe"); el = document.createElement("iframe")
width = typeof width !== 'undefined' ? width : '100%'; width = typeof width !== 'undefined' ? width : '100%'
height = typeof height !== 'undefined' ? height : '350px'; height = typeof height !== 'undefined' ? height : '350px'
if (opt.src) { if (opt.src)
el.src = opt.src; el.src = opt.src
} else { else
el.src = opt; el.src = opt
}
if (opt.frameBorder) { if (opt.frameBorder)
el.frameBorder = opt.frameBorder; el.frameBorder = opt.frameBorder
} else { else
el.frameBorder = 1; el.frameBorder = 1
}
if (opt.width) { if (opt.width)
el.width = opt.width; el.width = opt.width
} else { else
el.width = width; el.width = width
}
if (opt.height) { if (opt.height)
el.height = opt.height; el.height = opt.height
} else { else
el.height = height; el.height = height
}
el.scrolling = "no"; el.scrolling = "no"
el.seamless = "seamless"; el.seamless = "seamless"
return el; return el
} }
function showStat(o, subst) { function showStat(o, subst) {
var content, caption; var content, caption
subst = typeof subst !== 'undefined' ? subst : {}; subst = typeof subst !== 'undefined' ? subst : {}
if (o.thumbnail) { if (o.thumbnail) {
content = document.createElement("img"); content = document.createElement("img")
content.src = listReplace(o.thumbnail, subst); content.src = listReplace(o.thumbnail, subst)
} }
if (o.caption) { if (o.caption) {
caption = listReplace(o.caption, subst); caption = listReplace(o.caption, subst)
if (!content) { if (!content)
content = document.createTextNode(caption); content = document.createTextNode(caption)
}
} }
if (o.iframe) { if (o.iframe) {
content = createIframe(o.iframe, o.width, o.height); content = createIframe(o.iframe, o.width, o.height)
if (o.iframe.src) { if (o.iframe.src)
content.src = listReplace(o.iframe.src, subst); content.src = listReplace(o.iframe.src, subst)
} else { else
content.src = listReplace(o.iframe, subst); content.src = listReplace(o.iframe, subst)
}
} }
var p = document.createElement("p"); var p = document.createElement("p")
if (o.href) { if (o.href) {
var link = document.createElement("a"); var link = document.createElement("a")
link.target = "_blank"; link.target = "_blank"
link.href = listReplace(o.href, subst); link.href = listReplace(o.href, subst)
link.appendChild(content); link.appendChild(content)
if (caption && o.thumbnail) { if (caption && o.thumbnail)
link.title = caption; link.title = caption
}
p.appendChild(link)
p.appendChild(link); } else
} else { p.appendChild(content)
p.appendChild(content);
} return p
return p;
} }

View file

@ -3,13 +3,14 @@
<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="roboto-slab-fontface.css"> <link rel="stylesheet" href="css/ionicons.min.css">
<link rel="stylesheet" href="roboto-fontface.css"> <link rel="stylesheet" href="css/roboto-slab/roboto-slab-fontface.css">
<link rel="stylesheet" href="style.css?__inline=true"> <link rel="stylesheet" href="css/roboto/roboto-fontface.css">
<script src="vendor/es6-shim/es6-shim.min.js?__inline=true"></script> <link rel="stylesheet" href="style.css">
<script src="vendor/es6-shim/es6-shim.min.js"></script>
<script src="app.js"></script> <script src="app.js"></script>
<script> <script>
console.log("Version: #revision#"); console.log("Version: #revision#")
</script> </script>
</head> </head>
<body> <body>

View file

@ -3,25 +3,15 @@
<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="node_modules/roboto-fontface/css/roboto-slab/roboto-slab-fontface.css">
<link rel="stylesheet" href="bower_components/roboto-fontface/roboto-fontface.css"> <link rel="stylesheet" href="node_modules/roboto-fontface/css/roboto/roboto-fontface.css">
<link rel="stylesheet" href="bower_components/leaflet/dist/leaflet.css"> <link rel="stylesheet" href="node_modules/leaflet/dist/leaflet.css">
<link rel="stylesheet" href="bower_components/Leaflet.label/dist/leaflet.label.css"> <link rel="stylesheet" href="node_modules/leaflet-label/dist/leaflet.label.css">
<link rel="stylesheet" href="assets/icons/hopglass-icons.css"> <link rel="stylesheet" href="node_modules/ionicons/css/ionicons.min.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="node_modules/es6-shim/es6-shim.min.js"></script>
<script src="bower_components/requirejs/require.js" data-main="app"></script> <script src="node_modules/requirejs/require.js" data-main="app"></script>
</head> </head>
<body> <body>
<div class="loader">
<p>
Lade<br/>
<span class="spinner"></span><br \>
Karte &amp; Knoten...
</p>
<noscript>
<strong>JavaScript required</strong>
</noscript>
</div>
</body> </body>
</html> </html>

View file

@ -1,38 +1,38 @@
define(function () { define(function () {
"use strict";
return function() { return function() {
this.render = function (d) { this.render = function (d) {
var s = "<h2>Über HopGlass</h2>"; var el = document.createElement("div")
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/hopglass/hopglass\">"
s += "https://github.com/plumpudding/hopglass</a>."; s += "https://github.com/hopglass/hopglass</a>."
d.innerHTML = s; el.innerHTML = s
}; }
}; }
}); })

View file

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

View file

@ -1,93 +1,80 @@
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) { targets = targets.filter( function (e) { return d !== e } )
return d !== e;
});
} }
function add(d) { function add(d) {
targets.push(d); targets.push(d)
if (filteredData !== undefined) { if (filteredData !== undefined)
d.setData(filteredData); d.setData(filteredData)
}
} }
function setData(d) { function setData(d) {
data = d; data = d
refresh(); refresh()
} }
function refresh() { function refresh() {
if (data === undefined) { if (data === undefined)
return; return
}
var filter = filters.reduce( function (a, f) { var filter = filters.reduce( function (a, f) {
return function (d) { return function (d) {
return a(d) && f.run(d); return a(d) && f.run(d)
}; }
}, function () { }, function () { return true })
return true;
});
filteredData = new NodeFilter(filter)(data); filteredData = new NodeFilter(filter)(data)
targets.forEach( function (t) { targets.forEach( function (t) {
t.setData(filteredData); t.setData(filteredData)
}); })
} }
function notifyObservers() { function notifyObservers() {
filterObservers.forEach( function (d) { filterObservers.forEach( function (d) {
d.filtersChanged(filters); d.filtersChanged(filters)
}); })
} }
function addFilter(d) { function addFilter(d) {
filters.push(d); filters.push(d)
notifyObservers(); notifyObservers()
d.setRefresh(refresh); d.setRefresh(refresh)
refresh(); refresh()
} }
function removeFilter(d) { function removeFilter(d) {
filters = filters.filter(function (e) { filters = filters.filter( function (e) { return d !== e } )
return d !== e; notifyObservers()
}); refresh()
notifyObservers();
refresh();
} }
function watchFilters(d) { function watchFilters(d) {
filterObservers.push(d); filterObservers.push(d)
d.filtersChanged(filters); d.filtersChanged(filters)
return function () { return function () {
filterObservers = filterObservers.filter(function (e) { filterObservers = filterObservers.filter( function (e) { return d !== e })
return d !== e; }
});
};
} }
return { return { add: add,
add: add,
remove: remove, remove: remove,
setData: setData, setData: setData,
addFilter: addFilter, addFilter: addFilter,
removeFilter: removeFilter, removeFilter: removeFilter,
watchFilters: watchFilters, watchFilters: watchFilters,
refresh: refresh refresh: refresh
}; }
}; }
}); })

View file

@ -1,43 +1,40 @@
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")
container.appendChild(li); var div = document.createElement("div")
d.render(li); container.appendChild(li)
li.appendChild(div)
d.render(div)
var button = document.createElement("button"); var button = document.createElement("button")
button.classList.add("ion-android-close"); button.textContent = ""
button.onclick = function () { button.onclick = function () {
distributor.removeFilter(d); distributor.removeFilter(d)
};
li.appendChild(button);
});
if (container.parentNode === div && filters.length === 0) {
div.removeChild(container);
} else if (filters.length > 0) {
div.appendChild(container);
} }
li.appendChild(button)
})
if (container.parentNode === div && filters.length === 0)
div.removeChild(container)
else if (filters.length > 0)
div.appendChild(container)
} }
return { return { render: render,
render: render,
filtersChanged: filtersChanged filtersChanged: filtersChanged
}; }
}; }
}); })

View file

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

View file

@ -1,38 +1,33 @@
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
}; }
}; }
}); })

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,58 +1,48 @@
define(["helper"], function (helper) { define(function () {
"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 helper.showStat(o, subst); return showStat(o, subst)
} }
return function (config, el, router, d) { return function (config, el, router, d) {
var unknown = !d.source.node; var unknown = !(d.source.node)
var h2 = document.createElement("h2"); var h2 = document.createElement("h2")
var a1; var a1 = document.createElement("a")
if (!unknown) { if (!unknown) {
a1 = document.createElement("a"); a1.href = "#"
a1.href = "#"; a1.onclick = router.node(d.source.node)
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 arrow = document.createElement("spam"); var attributes = document.createElement("table")
arrow.classList.add("ion-ios-arrow-thin-right"); attributes.classList.add("attributes")
h2.appendChild(arrow);
var a2 = document.createElement("a"); attributeEntry(attributes, "TQ", showTq(d))
a2.href = "#"; attributeEntry(attributes, "Entfernung", showDistance(d))
a2.onclick = router.node(d.target.node); attributeEntry(attributes, "Typ", d.type)
a2.textContent = d.target.node.nodeinfo.hostname; var hw1 = unknown ? null : dictGet(d.source.node.nodeinfo, ["hardware", "model"])
h2.appendChild(a2); var hw2 = dictGet(d.target.node.nodeinfo, ["hardware", "model"])
el.appendChild(h2); attributeEntry(attributes, "Hardware", (hw1 != null ? hw1 : "unbekannt") + " " + (hw2 != null ? hw2 : "unbekannt"))
el.appendChild(attributes)
var attributes = document.createElement("table"); if (config.linkInfos)
attributes.classList.add("attributes");
helper.attributeEntry(attributes, "TQ", helper.showTq(d));
helper.attributeEntry(attributes, "Entfernung", helper.showDistance(d));
helper.attributeEntry(attributes, "Typ", d.type);
var hw1 = unknown ? null : helper.dictGet(d.source.node.nodeinfo, ["hardware", "model"]);
var hw2 = helper.dictGet(d.target.node.nodeinfo, ["hardware", "model"]);
helper.attributeEntry(attributes, "Hardware", (hw1 != null ? hw1 : "unbekannt") + " " + (hw2 != null ? hw2 : "unbekannt"));
el.appendChild(attributes);
if (config.linkInfos) {
config.linkInfos.forEach( function (linkInfo) { config.linkInfos.forEach( function (linkInfo) {
var h4 = document.createElement("h4"); var h4 = document.createElement("h4")
h4.textContent = linkInfo.name; h4.textContent = linkInfo.name
el.appendChild(h4); el.appendChild(h4)
el.appendChild(showStatImg(linkInfo, d)); el.appendChild(showStatImg(linkInfo, d))
}); })
} }
}; })
});

View file

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

View file

@ -1,53 +1,51 @@
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", "ion-android-close"); closeButton.classList.add("close")
closeButton.onclick = router.reset; closeButton.onclick = router.reset
el.appendChild(closeButton); el.appendChild(closeButton)
} }
self.resetView = destroy; self.resetView = destroy
self.gotoNode = function (d) { self.gotoNode = function (d) {
create(); create()
Node(config, el, router, d); new Node(config, el, router, d)
}; }
self.gotoLink = function (d) { self.gotoLink = function (d) {
create(); create()
Link(config, el, router, d); new Link(config, el, router, d)
}; }
self.gotoLocation = function (d) { self.gotoLocation = function (d) {
create(); create()
Location(config, el, router, d); new Location(config, el, router, d)
}; }
return self; return self
}; }
}); })

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,13 +1,10 @@
define(["map/clientlayer", "map/labelslayer", define(["map/clientlayer", "map/labelslayer",
"d3", "leaflet", "moment", "locationmarker", "rbush", "helper", "d3", "leaflet", "moment", "locationmarker", "rbush",
"leaflet.label", "leaflet.providers", "moment.de"], "leaflet.label", "leaflet.providers"],
function (ClientLayer, LabelsLayer, d3, L, moment, LocationMarker, rbush, helper) { function (ClientLayer, LabelsLayer, d3, L, moment, LocationMarker, rbush) {
"use strict"; var options = { worldCopyJump: true,
var options = {
worldCopyJump: true,
zoomControl: false zoomControl: false
}; }
var AddLayerButton = L.Control.extend({ var AddLayerButton = L.Control.extend({
options: { options: {
@ -15,23 +12,24 @@ define(["map/clientlayer", "map/labelslayer",
}, },
initialize: function (f, options) { initialize: function (f, options) {
L.Util.setOptions(this, options); L.Util.setOptions(this, options)
this.f = f; this.f = f
}, },
onAdd: function () { onAdd: function () {
var button = L.DomUtil.create("button", "add-layer ion-android-add shadow"); var button = L.DomUtil.create("button", "add-layer")
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: {
@ -42,33 +40,35 @@ define(["map/clientlayer", "map/labelslayer",
button: undefined, button: undefined,
initialize: function (f, options) { initialize: function (f, options) {
L.Util.setOptions(this, options); L.Util.setOptions(this, options)
this.f = f; this.f = f
}, },
onAdd: function () { onAdd: function () {
var button = L.DomUtil.create("button", "locate-user ion-android-locate shadow"); var button = L.DomUtil.create("button", "locate-user")
L.DomEvent.disableClickPropagation(button); button.textContent = "\uF2E9"
L.DomEvent.addListener(button, "click", this.onClick, this);
this.button = button; L.DomEvent.disableClickPropagation(button)
L.DomEvent.addListener(button, "click", this.onClick, this)
return button; this.button = button
return button
}, },
update: function() { update: function() {
this.button.classList.toggle("active", this.active); this.button.classList.toggle("active", this.active)
}, },
set: function(v) { set: function(v) {
this.active = v; this.active = v
this.update(); this.update()
}, },
onClick: function () { onClick: function () {
this.f(!this.active); this.f(!this.active)
} }
}); })
var CoordsPickerButton = L.Control.extend({ var CoordsPickerButton = L.Control.extend({
options: { options: {
@ -79,546 +79,470 @@ 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 ion-pin shadow"); var button = L.DomUtil.create("button", "coord-picker")
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"
}); })
return graph.map(function (d) { var lines = graph.map( function (d) {
var opts = { var opts = { color: d.type === "Kabel" ? "#50B0F0" : linkScale(d.tq).hex(),
color: d.type === "Kabel" ? "#50B0F0" : linkScale(d.tq).hex(),
weight: 4, weight: 4,
opacity: 0.5, opacity: 0.5,
dashArray: "none" dashArray: "none"
}; }
var line = L.polyline(d.latlngs, opts); var line = L.polyline(d.latlngs, opts)
line.resetStyle = function () { line.resetStyle = function () {
line.setStyle(opts); line.setStyle(opts)
};
line.bindLabel(d.source.node.nodeinfo.hostname + " " + d.target.node.nodeinfo.hostname + "<br><strong>" + helper.showDistance(d) + " / " + helper.showTq(d) + "</strong>");
line.on("click", router.link(d));
dict[d.id] = line;
return line;
});
} }
var iconOnline = { line.bindLabel(d.source.node.nodeinfo.hostname + " " + d.target.node.nodeinfo.hostname + "<br><strong>" + showDistance(d) + " / " + showTq(d) + "</strong>")
color: "#1566A9", line.on("click", router.link(d))
fillColor: "#1566A9",
radius: 6, dict[d.id] = line
fillOpacity: 0.5,
opacity: 0.5, return line
weight: 2, })
className: "stroke-first"
}; return lines
var iconOffline = { }
color: "#D43E2A",
fillColor: "#D43E2A", var iconOnline = { color: "#1566A9", fillColor: "#1566A9", radius: 6, fillOpacity: 0.5, opacity: 0.5, weight: 2, className: "stroke-first" }
radius: 3, var iconOffline = { color: "#D43E2A", fillColor: "#D43E2A", radius: 3, fillOpacity: 0.5, opacity: 0.5, weight: 1, className: "stroke-first" }
fillOpacity: 0.5, var iconLost = { color: "#D43E2A", fillColor: "#D43E2A", radius: 6, fillOpacity: 0.8, opacity: 0.8, weight: 1, className: "stroke-first" }
opacity: 0.5, var iconAlert = { color: "#D43E2A", fillColor: "#D43E2A", radius: 6, fillOpacity: 0.8, opacity: 0.8, weight: 2, className: "stroke-first node-alert" }
weight: 1, var iconNew = { color: "#1566A9", fillColor: "#93E929", radius: 6, fillOpacity: 1.0, opacity: 0.5, weight: 2 }
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 = { savedView = {center: map.getCenter(),
center: map.getCenter(), zoom: map.getZoom()}
zoom: map.getZoom()
};
} }
function enableTracking() { function enableTracking() {
map.locate({ map.locate({watch: true,
watch: true,
enableHighAccuracy: true, enableHighAccuracy: true,
setView: true setView: true
}); })
locateUserButton.set(true); locateUserButton.set(true)
} }
function disableTracking() { function disableTracking() {
map.stopLocate(); map.stopLocate()
locationError(); locationError()
locateUserButton.set(false); locateUserButton.set(false)
} }
function enableCoords() { function enableCoords() {
map.getContainer().classList.add("pick-coordinates"); map.getContainer().classList.add("pick-coordinates")
map.on("click", showCoordinates); map.on("click", showCoordinates)
showCoordsPickerButton.set(true); showCoordsPickerButton.set(true)
} }
function disableCoords() { function disableCoords() {
map.getContainer().classList.remove("pick-coordinates"); map.getContainer().classList.remove("pick-coordinates")
map.off("click", showCoordinates); map.off("click", showCoordinates)
showCoordsPickerButton.set(false); showCoordsPickerButton.set(false)
} }
function showCoordinates(e) { function showCoordinates(e) {
router.gotoLocation(e.latlng); router.gotoLocation(e.latlng)
// window.prompt("Koordinaten (Lat, Lng)", e.latlng.lat.toFixed(9) + ", " + e.latlng.lng.toFixed(9)) // window.prompt("Koordinaten (Lat, Lng)", e.latlng.lat.toFixed(9) + ", " + e.latlng.lng.toFixed(9))
disableCoords(); disableCoords()
} }
function locationFound(e) { function locationFound(e) {
if (!userLocation) { if (!userLocation)
userLocation = new LocationMarker(e.latlng).addTo(map); userLocation = new LocationMarker(e.latlng).addTo(map)
}
userLocation.setLatLng(e.latlng); userLocation.setLatLng(e.latlng)
userLocation.setAccuracy(e.accuracy); userLocation.setAccuracy(e.accuracy)
} }
function locationError() { function locationError() {
if (userLocation) { if (userLocation) {
map.removeLayer(userLocation); map.removeLayer(userLocation)
userLocation = null; userLocation = null
} }
} }
function addLayer(layerName) { function addLayer(layerName) {
if (layerName in baseLayers) { if (layerName in baseLayers)
return; return
}
if (layerName in customLayers) { if (layerName in customLayers)
return; return
}
try { try {
var layer = L.tileLayer.provider(layerName); var layer = L.tileLayer.provider(layerName)
layerControl.addBaseLayer(layer, layerName); layerControl.addBaseLayer(layer, layerName)
customLayers[layerName] = layer; customLayers[layerName] = layer
if (helper.localStorageTest()) { if (localStorageTest())
localStorage.setItem("map/customLayers", JSON.stringify(Object.keys(customLayers))); localStorage.setItem("map/customLayers", JSON.stringify(Object.keys(customLayers)))
}
} catch (e) { } catch (e) {
console.error(e); return
} }
} }
function contextMenuGotoLocation(e) { function contextMenuGotoLocation(e) {
router.gotoLocation(e.latlng); router.gotoLocation(e.latlng)
} }
var el = document.createElement("div"); var el = document.createElement("div")
el.classList.add("map"); el.classList.add("map")
map = L.map(el, options); map = L.map(el, options)
var layers = config.mapLayers.map( function (d) { var layers = config.mapLayers.map( function (d) {
return { return {
"name": d.name, "name": d.name,
"layer": "url" in d ? L.tileLayer(d.url, d.config) : L.tileLayer.provider(d.name) "layer": "url" in d ? "layers" in d.config ? L.tileLayer.wms(d.url, d.config) : 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 (helper.localStorageTest()) { if (localStorageTest()) {
var d = JSON.parse(localStorage.getItem("map/customLayers")); var d = JSON.parse(localStorage.getItem("map/customLayers"))
if (d)
d.forEach(addLayer)
d = JSON.parse(localStorage.getItem("map/selectedLayer"))
d = d && d.name in baseLayers ? baseLayers[d.name] : d && d.name in customLayers ? customLayers[d.name] : false
if (d) { if (d) {
d.forEach(addLayer); map.removeLayer(layers[0].layer)
} 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) { if (map.getZoom() > map.options.maxZoom) map.setZoom(map.options.maxZoom)
map.setZoom(map.options.maxZoom); if (localStorageTest())
} 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: 4, opacity: 1, dashArray: "5, 10"}); m.setStyle({ weight: 7, opacity: 1, dashArray: "10, 10" })
}
}
} }
if (!nopanzoom) { if (!nopanzoom)
if (m) { if (m)
goto(m); goto(m)
} else if (savedView) { else if (savedView)
map.setView(savedView.center, savedView.zoom); map.setView(savedView.center, savedView.zoom)
} else { else
resetZoom(); resetZoom()
}
}
} }
function calcBarycenter(nodes) { function calcBarycenter(nodes) {
nodes = nodes.map(function (d) { nodes = nodes.map(function (d) { return d.nodeinfo.location })
return d.nodeinfo.location;
});
if (nodes.length === 0) { if (nodes.length === 0)
return undefined; return undefined
}
var lats = nodes.map(function (d) { var lats = nodes.map(function (d) { return d.latitude })
return d.latitude; var lngs = nodes.map(function (d) { return d.longitude })
});
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(helper.hasLocation)); barycenter = calcBarycenter(data.nodes.all.filter(has_location))
} else { else
barycenter = L.circle(L.latLng(new L.LatLng(config.fixedCenter.lat, config.fixedCenter.lng)), config.fixedCenter.radius * 1000); barycenter = L.circle(L.latLng(new L.LatLng(config.fixedCenter.lat, config.fixedCenter.lng)), config.fixedCenter.radius * 1000)
}
var nodesOnline = helper.subtract(data.nodes.all.filter(helper.online), data.nodes.new); var nodesOnline = subtract(data.nodes.all.filter(online), data.nodes.new)
var nodesOffline = helper.subtract(data.nodes.all.filter(helper.offline), data.nodes.lost); var nodesOffline = subtract(data.nodes.all.filter(offline), data.nodes.lost)
var markersOnline = nodesOnline.filter(helper.hasLocation) var markersOnline = nodesOnline.filter(has_location)
.map(mkMarker(nodeDict, function () { .map(mkMarker(nodeDict, function () { return iconOnline }, router))
return iconOnline;
}, router));
var markersOffline = nodesOffline.filter(helper.hasLocation) var markersOffline = nodesOffline.filter(has_location)
.map(mkMarker(nodeDict, function () { .map(mkMarker(nodeDict, function () { return iconOffline }, router))
return iconOffline;
}, router));
var markersNew = data.nodes.new.filter(helper.hasLocation) var markersNew = data.nodes.new.filter(has_location)
.map(mkMarker(nodeDict, function () { .map(mkMarker(nodeDict, function () { return iconNew }, router))
return iconNew;
}, router));
var markersLost = data.nodes.lost.filter(helper.hasLocation) var markersLost = data.nodes.lost.filter(has_location)
.map(mkMarker(nodeDict, function (d) { .map(mkMarker(nodeDict, function (d) {
if (d.lastseen.isAfter(moment(data.now).subtract(3, "days"))) { if (d.lastseen.isAfter(moment(data.now).subtract(3, "days")))
return iconAlert; return iconAlert
return iconLost
}, router))
groupOffline = L.featureGroup(markersOffline).addTo(map)
groupOnline = L.featureGroup(markersOnline).addTo(map)
groupLost = L.featureGroup(markersLost).addTo(map)
groupNew = L.featureGroup(markersNew).addTo(map)
var rtreeOnlineAll = rbush(9)
rtreeOnlineAll.load(data.nodes.all.filter(online).filter(has_location).map(mapRTree))
clientLayer.setData(rtreeOnlineAll)
labelsLayer.setData({online: nodesOnline.filter(has_location),
offline: nodesOffline.filter(has_location),
new: data.nodes.new.filter(has_location),
lost: data.nodes.lost.filter(has_location)
})
updateView(true)
} }
if (d.lastseen.isAfter(moment(data.now).subtract(14, "days"))) {
return iconLost;
}
}, router));
groupOffline = L.featureGroup(markersOffline).addTo(map);
groupLost = L.featureGroup(markersLost).addTo(map);
groupOnline = L.featureGroup(markersOnline).addTo(map);
groupNew = L.featureGroup(markersNew).addTo(map);
var rtreeOnlineAll = rbush(9);
rtreeOnlineAll.load(data.nodes.all.filter(helper.online).filter(helper.hasLocation).map(mapRTree));
clientLayer.setData(rtreeOnlineAll);
labelsLayer.setData({
online: nodesOnline.filter(helper.hasLocation),
offline: nodesOffline.filter(helper.hasLocation),
new: data.nodes.new.filter(helper.hasLocation),
lost: data.nodes.lost.filter(helper.hasLocation)
});
updateView(true);
};
self.resetView = function () { self.resetView = function () {
disableTracking(); disableTracking()
highlight = undefined; highlight = undefined
updateView(); updateView()
}; }
self.gotoNode = function (d) { self.gotoNode = function (d) {
disableTracking(); disableTracking()
highlight = {type: "node", o: d}; highlight = {type: "node", o: d}
updateView(); updateView()
}; }
self.gotoLink = function (d) { self.gotoLink = function (d) {
disableTracking(); disableTracking()
highlight = {type: "link", o: d}; highlight = {type: "link", o: d}
updateView(); updateView()
}; }
self.gotoLocation = function () { self.gotoLocation = function () {
//ignore //ignore
}; }
self.destroy = function () { self.destroy = function () {
clearButtons(); clearButtons()
map.remove(); map.remove()
if (el.parentNode) { if (el.parentNode)
el.parentNode.removeChild(el); el.parentNode.removeChild(el)
} }
};
self.render = function (d) { self.render = function (d) {
d.appendChild(el); d.appendChild(el)
map.invalidateSize(); map.invalidateSize()
}; }
return self; return self
}; }
}); })

View file

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

View file

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

View file

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

View file

@ -1,111 +1,97 @@
define(["sorttable", "virtual-dom", "helper"], function (SortTable, V, helper) { define(["sorttable", "virtual-dom", "numeral"], function (SortTable, V, numeral) {
"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 = [{
name: ''
}, {
name: "Knoten",
sort: function (a, b) { sort: function (a, b) {
var aname = typeof a.nodeinfo.hostname === "string" ? a.nodeinfo.hostname : a.nodeinfo.node_id; var aname = typeof a.nodeinfo.hostname === "string" ? a.nodeinfo.hostname : a.nodeinfo.node_id
var bname = typeof b.nodeinfo.hostname === "string" ? b.nodeinfo.hostname : b.nodeinfo.node_id; var bname = typeof b.nodeinfo.hostname === "string" ? b.nodeinfo.hostname : b.nodeinfo.node_id
if (typeof aname === "string" && typeof bname === "string") { if (typeof aname === "string" && typeof bname === "string")
return aname.localeCompare(bname); return aname.localeCompare(bname)
} return typeof aname === "string" ? 1 : typeof bname === "string" ? -1 : 0
return typeof aname === "string" ? 1 : typeof bname === "string" ? -1 : 0;
}, },
reverse: false reverse: false
}, },
{ { name: "Uptime",
name: "Uptime",
sort: function (a, b) { sort: function (a, b) {
return a.uptime - b.uptime; return a.uptime - b.uptime
}, },
reverse: true reverse: true
}, },
{ { name: "#Links",
name: "#Links",
sort: function (a, b) { sort: function (a, b) {
return a.meshlinks - b.meshlinks; return a.meshlinks - b.meshlinks
}, },
reverse: true reverse: true
}, },
{ { name: "Clients",
name: "Clients",
sort: function (a, b) { sort: function (a, b) {
return ("clients" in a.statistics ? a.statistics.clients : -1) - return ("clients" in a.statistics ? a.statistics.clients : -1) -
("clients" in b.statistics ? b.statistics.clients : -1); ("clients" in b.statistics ? b.statistics.clients : -1)
}, },
reverse: true reverse: true
}]; }]
return function(router) { return function(router) {
function renderRow(d) { function renderRow(d) {
var td0Content = []; var td1Content = []
var td1Content = []; var aClass = ["hostname", d.flags.online ? "online" : "offline"]
var aClass = ["hostname", d.flags.online ? "online" : "offline"];
td1Content.push(V.h("a", { td1Content.push(V.h("a", { className: aClass.join(" "),
className: aClass.join(" "),
onclick: router.node(d), onclick: router.node(d),
href: "#!n:" + d.nodeinfo.node_id href: "#!n:" + d.nodeinfo.node_id
}, d.nodeinfo.hostname)); }, d.nodeinfo.hostname))
if (helper.hasLocation(d)) { if (has_location(d))
td0Content.push(V.h("span", {className: "icon ion-location"})); td1Content.push(V.h("span", {className: "icon ion-location"}))
var td1 = V.h("td", td1Content)
var td2 = V.h("td", showUptime(d.uptime))
var td3 = V.h("td", d.meshlinks.toString())
var td4 = V.h("td", numeral("clients" in d.statistics ? d.statistics.clients : "").format("0,0"))
return V.h("tr", [td1, td2, td3, td4])
} }
var td0 = V.h("td", td0Content); var table = new SortTable(headings, 0, renderRow)
var td1 = V.h("td", td1Content);
var td2 = V.h("td", showUptime(d.uptime));
var td3 = V.h("td", d.meshlinks.toString());
var td4 = V.h("td", ("clients" in d.statistics ? d.statistics.clients : 0).toFixed(0));
return V.h("tr", [td0, td1, td2, td3, td4]);
}
var table = new SortTable(headings, 1, renderRow);
table.el.classList.add('node-list');
this.render = function (d) { this.render = function (d) {
var h2 = document.createElement("h2"); var el = document.createElement("div")
h2.textContent = "Alle Knoten"; d.appendChild(el)
d.appendChild(h2);
d.appendChild(table.el); var h2 = document.createElement("h2")
}; 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)
}; }
}; }
}); })

View file

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

View file

@ -1,232 +1,214 @@
define(["helper"], function (helper) { define(function () {
"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 (currentObject) {
if ("node" in currentObject) { if ("node" in currentObject)
e.push("n:" + encodeURIComponent(currentObject.node.nodeinfo.node_id)); e.push("n:" + encodeURIComponent(currentObject.node.nodeinfo.node_id))
if ("link" in currentObject)
e.push("l:" + encodeURIComponent(currentObject.link.id))
} }
if ("link" in currentObject) { var s = "#!" + e.join(";")
e.push("l:" + encodeURIComponent(currentObject.link.id));
}
}
var s = "#!" + e.join(";"); window.history.pushState(s, undefined, s)
window.history.pushState(s, undefined, s);
} }
function resetView(push) { function resetView(push) {
push = helper.trueDefault(push); push = trueDefault(push)
targets.forEach( function (t) { targets.forEach( function (t) {
t.resetView(); t.resetView()
}); })
if (push) { if (push) {
currentObject = undefined; currentObject = undefined
saveState(); saveState()
} }
} }
function gotoNode(d) { function gotoNode(d) {
if (!d) { if (!d)
return false; return false
}
targets.forEach( function (t) { targets.forEach( function (t) {
t.gotoNode(d); t.gotoNode(d)
}); })
return true; return true
} }
function gotoLink(d) { function gotoLink(d) {
if (!d) { if (!d)
return false; return false
}
targets.forEach( function (t) { targets.forEach( function (t) {
t.gotoLink(d); t.gotoLink(d)
}); })
return true; return true
} }
function gotoLocation(d) { function gotoLocation(d) {
if (!d) { if (!d)
return false; return false
}
targets.forEach( function (t) { targets.forEach( function (t) {
if (!t.gotoLocation) { if(!t.gotoLocation)console.warn("has no gotoLocation", t)
console.warn("has no gotoLocation", t); t.gotoLocation(d)
} })
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) { if (!running)
return; return
}
saveState(); saveState()
if (!currentObject) { if (!currentObject) {
resetView(false); resetView(false)
return; return
} }
if ("node" in currentObject) { if ("node" in currentObject)
gotoNode(currentObject.node); gotoNode(currentObject.node)
}
if ("link" in currentObject) { if ("link" in currentObject)
gotoLink(currentObject.link); gotoLink(currentObject.link)
} }
} }
};
self.node = function (d) { self.node = function (d) {
return function () { return function () {
if (gotoNode(d)) { if (gotoNode(d)) {
currentObject = {node: d}; currentObject = { node: d }
saveState(); saveState()
} }
return false; return false
}; }
}; }
self.link = function (d) { self.link = function (d) {
return function () { return function () {
if (gotoLink(d)) { if (gotoLink(d)) {
currentObject = {link: d}; currentObject = { link: d }
saveState(); saveState()
} }
return false; return false
}; }
}; }
self.gotoLocation = gotoLocation; self.gotoLocation = gotoLocation
self.reset = function () { self.reset = function () {
resetView(); resetView()
}; }
self.addTarget = function (d) { self.addTarget = function (d) {
targets.push(d); targets.push(d)
}; }
self.removeTarget = function (d) { self.removeTarget = function (d) {
targets = targets.filter( function (e) { targets = targets.filter( function (e) {
return d !== e; return d !== e
}); })
}; }
self.addView = function (k, d) { self.addView = function (k, d) {
views[k] = d; views[k] = d
}; }
self.setData = function (data) { self.setData = function (data) {
objects.nodes = {}; objects.nodes = {}
objects.links = {}; objects.links = {}
data.nodes.all.forEach( function (d) { data.nodes.all.forEach( function (d) {
objects.nodes[d.nodeinfo.node_id] = d; objects.nodes[d.nodeinfo.node_id] = d
}); })
data.graph.links.forEach( function (d) { data.graph.links.forEach( function (d) {
objects.links[d.id] = d; objects.links[d.id] = d
}); })
}; }
return self; return self
}; }
}); })

View file

@ -1,52 +1,49 @@
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", "shadow"); button.classList.add("sidebarhandle")
button.onclick = function () { button.onclick = function () {
sidebar.classList.toggle("hidden"); sidebar.classList.toggle("hidden")
};
var container = document.createElement("div");
container.classList.add("container");
sidebar.appendChild(container);
self.getWidth = function () {
if (sidebar.classList.contains("hidden")) {
return 0;
} }
var small = window.matchMedia("(max-width: 630pt)"); var container = document.createElement("div")
return small.matches ? 0 : sidebar.offsetWidth; container.classList.add("container")
}; sidebar.appendChild(container)
self.getWidth = function () {
if (sidebar.classList.contains("hidden"))
return 0
var small = window.matchMedia("(max-width: 630pt)")
return small.matches ? 0 : sidebar.offsetWidth
}
self.add = function (d) { self.add = function (d) {
d.render(container); d.render(container)
}; }
self.ensureVisible = function () { self.ensureVisible = function () {
sidebar.classList.remove("hidden"); sidebar.classList.remove("hidden")
}; }
self.hide = function () { self.hide = function () {
container.classList.add("hidden"); container.classList.add("hidden")
}; }
self.reveal = function () { self.reveal = function () {
container.classList.remove("hidden"); container.classList.remove("hidden")
}; }
self.container = sidebar; self.container = sidebar
return self; return self
}; }
}); })

View file

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

View file

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

View file

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

View file

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

View file

@ -1,243 +0,0 @@
"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;
}
});

60
lib/vercomp.js Normal file
View file

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

View file

@ -1,34 +1,41 @@
{ {
"name": "hopglass", "name": "hopglass",
"license": "AGPL-3.0", "version": "1.0.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.6", "autoprefixer": "^6.3.3",
"eslint": "^2.11.0", "dart-sass": "^1.16.1",
"eslint-config-defaults": "^9.0.0", "grunt": "^1.0.3",
"grunt": "^1.0.1", "grunt-check-dependencies": "^0.6.0",
"grunt-bower-install-simple": "^1.2.3", "grunt-contrib-clean": "^0.6.0",
"grunt-check-dependencies": "^0.12.0", "grunt-contrib-connect": "^0.8.0",
"grunt-contrib-clean": "^1.0.0", "grunt-contrib-copy": "^0.5.0",
"grunt-contrib-connect": "^1.0.2", "grunt-contrib-cssmin": "^0.12.2",
"grunt-contrib-copy": "^1.0.0", "grunt-contrib-requirejs": "^0.4.4",
"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-git-describe": "^2.3.2",
"grunt-inline": "^0.3.6", "grunt-postcss": "^0.7.2",
"grunt-postcss": "^0.8.0", "grunt-sass": "^3.0.2"
"grunt-sass": "^1.2.0", },
"grunt-sass-lint": "^0.2.0" "dependencies": {
"almond": "^0.3.3",
"chroma-js": "^0.7.8",
"d3": "^3.5.17",
"ionicons": "^2.0.1",
"leaflet": "^0.7.7",
"leaflet-label": "^0.2.1-0",
"leaflet-providers": "^1.5.0",
"moment": "^2.23.0",
"numeraljs": "^1.5.6",
"rbush": "^1.4.3",
"requirejs": "^2.3.2",
"roboto-fontface": "^0.10.0",
"tablesort": "3.0.2",
"virtual-dom": "^2.1.1"
}, },
"eslintConfig": { "eslintConfig": {
"env": { "env": {
@ -36,6 +43,24 @@
"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
} }
} }
} }

53
scss/_filters.scss Normal file
View file

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

View file

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

View file

@ -1 +1 @@
../bower_components/Leaflet.label/dist/leaflet.label.css ../node_modules/leaflet-label/dist/leaflet.label.css

View file

@ -1 +1 @@
../bower_components/leaflet/dist/leaflet.css ../node_modules/leaflet/dist/leaflet.css

23
scss/_legend.scss Normal file
View file

@ -0,0 +1,23 @@
.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;
}

59
scss/_map.scss Normal file
View file

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

View file

@ -1,5 +1,7 @@
// Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) /**
// http://cssreset.com * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
* http://cssreset.com
*/
html, body, div, span, applet, object, iframe, 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,
@ -10,17 +12,23 @@ 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, figure, figcaption, footer, header, hgroup,
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;
} }
@ -33,6 +41,12 @@ 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;

15
scss/_shadow.scss Normal file
View file

@ -0,0 +1,15 @@
/* 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);
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +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($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);
}
}

View file

@ -1,56 +0,0 @@
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;
}

View file

@ -1,47 +0,0 @@
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;
}
}

View file

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

View file

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

View file

@ -1,34 +0,0 @@
.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;
}
}

View file

@ -1,32 +0,0 @@
.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;
}

View file

@ -1,41 +0,0 @@
.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%;
}

View file

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

View file

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

View file

@ -1,127 +0,0 @@
.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;
}

View file

@ -1,76 +0,0 @@
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;
}
}

View file

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

View file

@ -1,22 +0,0 @@
$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;

View file

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

1
style.css Symbolic link
View file

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

View file

@ -1,10 +1,13 @@
module.exports = function(grunt) { module.exports = function(grunt) {
"use strict";
grunt.config.merge({ grunt.config.merge({
bowerdir: "bower_components", nodedir: "node_modules",
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/",
@ -19,42 +22,39 @@ module.exports = function (grunt) {
src: ["es6-shim/es6-shim.min.js", src: ["es6-shim/es6-shim.min.js",
"es6-shim/es6-shim.map"], "es6-shim/es6-shim.map"],
expand: true, expand: true,
cwd: "bower_components/", cwd: "node_modules/",
dest: "build/vendor/" dest: "build/vendor/"
}, },
robotoSlab: {
src: ["fonts/*",
"roboto-slab-fontface.css"
],
expand: true,
dest: "build/",
cwd: "bower_components/roboto-slab-fontface"
},
roboto: { roboto: {
src: ["fonts/*", src: [ "fonts/roboto/*",
"roboto-fontface.css" "fonts/roboto-slab/*",
"css/roboto/roboto-fontface.css",
"css/roboto-slab/roboto-slab-fontface.css"
], ],
expand: true, expand: true,
dest: "build/", dest: "build/",
cwd: "bower_components/roboto-fontface" cwd: "node_modules/roboto-fontface/"
}, },
ionicons: { ionicons: {
src: ["fonts/*"], src: [ "fonts/*",
"css/ionicons.min.css"
],
expand: true, expand: true,
dest: "build/", dest: "build/",
cwd: "assets/icons/" cwd: "node_modules/ionicons/"
}, },
leafletImages: { leafletImages: {
src: [ "images/*" ], src: [ "images/*" ],
expand: true, expand: true,
dest: "build/", dest: "build/",
cwd: "bower_components/leaflet/dist/" cwd: "node_modules/leaflet/dist/"
} }
}, },
sass: { sass: {
options: { options: {
sourceMap: true, sourceMap: true,
outputStyle: "compressed" outputStyle: "compressed",
implementation: require("dart-sass")
}, },
dist: { dist: {
files: { files: {
@ -75,58 +75,33 @@ module.exports = function (grunt) {
src: "build/style.css" src: "build/style.css"
} }
}, },
inline: { cssmin: {
dist: { target: {
options: { files: {
cssmin: true, "build/style.css": [ "node_modules/leaflet/dist/leaflet.css",
uglify: true "node_modules/leaflet-label/dist/leaflet.label.css",
}, "style.css"
src: "build/index.html", ]
dest: "build/index.html"
}
},
"bower-install-simple": {
options: {
directory: "<%=bowerdir%>",
color: true,
interactive: false,
production: true
},
"prod": {
options: {
production: true
} }
} }
}, },
requirejs: { requirejs: {
default: { compile: {
options: { options: {
baseUrl: "lib", baseUrl: "lib",
name: "../bower_components/almond/almond", name: "../node_modules/almond/almond",
mainConfigFile: "app.js", mainConfigFile: "app.js",
include: "../app", include: "../app",
out: "build/app.js", wrap: true,
build: true optimize: "uglify",
} 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-contrib-copy")
grunt.loadNpmTasks("grunt-contrib-copy"); grunt.loadNpmTasks("grunt-contrib-requirejs")
grunt.loadNpmTasks("grunt-contrib-requirejs"); grunt.loadNpmTasks("grunt-sass")
grunt.loadNpmTasks("grunt-sass"); grunt.loadNpmTasks("grunt-postcss")
grunt.loadNpmTasks("grunt-postcss"); }
grunt.loadNpmTasks("grunt-inline");
};

View file

@ -1,11 +1,9 @@
module.exports = function (grunt) { module.exports = function (grunt) {
"use strict";
grunt.config.merge({ grunt.config.merge({
clean: { clean: {
build: ["build/**/*", "node_modules/grunt-newer/.cache"] build: ["build/**/*", "node_modules/grunt-newer/.cache"]
} }
}); })
grunt.loadNpmTasks("grunt-contrib-clean"); grunt.loadNpmTasks("grunt-contrib-clean")
}; }

View file

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

View file

@ -1,25 +1,24 @@
module.exports = function (grunt) { module.exports = function (grunt) {
"use strict";
grunt.config.merge({ grunt.config.merge({
checkDependencies: { checkDependencies: {
options: { options: {
install: true install: true
}, },
bower: {
options: {
packageManager: "bower"
}
},
npm: {} npm: {}
}, },
sasslint: {
options: {
configFile: '.sass-lint.yml'
},
target: ['scss/main.scss', 'scss/*/*.scss']
},
eslint: { eslint: {
options: {
rules: {
"semi": [2, "never"],
"curly": [2, "multi"],
"strict": [2, "never"],
"no-multi-spaces": 0,
"no-new": 0,
"no-shadow": 0,
"no-use-before-define": [1, "nofunc"],
"no-underscore-dangle": 0
}
},
sources: { sources: {
src: ["app.js", "!Gruntfile.js", "lib/**/*.js"] src: ["app.js", "!Gruntfile.js", "lib/**/*.js"]
}, },
@ -27,9 +26,8 @@ module.exports = function (grunt) {
src: ["Gruntfile.js", "tasks/*.js"] src: ["Gruntfile.js", "tasks/*.js"]
} }
} }
}); })
grunt.loadNpmTasks("grunt-check-dependencies"); grunt.loadNpmTasks("grunt-check-dependencies")
grunt.loadNpmTasks('grunt-sass-lint'); grunt.loadNpmTasks("grunt-eslint")
grunt.loadNpmTasks("grunt-eslint"); }
};

3451
yarn.lock Normal file

File diff suppressed because it is too large Load diff