Compare commits

..

No commits in common. "master" and "pr-infobox-node" have entirely different histories.

46 changed files with 532 additions and 4597 deletions

View file

@ -1,16 +0,0 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
charset = utf-8
# Get rid of whitespace to avoid diffs with a bunch of EOL changes
trim_trailing_whitespace = true
[*]
end_of_line = lf
insert_final_newline = true
[*.{js,html,scss,json}]
indent_size = 2
indent_style = space

View file

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

2
Gemfile Normal file
View file

@ -0,0 +1,2 @@
source 'https://rubygems.org'
gem "sass"

13
Gemfile.lock Normal file
View file

@ -0,0 +1,13 @@
GEM
remote: https://rubygems.org/
specs:
sass (3.4.16)
PLATFORMS
ruby
DEPENDENCIES
sass
BUNDLED WITH
1.10.6

View file

@ -17,7 +17,7 @@ module.exports = function (grunt) {
grunt.loadTasks("tasks") grunt.loadTasks("tasks")
grunt.registerTask("default", ["lint", "saveRevision", "copy", "sass", "postcss", "requirejs"]) grunt.registerTask("default", ["bower-install-simple", "lint", "saveRevision", "copy", "sass", "requirejs"])
grunt.registerTask("lint", ["eslint"]) grunt.registerTask("lint", ["eslint"])
grunt.registerTask("dev", ["default", "connect:server", "watch"]) grunt.registerTask("dev", ["default", "connect:server", "watch"])
} }

View file

@ -1,8 +1,8 @@
[![Build Status](https://travis-ci.org/hopglass/hopglass.svg?branch=master)](https://travis-ci.org/hopglass/hopglass) [![Build Status](https://travis-ci.org/plumpudding/hopglass.svg?branch=master)](https://travis-ci.org/plumpudding/hopglass)
# HopGlass # HopGlass
HopGlass is a frontend for the [HopGlass Server](https://github.com/hopglass/hopglass-server). HopGlass is a frontend for the [HopGlass Server](https://github.com/plumpudding/hopglass-server).
# Screenshots # Screenshots
@ -14,32 +14,31 @@ HopGlass is a frontend for the [HopGlass Server](https://github.com/hopglass/hop
# Dependencies # Dependencies
- NodeJS - npm
- yarn (recommended) or npm - bower
- grunt-cli
- Sass (>= 3.2)
# Installing dependencies # Installing dependencies
Install npm package-manager. On Debian-like systems run: Install npm and Sass with your package-manager. On Debian-like systems run:
sudo apt-get install nodejs sudo apt-get install npm ruby-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. or if you have bundler you can install ruby-sass simply via `bundle install`
On Mac you can install nodejs and yarn via brew: On Mac you have to install only npm via brew and sass
/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
brew install yarn sudo gem install sass
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/hopglass/hopglass git clone https://github.com/plumpudding/hopglass
cd hopglass cd hopglass
yarn install # or `npm install` npm install
npm install grunt-cli
# Building # Building
@ -55,7 +54,7 @@ Copy `config.json.example` to `build/config.json` and change it to match your co
## dataPath (string/array) ## dataPath (string/array)
`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. `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.
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.
@ -85,25 +84,9 @@ property and optionally `url` and `config` properties. If no `url` is supplied
`name` is assumed to name a `name` is assumed to name a
[Leaflet-provider](http://leaflet-extras.github.io/leaflet-providers/preview/). [Leaflet-provider](http://leaflet-extras.github.io/leaflet-providers/preview/).
## fixedCenter (array, optional)
This option allows to fix the map at one specific coordinate depending on following case-sensitive parameters:
- `lat` latitude of the center point
- `lng` longitude of the center point
- `radius` visible radius around the center in km
Examples for `fixedCenter`:
"fixedCenter": {
"lat": 50.80,
"lng": 12.07,
"radius": 30
}
## nodeInfos (array, optional) ## nodeInfos (array, optional)
This option allows to show node statistics depending on following case-sensitive parameters: This option allows to show client statistics depending on following case-sensitive parameters:
- `name` caption of statistics segment in infobox - `name` caption of statistics segment in infobox
- `href` absolute or relative URL to statistics image - `href` absolute or relative URL to statistics image
@ -120,12 +103,12 @@ Examples for `nodeInfos`:
{ "name": "Clientstatistik", { "name": "Clientstatistik",
"href": "stats/dashboard/db/node-byid?var-nodeid={NODE_ID}", "href": "stats/dashboard/db/node-byid?var-nodeid={NODE_ID}",
"thumbnail": "stats/render/dashboard-solo/db/node-byid?panelId=1&fullscreen&theme=light&width=600&height=300&var-nodeid={NODE_ID}", "thumbnail": "stats/render/dashboard-solo/db/node-byid?panelId=1&fullscreen&theme=light&width=600&height=300&var-nodeid={NODE_ID}"
"caption": "Knoten {NODE_ID}" "caption": "Knoten {NODE_ID}"
}, },
{ "name": "Uptime", { "name": "Uptime",
"href": "stats/dashboard/db/node-byid?var-nodeid={NODE_ID}", "href": "stats/dashboard/db/node-byid?var-nodeid={NODE_ID}",
"thumbnail": "stats/render/dashboard-solo/db/node-byid?panelId=2&fullscreen&theme=light&width=600&height=300&var-nodeid={NODE_ID}", "thumbnail": "stats/render/dashboard-solo/db/node-byid?panelId=2&fullscreen&theme=light&width=600&height=300&var-nodeid={NODE_ID}"
"caption": "Knoten {NODE_ID}" "caption": "Knoten {NODE_ID}"
} }
] ]
@ -154,25 +137,6 @@ Examples for `globalInfos` using Grafana server rendering:
} }
] ]
## linkInfos (array, optional)
This option allows to show link statistics depending on the following case-sensitive parameters:
- `name` caption of statistics segment in infobox
- `href` absolute or relative URL to statistics image
- `thumbnail` absolute or relative URL to thumbnail image,
can be the same like `href`
- `caption` is shown, if `thumbnail` is not present (no thumbnail in infobox)
To insert the source or target node-id in either `href`, `thumbnail` or `caption`
you can use the case-sensitive template strings `{SOURCE}` and `{TARGET}`.
"linkInfos": [
{ "href": "stats/dashboard/db/links?var-source={SOURCE}&var-target={TARGET}",
"thumbnail": "stats/render/dashboard-solo/db/links?panelId=1&fullscreen&theme=light&width=800&height=600&var-source={SOURCE}&var-target={TARGET}"
}
]
## siteNames (array, optional) ## siteNames (array, optional)
In this array name definitions for site statistics and node info can be saved. This requires one object for each site code. This object must contain: In this array name definitions for site statistics and node info can be saved. This requires one object for each site code. This object must contain:

26
app.js
View file

@ -1,20 +1,20 @@
require.config({ require.config({
baseUrl: "lib", baseUrl: "lib",
paths: { paths: {
"leaflet": "../node_modules/leaflet/dist/leaflet", "leaflet": "../bower_components/leaflet/dist/leaflet",
"leaflet.label": "../node_modules/leaflet-label/dist/leaflet.label", "leaflet.label": "../bower_components/Leaflet.label/dist/leaflet.label",
"leaflet.providers": "../node_modules/leaflet-providers/leaflet-providers", "leaflet.providers": "../bower_components/leaflet-providers/leaflet-providers",
"chroma-js": "../node_modules/chroma-js/chroma.min", "chroma-js": "../bower_components/chroma-js/chroma.min",
"moment": "../node_modules/moment/min/moment-with-locales.min", "moment": "../bower_components/moment/min/moment-with-locales.min",
"tablesort": "../node_modules/tablesort/tablesort.min", "tablesort": "../bower_components/tablesort/tablesort.min",
"tablesort.numeric": "../node_modules/tablesort/src/sorts/tablesort.numeric", "tablesort.numeric": "../bower_components/tablesort/src/sorts/tablesort.numeric",
"d3": "../node_modules/d3/d3.min", "d3": "../bower_components/d3/d3.min",
"numeral": "../node_modules/numeraljs/min/numeral.min", "numeral": "../bower_components/numeraljs/min/numeral.min",
"numeral-intl": "../node_modules/numeraljs/min/languages.min", "numeral-intl": "../bower_components/numeraljs/min/languages.min",
"virtual-dom": "../node_modules/virtual-dom/dist/virtual-dom", "virtual-dom": "../bower_components/virtual-dom/dist/virtual-dom",
"rbush": "../node_modules/rbush/rbush", "rbush": "../bower_components/rbush/rbush",
"helper": "../helper", "helper": "../helper",
"jshashes": "../node_modules/jshashes/hashes" "jshashes": "../bower_components/jshashes/hashes"
}, },
shim: { shim: {
"leaflet.label": ["leaflet"], "leaflet.label": ["leaflet"],

36
bower.json Normal file
View file

@ -0,0 +1,36 @@
{
"name": "HopGlass",
"ignore": [
"node_modules",
"bower_components",
"**/.*",
"test",
"tests"
],
"dependencies": {
"Leaflet.label": "~0.2.1",
"chroma-js": "~0.6.1",
"leaflet": "~0.7.3",
"ionicons": "~2.0.1",
"moment": "~2.9.0",
"requirejs": "~2.1.16",
"tablesort": "https://github.com/tristen/tablesort.git#v3.0.2",
"roboto-slab-fontface": "*",
"es6-shim": "~0.27.1",
"almond": "~0.3.1",
"r.js": "~2.1.16",
"d3": "~3.5.5",
"numeraljs": "~1.5.3",
"roboto-fontface": "~0.3.0",
"virtual-dom": "~2.0.1",
"leaflet-providers": "~1.0.27",
"rbush": "https://github.com/mourner/rbush.git#~1.3.5",
"jshashes": "~1.0.5"
},
"authors": [
"Milan Pässler <me@petabyteboy.de>",
"Nils Schneider <nils@nilsschneider.net>"
],
"license": "AGPL3",
"private": true
}

View file

@ -1,6 +1,6 @@
({ ({
baseUrl: "lib", baseUrl: "lib",
name: "../node_modules/almond/almond", name: "../bower_components/almond/almond",
mainConfigFile: "app.js", mainConfigFile: "app.js",
include: "../app", include: "../app",
wrap: true, wrap: true,

View file

@ -1,62 +1,27 @@
{ {
"dataPath": "https://map.ffdus.de/data/", "dataPath": "https://map.luebeck.freifunk.net/data/",
"siteName": "Freifunk Flingern", "siteName": "Freifunk Lübeck",
"mapSigmaScale": 0.5, "mapSigmaScale": 0.5,
"showContact": true, "showContact": true,
"maxAge": 14, "maxAge": 14,
"mapLayers": [ "mapLayers": [
{ "name": "CartoDB", { "name": "MapQuest",
"url": "https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png", "url": "https://otile{s}-s.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg",
"config": { "config": {
"maxZoom": 18, "subdomains": "1234",
"attribution": "&copy; <a href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a>, &copy; | <a href=\"https://carto.com/attribution\">CARTO</a>" "type": "osm",
"attribution": "Tiles &copy; <a href=\"https://www.mapquest.com/\" target=\"_blank\">MapQuest</a>, Data CC-BY-SA OpenStreetMap",
"maxZoom": 18
} }
}, },
{ {
"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": [
{ "href": "https://map.eulenfunk.de/stats/dashboard/db/node-byid?var-nodeid={NODE_ID}",
"thumbnail": "https://map.eulenfunk.de/stats/render/dashboard-solo/db/node-byid?panelId=1&theme=light&width=600&height=300&var-nodeid={NODE_ID}"
},
{ "href": "https://map.eulenfunk.de/stats/dashboard/db/node-byid?var-nodeid={NODE_ID}",
"thumbnail": "https://map.eulenfunk.de/stats/render/dashboard-solo/db/node-byid?panelId=2&theme=light&width=600&height=500&var-nodeid={NODE_ID}"
},
{ "href": "https://map.eulenfunk.de/stats/dashboard/db/node-byid?var-nodeid={NODE_ID}",
"thumbnail": "https://map.eulenfunk.de/stats/render/dashboard-solo/db/node-byid?panelId=3&theme=light&width=600&height=200&var-nodeid={NODE_ID}"
}
],
"globalInfos": [
{ "href": "https://map.eulenfunk.de/stats/dashboard/db/global?var-job=dus",
"thumbnail": "https://map.eulenfunk.de/stats/render/dashboard-solo/db/global?panelId=1&&theme=light&width=800&height=600&var-job=dus"
},
{ "href": "https://map.eulenfunk.de/stats/dashboard/db/global?var-job=dus",
"thumbnail": "https://map.eulenfunk.de/stats/render/dashboard-solo/db/global?panelId=8&&theme=light&width=800&height=600&var-job=dus"
}
],
"linkInfos": [
{ "href": "https://map.eulenfunk.de/stats/dashboard/db/links?var-source={SOURCE}&var-target={TARGET}",
"thumbnail": "https://map.eulenfunk.de/stats/render/dashboard-solo/db/links?panelId=1&&theme=light&width=800&height=600&var-source={SOURCE}&var-target={TARGET}"
} }
], ],
"siteNames": [ "siteNames": [
{ "site": "dus", "name": "Flingern" } { "site": "ffhl", "name": "Lübeck" },
], { "site": "ffeh", "name": "Entenhausen" },
"hwImg": [ { "site": "ffgt", "name": "Gothamcity" },
{ "thumbnail": "https://cdn.rawgit.com/Moorviper/meshviewer_hwpics/master/nodes/{MODELHASH}.svg", { "site": "ffal", "name": "Atlantis" }
"caption": "Knoten {MODELHASH}"
}
] ]
} }

106
helper.js
View file

@ -1,23 +1,23 @@
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) {
@ -73,14 +73,6 @@ function localStorageTest() {
} }
} }
function listReplace(s, subst) {
for (key in subst) {
var re = new RegExp(key, 'g')
s = s.replace(re, subst[key])
}
return s
}
/* Helpers working with nodes */ /* Helpers working with nodes */
function offline(d) { function offline(d) {
@ -130,13 +122,7 @@ function attributeEntry(el, label, value) {
var tr = document.createElement("tr") var tr = document.createElement("tr")
var th = document.createElement("th") var th = document.createElement("th")
if (typeof label === "string")
th.textContent = label th.textContent = label
else {
th.appendChild(label)
tr.className = "routerpic"
}
tr.appendChild(th) tr.appendChild(th)
var td = document.createElement("td") var td = document.createElement("td")
@ -152,77 +138,3 @@ function attributeEntry(el, label, value) {
return td return td
} }
function createIframe(opt, width, height) {
el = document.createElement("iframe")
width = typeof width !== 'undefined' ? width : '100%'
height = typeof height !== 'undefined' ? height : '350px'
if (opt.src)
el.src = opt.src
else
el.src = opt
if (opt.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
}
function showStat(o, subst) {
var content, caption
subst = typeof subst !== 'undefined' ? subst : {}
if (o.thumbnail) {
content = document.createElement("img")
content.src = listReplace(o.thumbnail, subst)
}
if (o.caption) {
caption = listReplace(o.caption, subst)
if (!content)
content = document.createTextNode(caption)
}
if (o.iframe) {
content = createIframe(o.iframe, o.width, o.height)
if (o.iframe.src)
content.src = listReplace(o.iframe.src, subst)
else
content.src = listReplace(o.iframe, subst)
}
var p = document.createElement("p")
if (o.href) {
var link = document.createElement("a")
link.target = "_blank"
link.href = listReplace(o.href, subst)
link.appendChild(content)
if (caption && o.thumbnail)
link.title = caption
p.appendChild(link)
} else
p.appendChild(content)
return p
}

View file

@ -4,8 +4,8 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" href="css/ionicons.min.css"> <link rel="stylesheet" href="css/ionicons.min.css">
<link rel="stylesheet" href="css/roboto-slab/roboto-slab-fontface.css"> <link rel="stylesheet" href="roboto-slab-fontface.css">
<link rel="stylesheet" href="css/roboto/roboto-fontface.css"> <link rel="stylesheet" href="roboto-fontface.css">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="vendor/es6-shim/es6-shim.min.js"></script> <script src="vendor/es6-shim/es6-shim.min.js"></script>
<script src="app.js"></script> <script src="app.js"></script>
@ -14,15 +14,5 @@
</script> </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

@ -3,14 +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="node_modules/roboto-fontface/css/roboto-slab/roboto-slab-fontface.css"> <link rel="stylesheet" href="bower_components/roboto-slab-fontface/roboto-slab-fontface.css">
<link rel="stylesheet" href="node_modules/roboto-fontface/css/roboto/roboto-fontface.css"> <link rel="stylesheet" href="bower_components/roboto-fontface/roboto-fontface.css">
<link rel="stylesheet" href="node_modules/leaflet/dist/leaflet.css"> <link rel="stylesheet" href="bower_components/leaflet/dist/leaflet.css">
<link rel="stylesheet" href="node_modules/leaflet-label/dist/leaflet.label.css"> <link rel="stylesheet" href="bower_components/Leaflet.label/dist/leaflet.label.css">
<link rel="stylesheet" href="node_modules/ionicons/css/ionicons.min.css"> <link rel="stylesheet" href="bower_components/ionicons/css/ionicons.min.css">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="node_modules/es6-shim/es6-shim.min.js"></script> <script src="bower_components/es6-shim/es6-shim.min.js"></script>
<script src="node_modules/requirejs/require.js" data-main="app"></script> <script src="bower_components/requirejs/require.js" data-main="app"></script>
</head> </head>
<body> <body>
</body> </body>

View file

@ -29,8 +29,8 @@ define(function () {
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/hopglass/hopglass\">" s += "<a href=\"https://github.com/plumpudding/hopglass\">"
s += "https://github.com/hopglass/hopglass</a>." s += "https://github.com/plumpudding/hopglass</a>."
el.innerHTML = s el.innerHTML = s
} }

View file

@ -4,18 +4,18 @@ define([], function () {
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 if (!d.node)
if (d.node) return true
r = filter(d.node)
else var r = filter(d.node)
r = filter({})
if (r) if (r)
filteredIds.add(d.id) filteredIds.add(d.id)

View file

@ -1,7 +1,7 @@
define(["d3"], function (d3) { define(["d3"], function (d3) {
var margin = 200 var margin = 200
var NODE_RADIUS = 15 var NODE_RADIUS = 15
var LINE_RADIUS = 7 var LINE_RADIUS = 12
return function (config, linkScale, sidebar, router) { return function (config, linkScale, sidebar, router) {
var self = this var self = this
@ -19,7 +19,6 @@ define(["d3"], function (d3) {
var nodes = [] var nodes = []
var uplinkNodes = [] var uplinkNodes = []
var nonUplinkNodes = [] var nonUplinkNodes = []
var unseenNodes = []
var unknownNodes = [] var unknownNodes = []
var savedPanZoom var savedPanZoom
@ -214,7 +213,7 @@ define(["d3"], function (d3) {
function drawLabel(d) { function drawLabel(d) {
var neighbours = d.neighbours.filter(function (d) { var neighbours = d.neighbours.filter(function (d) {
return !d.link.o.isVPN return d.link.o.type !== "VPN"
}) })
var sum = neighbours.reduce(function (a, b) { var sum = neighbours.reduce(function (a, b) {
@ -242,8 +241,7 @@ define(["d3"], function (d3) {
} }
function visibleLinks(d) { function visibleLinks(d) {
return (d.o.isVPN || return (d.source.x > screenRect.left && d.source.x < screenRect.right &&
d.source.x > screenRect.left && d.source.x < screenRect.right &&
d.source.y > screenRect.top && d.source.y < screenRect.bottom) || d.source.y > screenRect.top && d.source.y < screenRect.bottom) ||
(d.target.x > screenRect.left && d.target.x < screenRect.right && (d.target.x > screenRect.left && d.target.x < screenRect.right &&
d.target.y > screenRect.top && d.target.y < screenRect.bottom) d.target.y > screenRect.top && d.target.y < screenRect.bottom)
@ -315,7 +313,6 @@ define(["d3"], function (d3) {
var unknownColor = "#D10E2A" var unknownColor = "#D10E2A"
var nonUplinkColor = "#F2E3C6" var nonUplinkColor = "#F2E3C6"
var uplinkColor = "#5BAAEB" var uplinkColor = "#5BAAEB"
var unseenColor = "#FFA726"
var highlightColor = "rgba(252, 227, 198, 0.15)" var highlightColor = "rgba(252, 227, 198, 0.15)"
var nodeRadius = 6 var nodeRadius = 6
var cableColor = "#50B0F0" var cableColor = "#50B0F0"
@ -325,19 +322,16 @@ define(["d3"], function (d3) {
links.forEach(function (d) { links.forEach(function (d) {
var dx = d.target.x - d.source.x var dx = d.target.x - d.source.x
var dy = d.target.y - d.source.y var dy = d.target.y - d.source.y
var a = Math.sqrt(dx * dx + dy * dy) * 2 var a = Math.sqrt(dx * dx + dy * dy)
dx /= a dx /= a
dy /= a dy /= a
var distancex = d.target.x - d.source.x - (10 * dx)
var distancey = d.target.y - d.source.y - (10 * dy)
ctx.beginPath() ctx.beginPath()
ctx.moveTo(d.source.x + dx * nodeRadius, d.source.y + dy * nodeRadius) ctx.moveTo(d.source.x + dx * nodeRadius, d.source.y + dy * nodeRadius)
ctx.lineTo(d.target.x - (distancex / 2) - dx * nodeRadius, d.target.y - (distancey / 2) - dy * nodeRadius) ctx.lineTo(d.target.x - dx * nodeRadius, d.target.y - dy * nodeRadius)
ctx.strokeStyle = d.o.type === "Kabel" ? cableColor : d.color ctx.strokeStyle = d.o.type === "Kabel" ? cableColor : d.color
ctx.globalAlpha = d.o.isVPN ? 0.1 : 0.8 ctx.globalAlpha = d.o.type === "VPN" ? 0.1 : 0.8
ctx.lineWidth = d.o.isVPN ? 1.5 : 2.5 ctx.lineWidth = d.o.type === "VPN" ? 1.5 : 2.5
ctx.stroke() ctx.stroke()
}) })
@ -370,17 +364,11 @@ define(["d3"], function (d3) {
ctx.drawImage(uplinkNode, scale * r * d.x - uplinkNode.width / 2, scale * r * d.y - uplinkNode.height / 2) ctx.drawImage(uplinkNode, scale * r * d.x - uplinkNode.width / 2, scale * r * d.y - uplinkNode.height / 2)
}) })
var unseenNode = drawNode(unseenColor, nodeRadius, scale, r)
unseenNodes.filter(visibleNodes).forEach(function (d) {
ctx.drawImage(unseenNode, scale * r * d.x - unseenNode.width / 2, scale * r * d.y - unseenNode.height / 2)
})
ctx.restore() ctx.restore()
// -- draw clients -- // -- draw clients --
ctx.save() ctx.save()
ctx.beginPath() ctx.beginPath()
if (scale > 0.9)
nodes.filter(visibleNodes).forEach(function (d) { nodes.filter(visibleNodes).forEach(function (d) {
var clients = d.o.node.statistics.clients var clients = d.o.node.statistics.clients
if (clients === 0) if (clients === 0)
@ -475,34 +463,32 @@ define(["d3"], function (d3) {
requestAnimationFrame(redraw) requestAnimationFrame(redraw)
} }
function distance(ax, ay, bx, by) { function distance(a, b) {
return Math.pow(ax - bx, 2) + Math.pow(ay - by, 2) return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)
} }
function distancePoint(a, b) { function distancePoint(a, b) {
return Math.sqrt(distance(a.x, a.y, b.x, b.y)) return Math.sqrt(distance(a, b))
} }
function distanceLink(p, a, b) { function distanceLink(p, a, b) {
/* http://stackoverflow.com/questions/849211 */ /* http://stackoverflow.com/questions/849211 */
var bx = b.x - ((b.x - a.x) / 2) var l2 = distance(a, b)
var by = b.y - ((b.y - a.y) / 2)
var l2 = distance(a.x, a.y, bx, by)
if (l2 === 0) if (l2 === 0)
return distance(p.x, p.y, a.x, a.y) return distance(p, a)
var t = ((p.x - a.x) * (bx - a.x) + (p.y - a.y) * (by - a.y)) / l2 var t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2
if (t < 0) if (t < 0)
return distance(p.x, p.y, a.x, a.y) return distance(p, a)
if (t > 1) if (t > 1)
return distance(p.x, p.y, bx, by) return distance(p, b)
return Math.sqrt(distance(p.x, p.y, a.x + t * (bx - a.x), a.y + t * (by - a.y) )) return Math.sqrt(distance(p, { x: a.x + t * (b.x - a.x),
y: a.y + t * (b.y - a.y) }))
} }
function translateXY(d) { function translateXY(d) {
@ -530,7 +516,7 @@ define(["d3"], function (d3) {
} }
var links = intLinks.filter(function (d) { var links = intLinks.filter(function (d) {
return !d.o.isVPN return d.o.type !== "VPN"
}).filter(function (d) { }).filter(function (d) {
return distanceLink(e, d.source, d.target) < LINE_RADIUS return distanceLink(e, d.source, d.target) < LINE_RADIUS
}) })
@ -591,13 +577,13 @@ define(["d3"], function (d3) {
.charge(-250) .charge(-250)
.gravity(0.1) .gravity(0.1)
.linkDistance(function (d) { .linkDistance(function (d) {
if (d.o.isVPN) if (d.o.type === "VPN")
return 0 return 0
else else
return LINK_DISTANCE return LINK_DISTANCE
}) })
.linkStrength(function (d) { .linkStrength(function (d) {
if (d.o.isVPN) if (d.o.type === "VPN")
return 0 return 0
else else
return Math.max(0.5, 1 / d.o.tq) return Math.max(0.5, 1 / d.o.tq)
@ -651,7 +637,7 @@ define(["d3"], function (d3) {
e.source = newNodesDict[d.source.id] e.source = newNodesDict[d.source.id]
e.target = newNodesDict[d.target.id] e.target = newNodesDict[d.target.id]
if (d.isVPN) if (d.type === "VPN")
e.color = "rgba(255, 255, 255, " + (0.6 / d.tq) + ")" e.color = "rgba(255, 255, 255, " + (0.6 / d.tq) + ")"
else else
e.color = linkScale(d.tq).hex() e.color = linkScale(d.tq).hex()
@ -699,29 +685,19 @@ define(["d3"], function (d3) {
d.source.neighbours[d.target.o.id] = {node: d.target, link: d} d.source.neighbours[d.target.o.id] = {node: d.target, link: d}
d.target.neighbours[d.source.o.id] = {node: d.source, link: d} d.target.neighbours[d.source.o.id] = {node: d.source, link: d}
if (d.o.source && d.o.target) if (d.o.source.node && d.o.target.node)
linksDict[d.o.id] = d linksDict[d.o.id] = d
}) })
intLinks.forEach(function (d) {
if (linksDict[d.target.o.node_id + "-" + d.source.o.node_id])
return
var obj = { source: d.target, target: d.source, o: { isVPN: d.o.isVPN, type: "dead", id: d.target.o.node_id + "-" + d.source.o.node_id, tq: 1 }, color: "rgba(255, 255, 255, 0.6)" }
intLinks.push(obj)
linksDict[d.target.o.node_id + "-" + d.source.o.node_id] = obj
})
intNodes.forEach(function (d) { intNodes.forEach(function (d) {
d.neighbours = Object.keys(d.neighbours).map(function (k) { d.neighbours = Object.keys(d.neighbours).map(function (k) {
return d.neighbours[k] return d.neighbours[k]
}) })
}) })
nodes = intNodes.filter(function (d) { return !d.o.unseen && d.o.node }) nodes = intNodes.filter(function (d) { return d.o.node })
uplinkNodes = nodes.filter(function (d) { return d.o.node.flags.uplink }) uplinkNodes = nodes.filter(function (d) { return d.o.node.flags.uplink })
nonUplinkNodes = nodes.filter(function (d) { return !d.o.node.flags.uplink }) nonUplinkNodes = nodes.filter(function (d) { return !d.o.node.flags.uplink })
unseenNodes = intNodes.filter(function (d) { return d.o.unseen && d.o.node })
unknownNodes = intNodes.filter(function (d) { return !d.o.node }) unknownNodes = intNodes.filter(function (d) { return !d.o.node })
if (localStorageTest()) { if (localStorageTest()) {

View file

@ -48,9 +48,6 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
} }
} }
var loader = document.getElementsByClassName("loader")[0]
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)
@ -60,7 +57,7 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
contentDiv.appendChild(buttons) contentDiv.appendChild(buttons)
var buttonToggle = document.createElement("button") var buttonToggle = document.createElement("button")
buttonToggle.textContent = "\uF133" buttonToggle.textContent = ""
buttonToggle.onclick = function () { buttonToggle.onclick = function () {
if (content.constructor === Map) if (content.constructor === Map)
router.view("g") router.view("g")

View file

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

View file

@ -1,100 +1,92 @@
define(function () { define(function () {
return function (config, el, router, d) { return function (config, el, router, d) {
var sidebarTitle = document.createElement("h2") var h2 = document.createElement("h2")
sidebarTitle.textContent = "Location: " + d.toString() h2.textContent = "Location: " + d.toString()
el.appendChild(sidebarTitle) el.appendChild(h2)
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&addressdetail=0")
.then(function(result) { .then(function(result) {
if(result.display_name) h2.textContent = result.display_name
sidebarTitle.textContent = result.display_name
}) })
var editLat = document.createElement("input") var h3lat = document.createElement("h3")
editLat.type = "text" h3lat.textContent = "Breitengrad"
editLat.value = d.lat.toFixed(9) el.appendChild(h3lat)
el.appendChild(createBox("lat", "Breitengrad", editLat)) var txt1 = document.createElement("textarea")
txt1.id = "location-latitude"
txt1.value = d.lat.toFixed(9)
var p = document.createElement("p")
p.appendChild(txt1)
p.appendChild(createCopyButton(txt1.id))
el.appendChild(p)
var editLng = document.createElement("input") var h3lng = document.createElement("h3")
editLng.type = "text" h3lng.textContent = "Längengrad"
editLng.value = d.lng.toFixed(9) el.appendChild(h3lng)
el.appendChild(createBox("lng", "Längengrad", editLng)) var txt2 = document.createElement("textarea")
txt2.id = "location-longitude"
txt2.value = d.lng.toFixed(9)
var p2 = document.createElement("p")
p2.appendChild(txt2)
p2.appendChild(createCopyButton(txt2.id))
el.appendChild(p2)
var editUci = document.createElement("textarea") var a1 = document.createElement("a")
editUci.value = a1.textContent = "plain"
"uci set gluon-node-info.@location[0]='location'; " + a1.onclick = function() {
"uci set gluon-node-info.@location[0].share_location='1';" +
"uci set gluon-node-info.@location[0].latitude='" + d.lat.toFixed(9) + "';" +
"uci set gluon-node-info.@location[0].longitude='" + d.lng.toFixed(9) + "';" +
"uci commit gluon-node-info"
el.appendChild(createBox("uci", "Befehl", editUci, false))
var linkPlain = document.createElement("a")
linkPlain.textContent = "plain"
linkPlain.onclick = function() {
switch2plain() switch2plain()
return false return false
} }
linkPlain.href = "#" a1.href = config.siteURL
var a2 = document.createElement("a")
var linkUci = document.createElement("a") a2.textContent = "uci"
linkUci.textContent = "uci" a2.onclick = function() {
linkUci.onclick = function() {
switch2uci() switch2uci()
return false return false
} }
linkUci.href = "#" a2.href = config.siteURL
var hintText = document.createElement("p") var p3 = document.createElement("p")
hintText.appendChild(document.createTextNode("Du kannst zwischen ")) p3.textContent = "Du kannst zwischen "
hintText.appendChild(linkPlain) p3.appendChild(a1)
hintText.appendChild(document.createTextNode(" und ")) var t1 = document.createTextNode(" und ")
hintText.appendChild(linkUci) p3.appendChild(t1)
hintText.appendChild(document.createTextNode(" wechseln.")) p3.appendChild(a2)
el.appendChild(hintText) var t2 = document.createTextNode(" wechseln.")
p3.appendChild(t2)
el.appendChild(p3)
function createBox(name, title, inputElem, isVisible) { function createCopyButton(id) {
var visible = typeof isVisible !== "undefined" ? isVisible : true
var box = document.createElement("div")
var heading = document.createElement("h3")
heading.textContent = title
box.appendChild(heading)
var btn = document.createElement("button") var btn = document.createElement("button")
btn.className = "ion-ios-copy" btn.className = "ion-ios-copy"
btn.title = "Kopieren" btn.title = "Kopiere"
btn.onclick = function() { copy2clip(inputElem.id) } btn.onclick = function() {
inputElem.id = "location-" + name copy2clip(id)
inputElem.readOnly = true }
var line = document.createElement("p") return btn
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 copyTextarea = document.querySelector("#" + id)
copyField.select() copyTextarea.select()
try { try {
document.execCommand("copy") var successful = document.execCommand("copy")
var msg = successful ? "successful" : "unsuccessful"
console.log("Copying text command was " + msg)
} catch (err) { } catch (err) {
console.log(err) console.log("Oops, unable to copy")
} }
} }
function switch2plain() { function switch2plain() {
document.getElementById("box-uci").style.display = "none" var box1 = document.getElementById("location-latitude")
document.getElementById("box-lat").style.display = "block" box1.value = d.lat.toFixed(9)
document.getElementById("box-lng").style.display = "block" var box2 = document.getElementById("location-longitude")
box2.value = d.lng.toFixed(9)
} }
function switch2uci() { function switch2uci() {
document.getElementById("box-uci").style.display = "block" var box1 = document.getElementById("location-latitude")
document.getElementById("box-lat").style.display = "none" box1.value = "uci set gluon-node-info.@location[0].latitude='" + d.lat.toFixed(9) + "'"
document.getElementById("box-lng").style.display = "none" var box2 = document.getElementById("location-longitude")
box2.value = "uci set gluon-node-info.@location[0].longitude='" + d.lng.toFixed(9) + "'"
} }
} }
}) })

View file

@ -42,6 +42,7 @@ define(["infobox/link", "infobox/node", "infobox/location"], function (Link, Nod
} }
self.gotoLocation = function (d) { self.gotoLocation = function (d) {
console.log("goto location called with ", d)
create() create()
new Location(config, el, router, d) new Location(config, el, router, d)
} }

View file

@ -38,11 +38,8 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
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.online ? "online" : "offline")
if (d.flags.online) el.textContent = (d.flags.online ? "online " : "offline, " + d.lastseen.fromNow(true)) + " (Stand " + 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
el.textContent = "offline, letzte Nachricht " + d.lastseen.fromNow() + " (" + d.lastseen.format("DD.MM.YYYY, H:mm:ss") + ")"
} }
} }
@ -81,124 +78,18 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
return d.firstseen.fromNow(true) return d.firstseen.fromNow(true)
} }
function wifiChannelAlias(ch) {
var chlist = {
"1": "2412 MHz",
"2": "2417 MHz",
"3": "2422 MHz",
"4": "2427 MHz",
"5": "2432 MHz",
"6": "2437 MHz",
"7": "2442 MHz",
"8": "2447 MHz",
"9": "2452 MHz",
"10": "2457 MHz",
"11": "2462 MHz",
"12": "2467 MHz",
"13": "2472 MHz",
"36": "5180 MHz (Indoors)",
"40": "5200 MHz (Indoors)",
"44": "5220 MHz (Indoors)",
"48": "5240 MHz (Indoors)",
"52": "5260 MHz (Indoors/DFS/TPC)",
"56": "5280 MHz (Indoors/DFS/TPC)",
"60": "5300 MHz (Indoors/DFS/TPC)",
"64": "5320 MHz (Indoors/DFS/TPC)",
"100": "5500 MHz (DFS) !!",
"104": "5520 MHz (DFS) !!",
"108": "5540 MHz (DFS) !!",
"112": "5560 MHz (DFS) !!",
"116": "5580 MHz (DFS) !!",
"120": "5600 MHz (DFS) !!",
"124": "5620 MHz (DFS) !!",
"128": "5640 MHz (DFS) !!",
"132": "5660 MHz (DFS) !!",
"136": "5680 MHz (DFS) !!",
"140": "5700 MHz (DFS) !!"
}
if (!(ch in chlist))
return ""
else
return chlist[ch]
}
function showWifiChannel(ch) {
if (!ch)
return undefined
return ch + " (" + wifiChannelAlias(ch) + ")"
}
function showClients(d) { function showClients(d) {
if (!d.flags.online) if (!d.flags.online)
return undefined return undefined
var meshclients = getMeshClients(d)
resetMeshClients(d)
var before = " ("
var after = " in der lokalen Wolke)"
return function (el) { 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(meshclients > 0 ? meshclients : "keine"))
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.textContent = " ".repeat(d.statistics.clients) span.textContent = " ".repeat(d.statistics.clients)
el.appendChild(span) el.appendChild(span)
var spanmesh = document.createElement("span")
spanmesh.classList.add("clientsMesh")
spanmesh.textContent = " ".repeat(meshclients - d.statistics.clients)
el.appendChild(spanmesh)
}
}
function getMeshClients(node) {
var meshclients = 0
if (node.statistics && !isNaN(node.statistics.clients))
meshclients = node.statistics.clients
if (!node)
return 0
if (node.parsed)
return 0
node.parsed = 1
node.neighbours.forEach(function (neighbour) {
if (!neighbour.link.isVPN && neighbour.node)
meshclients += getMeshClients(neighbour.node)
})
return meshclients
}
function resetMeshClients(node) {
if (!node.parsed)
return
node.parsed = 0
node.neighbours.forEach(function (neighbour) {
if (!neighbour.link.isVPN && neighbour.node)
resetMeshClients(neighbour.node)
})
return
}
function showMeshClients(d) {
if (!d.flags.online)
return undefined
var meshclients = getMeshClients(d)
resetMeshClients(d)
return function (el) {
el.appendChild(document.createTextNode(meshclients > 0 ? meshclients : "keine"))
el.appendChild(document.createElement("br"))
} }
} }
@ -218,9 +109,6 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
if (link) { if (link) {
var a = document.createElement("a") var a = document.createElement("a")
if (ip.includes("."))
a.href = "http://" + ip + "/"
else
a.href = "http://[" + ip + "]/" a.href = "http://[" + ip + "]/"
a.textContent = ip a.textContent = ip
el.appendChild(a) el.appendChild(a)
@ -265,7 +153,7 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
} }
var label = document.createElement("label") var label = document.createElement("label")
label.textContent = +(Math.round(v + "e+2") + "e-2") label.textContent = (v)
span.appendChild(label) span.appendChild(label)
return span return span
@ -289,65 +177,6 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
} }
} }
function showAirtime(band, val) {
if (!val)
return undefined
return function (el) {
el.appendChild(showBar("airtime" + band.toString(), val))
}
}
function createLink(target, router) {
if (!target) return document.createTextNode("unknown")
var unknown = !(target.node)
var text = unknown ? (target.id ? target.id : target) : target.node.nodeinfo.hostname
if (!unknown) {
var link = document.createElement("a")
link.classList.add("hostname-link")
link.href = "#"
link.onclick = router.node(target.node)
link.textContent = text
return link
}
return document.createTextNode(text)
}
function showGateway(d, router) {
var nh
if (dictGet(d.statistics, ["nexthop"]))
nh = dictGet(d.statistics, ["nexthop"])
if (dictGet(d.statistics, ["gateway_nexthop"]))
nh = dictGet(d.statistics, ["gateway_nexthop"])
var gw = dictGet(d.statistics, ["gateway"])
if (!gw) return null
return function (el) {
var num = 0
while (gw && nh && gw.id !== nh.id && num < 10) {
if (num !== 0) el.appendChild(document.createTextNode(" -> "))
el.appendChild(createLink(nh, router))
num++
if (!nh.node || !nh.node.statistics) break
if (!dictGet(nh.node.statistics, ["gateway"]) || !dictGet(nh.node.statistics, ["gateway"]).id) break
if (dictGet(nh.node.statistics, ["gateway"]).id !== gw.id) break
if (dictGet(nh.node.statistics, ["gateway_nexthop"]))
nh = dictGet(nh.node.statistics, ["gateway_nexthop"])
else if (dictGet(nh.node.statistics, ["nexthop"]))
nh = dictGet(nh.node.statistics, ["nexthop"])
else
break
}
if (gw && nh && gw.id !== nh.id) {
if (num !== 0) el.appendChild(document.createTextNode(" -> "))
num++
el.appendChild(document.createTextNode("..."))
}
if (num !== 0) el.appendChild(document.createTextNode(" -> "))
el.appendChild(createLink(gw, router))
}
}
function showPages(d) { function showPages(d) {
var webpages = dictGet(d.nodeinfo, ["pages"]) var webpages = dictGet(d.nodeinfo, ["pages"])
if (webpages === null) if (webpages === null)
@ -387,68 +216,46 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
return au.enabled ? "aktiviert (" + au.branch + ")" : "deaktiviert" return au.enabled ? "aktiviert (" + au.branch + ")" : "deaktiviert"
} }
function showNodeImg(o, model) { function showStatImg(o, nodeId) {
if (!model)
return document.createTextNode("Knotenname")
var content, caption var content, caption
var modelhash = model.split("").reduce(function(a, b) {
a = ((a << 5) - a) + b.charCodeAt(0)
return a & a
}, 0)
if (o.thumbnail) {
content = document.createElement("img") content = document.createElement("img")
content.id = "routerpicture" content.src = o.thumbnail.replace("{NODE_ID}", nodeId)
content.classList.add("nodeImg")
content.src = o.thumbnail.replace("{MODELHASH}", modelhash)
content.onerror = function() {
document.getElementById("routerpicdiv").outerHTML = "Knotenname"
} }
if (o.caption) { if (o.caption) {
caption = o.caption.replace("{MODELHASH}", modelhash) caption = o.caption.replace("{NODE_ID}", nodeId)
if (!content) if (!content)
content = document.createTextNode(caption) content = document.createTextNode(caption)
} }
var p = document.createElement("p") var p = document.createElement("p")
if (o.href) {
var link = document.createElement("a")
link.target = "_blank"
link.href = o.href.replace("{NODE_ID}", nodeId)
link.appendChild(content)
if (caption && o.thumbnail)
link.title = caption
p.appendChild(link)
} else
p.appendChild(content) p.appendChild(content)
return content return p
}
function showStatImg(o, d) {
var subst = {}
subst["{NODE_ID}"] = d.nodeinfo.node_id ? d.nodeinfo.node_id : "unknown"
subst["{NODE_NAME}"] = d.nodeinfo.hostname ? d.nodeinfo.hostname : "unknown"
return showStat(o, subst)
} }
return function(config, el, router, d) { return function(config, el, router, d) {
var attributes = document.createElement("table")
attributes.classList.add("attributes")
if (config.hwImg) {
var top = document.createElement("div")
top.id = "routerpicdiv"
try {
config.hwImg.forEach(function(hwImg) {
try {
top.appendChild(showNodeImg(hwImg, dictGet(d, ["nodeinfo", "hardware", "model"])))
} catch (err) {
console.log(err.message)
}
})
} catch (err) {
console.log(err.message)
}
attributeEntry(attributes, top, d.nodeinfo.hostname)
} else {
var h2 = document.createElement("h2") var h2 = document.createElement("h2")
h2.textContent = d.nodeinfo.hostname h2.textContent = d.nodeinfo.hostname
el.appendChild(h2) el.appendChild(h2)
}
var attributes = document.createElement("table")
attributes.classList.add("attributes")
attributeEntry(attributes, "Status", showStatus(d)) attributeEntry(attributes, "Status", showStatus(d))
attributeEntry(attributes, "Gateway", d.flags.gateway ? "ja" : null) attributeEntry(attributes, "Gateway", d.flags.gateway ? "ja" : null)
@ -464,17 +271,13 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
attributeEntry(attributes, "Site", showSite(d, config)) attributeEntry(attributes, "Site", showSite(d, config))
attributeEntry(attributes, "Uptime", showUptime(d)) attributeEntry(attributes, "Uptime", showUptime(d))
attributeEntry(attributes, "Teil des Netzes", showFirstseen(d)) attributeEntry(attributes, "Teil des Netzes", showFirstseen(d))
attributeEntry(attributes, "Kanal 2.4 GHz", showWifiChannel(dictGet(d.nodeinfo, ["wireless", "chan2"])))
attributeEntry(attributes, "Kanal 5 GHz", showWifiChannel(dictGet(d.nodeinfo, ["wireless", "chan5"])))
attributeEntry(attributes, "Airtime 2.4 GHz", showAirtime(2, dictGet(d.statistics, ["wireless", "airtime2"])))
attributeEntry(attributes, "Airtime 5 GHz", showAirtime(5, dictGet(d.statistics, ["wireless", "airtime5"])))
attributeEntry(attributes, "Systemlast", showLoad(d)) attributeEntry(attributes, "Systemlast", showLoad(d))
attributeEntry(attributes, "Arbeitsspeicher", showRAM(d)) attributeEntry(attributes, "Arbeitsspeicher", showRAM(d))
attributeEntry(attributes, "IP Adressen", showIPs(d)) attributeEntry(attributes, "IP Adressen", showIPs(d))
attributeEntry(attributes, "Webseite", showPages(d)) attributeEntry(attributes, "Webseite", showPages(d))
attributeEntry(attributes, "Gewähltes Gateway", showGateway(d, router)) attributeEntry(attributes, "Gewähltes Gateway", dictGet(d.statistics, ["gateway"]))
attributeEntry(attributes, "Autom. Updates", showAutoupdate(d)) attributeEntry(attributes, "Autom. Updates", showAutoupdate(d))
attributeEntry(attributes, "Clients", showClients(d), showMeshClients(d)) attributeEntry(attributes, "Clients", showClients(d))
el.appendChild(attributes) el.appendChild(attributes)
@ -484,7 +287,7 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
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.nodeinfo.node_id))
}) })
if (d.neighbours.length > 0) { if (d.neighbours.length > 0) {
@ -523,7 +326,6 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
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 tr = document.createElement("tr") var tr = document.createElement("tr")
var td1 = document.createElement("td") var td1 = document.createElement("td")
@ -531,9 +333,14 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
tr.appendChild(td1) tr.appendChild(td1)
var td2 = document.createElement("td") var td2 = document.createElement("td")
td2.appendChild(createLink(d, router)) var a1 = document.createElement("a")
a1.classList.add("hostname")
a1.textContent = d.node.nodeinfo.hostname
a1.href = "#"
a1.onclick = router.node(d.node)
td2.appendChild(a1)
if (!unknown && has_location(d.node)) { if (has_location(d.node)) {
var span = document.createElement("span") var span = document.createElement("span")
span.classList.add("icon") span.classList.add("icon")
span.classList.add("ion-location") span.classList.add("ion-location")

View file

@ -1,6 +1,6 @@
define(["sorttable", "virtual-dom"], function (SortTable, V) { define(["sorttable", "virtual-dom"], function (SortTable, V) {
function linkName(d) { function linkName(d) {
return (d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + " " + d.target.node.nodeinfo.hostname return d.source.node.nodeinfo.hostname + " " + d.target.node.nodeinfo.hostname
} }
var headings = [{ name: "Knoten", var headings = [{ name: "Knoten",

View file

@ -4,14 +4,12 @@ function (moment, Router, L, GUI, numeral) {
function handleData(data) { function handleData(data) {
var dataNodes = {} var dataNodes = {}
dataNodes.nodes = [] dataNodes.nodes = []
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
@ -34,22 +32,11 @@ function (moment, Router, L, GUI, numeral) {
vererr = "Unsupported nodes version: " + data[i].version vererr = "Unsupported nodes version: " + data[i].version
console.log(vererr) //silent fail console.log(vererr) //silent fail
} else { } else {
data[i].nodes.forEach(fillData) dataNodes.nodes = dataNodes.nodes.concat(data[i].nodes)
dataNodes.timestamp = data[i].timestamp dataNodes.timestamp = data[i].timestamp
} }
} }
function fillData (node) {
var position = dataNodes.nodeIds.indexOf(node.nodeinfo.node_id)
if(position === -1){
dataNodes.nodes.push(node)
dataNodes.nodeIds.push(node.nodeinfo.node_id)
}
else
if(node.flags.online === true)
dataNodes.nodes[position] = node
}
var nodes = dataNodes.nodes.filter( function (d) { var nodes = dataNodes.nodes.filter( function (d) {
return "firstseen" in d && "lastseen" in d return "firstseen" in d && "lastseen" in d
}) })
@ -74,17 +61,15 @@ function (moment, Router, L, GUI, numeral) {
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) {
d.node.flags.online = true
d.node.flags.unseen = true
}
}
}) })
graph.links.forEach( function (d) { graph.links.forEach( function (d) {
if (graph.nodes[d.source].node)
d.source = graph.nodes[d.source] d.source = graph.nodes[d.source]
else
d.source = undefined
if (graph.nodes[d.target].node) if (graph.nodes[d.target].node)
d.target = graph.nodes[d.target] d.target = graph.nodes[d.target]
@ -93,25 +78,14 @@ function (moment, Router, L, GUI, numeral) {
}) })
var links = graph.links.filter( function (d) { var links = graph.links.filter( function (d) {
return d.target !== undefined return d.source !== undefined && d.target !== undefined
}) })
links.forEach( function (d) { links.forEach( function (d) {
var unknown = (d.source.node === undefined) var ids = [d.source.node.nodeinfo.node_id, d.target.node.nodeinfo.node_id]
var ids d.id = ids.sort().join("-")
if (unknown)
ids = [d.source.id.replace(/:/g, ""), d.target.node.nodeinfo.node_id]
else
ids = [d.source.node.nodeinfo.node_id, d.target.node.nodeinfo.node_id]
d.id = ids.join("-")
if (unknown || if (!("location" in d.source.node.nodeinfo && "location" in d.target.node.nodeinfo))
!d.source.node.nodeinfo.location ||
!d.target.node.nodeinfo.location ||
isNaN(d.source.node.nodeinfo.location.latitude) ||
isNaN(d.source.node.nodeinfo.location.longitude) ||
isNaN(d.target.node.nodeinfo.location.latitude) ||
isNaN(d.target.node.nodeinfo.location.longitude))
return return
d.latlngs = [] d.latlngs = []
@ -123,53 +97,21 @@ function (moment, Router, L, GUI, numeral) {
nodes.forEach( function (d) { nodes.forEach( function (d) {
d.neighbours = [] d.neighbours = []
if (d.statistics) {
/*eslint camelcase:0*/
if ("gateway" in d.statistics && d.statistics.gateway in graphnodes)
d.statistics.gateway = {"node": graphnodes[d.statistics.gateway], "id": d.statistics.gateway}
if ("nexthop" in d.statistics && d.statistics.nexthop in graphnodes)
d.statistics.nexthop = {"node": graphnodes[d.statistics.nexthop], "id": d.statistics.nexthop}
if ("gateway_nexthop" in d.statistics && d.statistics.gateway_nexthop in graphnodes)
d.statistics.gateway_nexthop = {"node": graphnodes[d.statistics.gateway_nexthop], "id": d.statistics.gateway_nexthop}
}
}) })
links.forEach( function (d) { links.forEach( function (d) {
if (d.type === "tunnel" || d.vpn) {
d.type = "VPN"
d.isVPN = true
} else if (d.type === "fastd") {
d.type = "fastd"
d.isVPN = true
} else if (d.type === "l2tp") {
d.type = "L2TP"
d.isVPN = true
} else if (d.type === "gre") {
d.type = "GRE"
d.isVPN = true
} else if (d.type === "wireless") {
d.type = "Wifi"
d.isVPN = false
} else if (d.type === "other") {
d.type = "Kabel"
d.isVPN = false
} else {
d.type = "N/A"
d.isVPN = false
}
if (d.isVPN && d.target.node)
d.target.node.flags.uplink = true
var unknown = (d.source.node === undefined)
if (unknown) {
d.target.node.neighbours.push({ id: d.source.id, link: d, incoming: true })
return
}
d.source.node.neighbours.push({ node: d.target.node, link: d, incoming: false }) d.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.type !== "tunnel")
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
if (d.type === "tunnel")
d.type = "VPN"
else if (d.type === "wireless")
d.type = "Wifi"
else if (d.type === "other")
d.type = "Kabel"
else
d.type = "N/A"
}) })
links.sort( function (a, b) { links.sort( function (a, b) {

View file

@ -18,7 +18,7 @@ define(["map/clientlayer", "map/labelslayer",
onAdd: function () { onAdd: function () {
var button = L.DomUtil.create("button", "add-layer") var button = L.DomUtil.create("button", "add-layer")
button.textContent = "\uF2C7" button.textContent = ""
// 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
@ -46,7 +46,7 @@ define(["map/clientlayer", "map/labelslayer",
onAdd: function () { onAdd: function () {
var button = L.DomUtil.create("button", "locate-user") var button = L.DomUtil.create("button", "locate-user")
button.textContent = "\uF2E9" button.textContent = ""
L.DomEvent.disableClickPropagation(button) L.DomEvent.disableClickPropagation(button)
L.DomEvent.addListener(button, "click", this.onClick, this) L.DomEvent.addListener(button, "click", this.onClick, this)
@ -85,7 +85,7 @@ define(["map/clientlayer", "map/labelslayer",
onAdd: function () { onAdd: function () {
var button = L.DomUtil.create("button", "coord-picker") var button = L.DomUtil.create("button", "coord-picker")
button.textContent = "\uF2A6" button.textContent = ""
// 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().
@ -172,7 +172,7 @@ define(["map/clientlayer", "map/labelslayer",
var map, userLocation var map, userLocation
var layerControl var layerControl
var customLayers = {} var customLayers = new Set()
var baseLayers = {} var baseLayers = {}
var locateUserButton = new LocateButton(function (d) { var locateUserButton = new LocateButton(function (d) {
@ -259,22 +259,23 @@ define(["map/clientlayer", "map/labelslayer",
if (layerName in baseLayers) if (layerName in baseLayers)
return return
if (layerName in customLayers) if (customLayers.has(layerName))
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.add(layerName)
if (localStorageTest()) if (localStorageTest())
localStorage.setItem("map/customLayers", JSON.stringify(Object.keys(customLayers))) localStorage.setItem("map/customLayers", JSON.stringify(Array.from(customLayers)))
} catch (e) { } catch (e) {
return return
} }
} }
function contextMenuGotoLocation(e) { function contextMenuGotoLocation(e) {
console.log("context menu called at ", e)
router.gotoLocation(e.latlng) router.gotoLocation(e.latlng)
} }
@ -286,7 +287,7 @@ define(["map/clientlayer", "map/labelslayer",
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 ? "layers" in d.config ? L.tileLayer.wms(d.url, d.config) : L.tileLayer(d.url, d.config) : L.tileLayer.provider(d.name) "layer": "url" in d ? L.tileLayer(d.url, d.config) : L.tileLayer.provider(d.name)
} }
}) })
@ -318,33 +319,16 @@ define(["map/clientlayer", "map/labelslayer",
if (d) if (d)
d.forEach(addLayer) d.forEach(addLayer)
d = JSON.parse(localStorage.getItem("map/selectedLayer"))
d = d && d.name in baseLayers ? baseLayers[d.name] : d && d.name in customLayers ? customLayers[d.name] : false
if (d) {
map.removeLayer(layers[0].layer)
map.addLayer(d)
}
} }
var clientLayer = new ClientLayer({minZoom: 15}) 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.options.maxZoom = e.layer.options.maxZoom
clientLayer.options.maxZoom = map.options.maxZoom
labelsLayer.options.maxZoom = map.options.maxZoom
if (map.getZoom() > map.options.maxZoom) map.setZoom(map.options.maxZoom)
if (localStorageTest())
localStorage.setItem("map/selectedLayer", JSON.stringify({name: e.name}))
})
var nodeDict = {} var nodeDict = {}
var linkDict = {} var linkDict = {}
var highlight var highlight
@ -464,10 +448,7 @@ define(["map/clientlayer", "map/labelslayer",
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")
barycenter = calcBarycenter(data.nodes.all.filter(has_location)) barycenter = calcBarycenter(data.nodes.all.filter(has_location))
else
barycenter = L.circle(L.latLng(new L.LatLng(config.fixedCenter.lat, config.fixedCenter.lng)), config.fixedCenter.radius * 1000)
var nodesOnline = subtract(data.nodes.all.filter(online), data.nodes.new) var nodesOnline = subtract(data.nodes.all.filter(online), data.nodes.new)
var nodesOffline = subtract(data.nodes.all.filter(offline), data.nodes.lost) var nodesOffline = subtract(data.nodes.all.filter(offline), data.nodes.lost)

View file

@ -1,12 +1,15 @@
define(["leaflet"], define(["leaflet", "jshashes"],
function (L) { function (L, jsHashes) {
var MD5 = new jsHashes.MD5()
return L.TileLayer.Canvas.extend({ return L.TileLayer.Canvas.extend({
setData: function (d) { setData: function (d) {
this.data = d this.data = d
//pre-calculate start angles //pre-calculate start angles
this.data.all().forEach(function (d) { this.data.all().forEach(function (d) {
d.startAngle = (parseInt(d.node.nodeinfo.node_id.substr(10, 2), 16) / 255) * 2 * Math.PI var hash = MD5.hex(d.node.nodeinfo.node_id)
d.startAngle = (parseInt(hash.substr(0, 2), 16) / 255) * 2 * Math.PI
}) })
this.redraw() this.redraw()
}, },

View file

@ -6,22 +6,16 @@ define(function () {
self.setData = function (d) { self.setData = function (d) {
var totalNodes = sum(d.nodes.all.map(one)) var totalNodes = sum(d.nodes.all.map(one))
var totalOnlineNodes = sum(d.nodes.all.filter(online).map(one)) var totalOnlineNodes = sum(d.nodes.all.filter(online).map(one))
var totalOfflineNodes = sum(d.nodes.all.filter(function (node) {return !node.flags.online}).map(one))
var totalNewNodes = sum(d.nodes.new.map(one)) var totalNewNodes = sum(d.nodes.new.map(one))
var totalLostNodes = sum(d.nodes.lost.map(one)) var totalLostNodes = sum(d.nodes.lost.map(one))
var totalClients = sum(d.nodes.all.filter(online).map( function (d) { var totalClients = sum(d.nodes.all.filter(online).map( function (d) {
return d.statistics.clients ? d.statistics.clients : 0 return d.statistics.clients
})) }))
var totalGateways = sum(Array.from(new Set(d.nodes.all.filter(online).map( function(d) { var totalGateways = sum(d.nodes.all.filter(online).filter( function (d) {
return ("gateway" in d.statistics && d.statistics.gateway.id) ? d.statistics.gateway.id : d.statistics.gateway
}).concat(d.nodes.all.filter( function (d) {
return d.flags.gateway return d.flags.gateway
})))).map(function(d) { }).map(one))
return (typeof d === "string") ? 1 : 0
}))
var nodetext = [{ count: totalOnlineNodes, label: "online" }, var nodetext = [{ count: totalOnlineNodes, label: "online" },
{ count: totalOfflineNodes, label: "offline" },
{ count: totalNewNodes, label: "neu" }, { count: totalNewNodes, label: "neu" },
{ count: totalLostNodes, label: "verschwunden" } { count: totalLostNodes, label: "verschwunden" }
].filter( function (d) { return d.count > 0 } ) ].filter( function (d) { return d.count > 0 } )

View file

@ -21,11 +21,7 @@ define(["sorttable", "virtual-dom", "numeral"], function (SortTable, V, numeral)
var headings = [{ name: "Knoten", var headings = [{ name: "Knoten",
sort: function (a, b) { sort: function (a, b) {
var aname = typeof a.nodeinfo.hostname === "string" ? a.nodeinfo.hostname : a.nodeinfo.node_id return a.nodeinfo.hostname.localeCompare(b.nodeinfo.hostname)
var bname = typeof b.nodeinfo.hostname === "string" ? b.nodeinfo.hostname : b.nodeinfo.node_id
if (typeof aname === "string" && typeof bname === "string")
return aname.localeCompare(bname)
return typeof aname === "string" ? 1 : typeof bname === "string" ? -1 : 0
}, },
reverse: false reverse: false
}, },
@ -56,7 +52,7 @@ define(["sorttable", "virtual-dom", "numeral"], function (SortTable, V, numeral)
td1Content.push(V.h("a", { className: aClass.join(" "), td1Content.push(V.h("a", { className: aClass.join(" "),
onclick: router.node(d), onclick: router.node(d),
href: "#!n:" + d.nodeinfo.node_id href: "#"
}, d.nodeinfo.hostname)) }, d.nodeinfo.hostname))
if (has_location(d)) if (has_location(d))

View file

@ -23,17 +23,43 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "verc
var uplinkTable = document.createElement("table") var uplinkTable = document.createElement("table")
uplinkTable.classList.add("proportion") uplinkTable.classList.add("proportion")
var gwNodesTable = document.createElement("table") var gwTable = document.createElement("table")
gwNodesTable.classList.add("proportion") gwTable.classList.add("proportion")
var gwClientsTable = document.createElement("table")
gwClientsTable.classList.add("proportion")
var siteTable = document.createElement("table") var siteTable = document.createElement("table")
siteTable.classList.add("proportion") siteTable.classList.add("proportion")
function showStatGlobal(o) { function showStatGlobal(o) {
return showStat(o) var content, caption
if (o.thumbnail) {
content = document.createElement("img")
content.src = o.thumbnail
}
if (o.caption) {
caption = o.caption
if (!content)
content = document.createTextNode(caption)
}
var p = document.createElement("p")
if (o.href) {
var link = document.createElement("a")
link.target = "_blank"
link.href = o.href
link.appendChild(content)
if (caption && o.thumbnail)
link.title = caption
p.appendChild(link)
} else
p.appendChild(content)
return p
} }
function count(nodes, key, f) { function count(nodes, key, f) {
@ -54,25 +80,6 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "verc
return Object.keys(dict).map(function (d) { return [d, dict[d], key, f] }) return Object.keys(dict).map(function (d) { return [d, dict[d], key, f] })
} }
function countClients(nodes, key, f) {
var dict = {}
nodes.forEach( function (d) {
var v = dictGet(d, key.slice(0))
if (f !== undefined)
v = f(v)
if (v === null)
return
dict[v] = d.statistics.clients + (v in dict ? dict[v] : 0)
})
return Object.keys(dict).map(function (d) { return [d, dict[d], key, f] })
}
function addFilter(filter) { function addFilter(filter) {
return function () { return function () {
filterManager.addFilter(filter) filterManager.addFilter(filter)
@ -128,17 +135,10 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "verc
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"])
if (d) {
d = d.replace(/\(r\)|\(tm\)/gi, "").replace(/AMD |Intel |TP-Link | CPU| Processor/g, "")
if (d.indexOf("@") > 0) d = d.substring(0, d.indexOf("@"))
}
return d
})
var geoDict = count(nodes, ["nodeinfo", "location"], function (d) { var geoDict = count(nodes, ["nodeinfo", "location"], function (d) {
return d && d.longitude && d.latitude ? "ja" : "nein" return d ? "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
@ -147,38 +147,21 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "verc
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 gwDict = count(nodes, ["statistics", "gateway"], function (d) {
if (d === null) if (d === null)
return null return null
if (d.node) if (d in nodeDict)
return d.node.nodeinfo.hostname return nodeDict[d].nodeinfo.hostname
if (d.id)
return d.id
return d return d
}) })
var gwClientsDict = countClients(onlineNodes, ["statistics", "gateway"], function (d) { var siteDict = count(onlineNodes, ["nodeinfo", "system", "site_code"], function (d) {
if (d === null)
return null
if (d.node)
return d.node.nodeinfo.hostname
if (d.id)
return d.id
return d
})
var siteDict = count(nodes, ["nodeinfo", "system", "site_code"], function (d) {
var rt = d var rt = d
if (config.siteNames) if (config.siteNames)
config.siteNames.forEach( function (t) { config.siteNames.forEach( function (t) {
@ -192,45 +175,71 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "verc
fillTable("Firmware", fwTable, fwDict.sort(function (a, b) { return vercomp(b[0], a[0]) })) 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("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("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("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("Uplink", uplinkTable, uplinkDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Gateway", gwClientsTable, gwClientsDict.sort(function (a, b) { return b[1] - a[1] })) fillTable("Gewähltes Gateway", gwTable, gwDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Site", siteTable, siteDict.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) h2 = document.createElement("h2")
self.renderSingle(el, "Nodes an Gateway", gwNodesTable) h2.textContent = "Status"
self.renderSingle(el, "Clients an Gateway", gwClientsTable) el.appendChild(h2)
self.renderSingle(el, "Firmwareversionen", fwTable) el.appendChild(statusTable)
self.renderSingle(el, "Uplink", uplinkTable)
self.renderSingle(el, "Hardwaremodelle", hwTable) h2 = document.createElement("h2")
self.renderSingle(el, "Auf der Karte sichtbar", geoTable) h2.textContent = "Firmwareversionen"
self.renderSingle(el, "Autoupdater", autoTable) el.appendChild(h2)
self.renderSingle(el, "Site", siteTable) el.appendChild(fwTable)
if(config.siteNames || config.showSites) {
h2 = document.createElement("h2")
h2.textContent = "Orte"
el.appendChild(h2)
el.appendChild(siteTable)
}
h2 = document.createElement("h2")
h2.textContent = "Hardwaremodelle"
el.appendChild(h2)
el.appendChild(hwTable)
h2 = document.createElement("h2")
h2.textContent = "Auf der Karte sichtbar"
el.appendChild(h2)
el.appendChild(geoTable)
h2 = document.createElement("h2")
h2.textContent = "Autoupdater"
el.appendChild(h2)
el.appendChild(autoTable)
h2 = document.createElement("h2")
h2.textContent = "Uplink"
el.appendChild(h2)
el.appendChild(uplinkTable)
h2 = document.createElement("h2")
h2.textContent = "Gewählter Gateway"
el.appendChild(h2)
el.appendChild(gwTable)
h2 = document.createElement("h2")
h2.textContent = "Site"
el.appendChild(h2)
el.appendChild(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) {
var h2
h2 = document.createElement("h2")
h2.textContent = heading
h2.onclick = function () {
table.classList.toggle("hidden")
}
el.appendChild(h2)
el.appendChild(table)
}
return self return self
} }
}) })

View file

@ -78,8 +78,6 @@ define(function () {
if (!s) if (!s)
return false return false
s = decodeURIComponent(s)
if (!s.startsWith("#!")) if (!s.startsWith("#!"))
return false return false
@ -96,7 +94,7 @@ define(function () {
var id var id
if (args[0] === "n") { if (args[0] === "n") {
id = args[1] id = decodeURIComponent(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])
@ -105,7 +103,7 @@ define(function () {
} }
if (args[0] === "l") { if (args[0] === "l") {
id = args[1] id = decodeURIComponent(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])

View file

@ -41,7 +41,7 @@ define(["moment", "virtual-dom"], function (moment, V) {
td1Content.push(V.h("a", { className: aClass.join(" "), td1Content.push(V.h("a", { className: aClass.join(" "),
onclick: router.node(d), onclick: router.node(d),
href: "#!n:" + d.nodeinfo.node_id href: "#"
}, d.nodeinfo.hostname)) }, d.nodeinfo.hostname))
if (has_location(d)) if (has_location(d))

View file

@ -20,7 +20,7 @@ define(function () {
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.nodeinfo.hostname + " " + d.target.node.nodeinfo.hostname)
} }
this.gotoLocation = function() { this.gotoLocation = function() {

View file

@ -1,41 +1,22 @@
{ {
"name": "hopglass", "name": "hopglass",
"version": "1.0.0",
"scripts": { "scripts": {
"test": "node -e \"require('grunt').cli()\" '' clean lint" "test": "node -e \"require('grunt').cli()\" '' clean lint"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^6.3.3", "grunt": "^0.4.5",
"dart-sass": "^1.16.1",
"grunt": "^1.0.3",
"grunt-check-dependencies": "^0.6.0", "grunt-check-dependencies": "^0.6.0",
"grunt-contrib-clean": "^0.6.0", "grunt-contrib-clean": "^0.6.0",
"grunt-contrib-connect": "^0.8.0", "grunt-contrib-connect": "^0.8.0",
"grunt-contrib-copy": "^0.5.0", "grunt-contrib-copy": "^0.5.0",
"grunt-contrib-cssmin": "^0.12.2", "grunt-contrib-cssmin": "^0.12.2",
"grunt-contrib-requirejs": "^0.4.4", "grunt-contrib-requirejs": "^0.4.4",
"grunt-contrib-sass": "^0.9.2",
"grunt-contrib-uglify": "^0.5.1", "grunt-contrib-uglify": "^0.5.1",
"grunt-contrib-watch": "^0.6.1", "grunt-contrib-watch": "^0.6.1",
"grunt-eslint": "^10.0.0", "grunt-eslint": "^10.0.0",
"grunt-git-describe": "^2.3.2", "grunt-bower-install-simple": "^1.1.2",
"grunt-postcss": "^0.7.2", "grunt-git-describe": "^2.3.2"
"grunt-sass": "^3.0.2"
},
"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": {
@ -45,7 +26,6 @@
"node": true "node": true
}, },
"globals": { "globals": {
"showStat": false,
"attributeEntry": false, "attributeEntry": false,
"dictGet": false, "dictGet": false,
"getJSON": false, "getJSON": false,

View file

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

View file

@ -2,7 +2,7 @@
margin: 0; margin: 0;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
font-family: Roboto, sans-serif; font-family: Roboto;
font-size: 0.83em; font-size: 0.83em;
font-weight: bold; font-weight: bold;
padding: 0 6pt 6pt !important; padding: 0 6pt 6pt !important;
@ -10,7 +10,7 @@
li { li {
border-radius: 20pt; border-radius: 20pt;
display: flex; display: flex;
padding: 0 0 0 8pt; padding: 0pt 0 0pt 8pt;
margin: 3pt; margin: 3pt;
align-items: center; align-items: center;
background: #009ee0; background: #009ee0;

View file

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

View file

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

View file

@ -1,4 +1,5 @@
.legend .symbol { .legend .symbol
{
width: 1em; width: 1em;
height: 1em; height: 1em;
border-radius: 50%; border-radius: 50%;
@ -6,18 +7,22 @@
vertical-align: -5%; vertical-align: -5%;
} }
.legend-new .symbol { .legend-new .symbol
{
background-color: #93E929; background-color: #93E929;
} }
.legend-online .symbol { .legend-online .symbol
{
background-color: #1566A9; background-color: #1566A9;
} }
.legend-offline .symbol { .legend-offline .symbol
{
background-color: #D43E2A; background-color: #D43E2A;
} }
.legend-online, .legend-offline { .legend-online, .legend-offline
{
margin-left: 1em; margin-left: 1em;
} }

View file

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

View file

@ -35,25 +35,13 @@
} }
@-webkit-keyframes blink { @-webkit-keyframes blink {
0% { 0% { opacity: 1.0; }
opacity: 1.0; 80% { opacity: 1.0; }
} 90% { opacity: 0.0; }
80% {
opacity: 1.0;
}
90% {
opacity: 0.0;
}
} }
@keyframes blink { @keyframes blink {
0% { 0% { opacity: 1.0; }
opacity: 1.0; 80% { opacity: 1.0; }
} 90% { opacity: 0.0; }
80% {
opacity: 1.0;
}
90% {
opacity: 0.0;
}
} }

View file

@ -22,31 +22,25 @@ time, mark, audio, video {
font: inherit; font: inherit;
vertical-align: baseline; vertical-align: baseline;
} }
/* HTML5 display-role reset for older browsers */ /* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section { footer, header, hgroup, menu, nav, section {
display: block; display: block;
} }
body { body {
line-height: 1; line-height: 1;
} }
ol, ul { ol, ul {
list-style: none; list-style: none;
} }
blockquote, q { blockquote, q {
quotes: none; quotes: none;
} }
blockquote:before, blockquote:after, blockquote:before, blockquote:after,
q:before, q:after { q:before, q:after {
content: ''; content: '';
content: none; content: none;
} }
table { table {
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; border-spacing: 0;

View file

@ -3,13 +3,17 @@
@mixin shadow($level:1){ @mixin shadow($level:1){
@if $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); box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
} @else if $level == 2 { }
@else if $level == 2 {
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
} @else if $level == 3 { }
@else if $level == 3 {
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
} @else if $level == 4 { }
@else if $level == 4 {
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22); box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
} @else if $level == 5 { }
@else if $level == 5 {
box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22); box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
} }
} }

View file

@ -1,36 +0,0 @@
.nodeheader {
width: 90%;
position: relative;
z-index: 2;
}
.nodeheader img {
width: 30%;
max-height: 180px;
margin: auto;
text-align: center;
max-width: 128px;
display: block;
z-index: 2;
}
.nodeheader img .none {
display: none;
}
.nodeheader span {
background-color: silver;
background-color: hsla(0, 0%, 100%, 0.5);
position: absolute;
bottom: 0;
width: 100%;
line-height: 1.5em;
text-align: center;
color: black;
font-weight: bold;
font-size: large;
z-index: 2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View file

@ -4,7 +4,6 @@
@import '_leaflet'; @import '_leaflet';
@import '_leaflet.label'; @import '_leaflet.label';
@import '_filters'; @import '_filters';
@import '_loader';
$minscreenwidth: 630pt; $minscreenwidth: 630pt;
$sidebarwidth: 420pt; $sidebarwidth: 420pt;
@ -45,7 +44,7 @@ $buttondistance: 12pt;
margin: 0; margin: 0;
list-style: none; list-style: none;
display: flex; display: flex;
font-family: Roboto, sans-serif; font-family: Roboto;
@include shadow(1); @include shadow(1);
} }
@ -70,9 +69,8 @@ $buttondistance: 12pt;
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: 'Roboto-Slab', serif; font-family: 'Roboto Slab', serif;
font-size: 11pt; font-size: 11pt;
color: #333;
} }
th.sort-header::selection { th.sort-header::selection {
@ -118,38 +116,6 @@ table.attributes td {
line-height: 1.41em; 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 { .sidebar {
.infobox, .container { .infobox, .container {
@include shadow(2); @include shadow(2);
@ -161,10 +127,6 @@ table.attributes tr.routerpic th {
display: none; display: none;
} }
.container table.hidden {
display: none;
}
p { p {
line-height: 1.67em; line-height: 1.67em;
} }
@ -177,13 +139,6 @@ table.attributes tr.routerpic th {
white-space: normal; white-space: normal;
} }
.infobox .clientsMesh {
font-family: "ionicons";
color: #dbdbdb;
word-spacing: -0.2em;
white-space: normal;
}
.infobox { .infobox {
position: relative; position: relative;
padding: 0.25em 0; padding: 0.25em 0;
@ -192,27 +147,6 @@ table.attributes tr.routerpic th {
img { img {
max-width: 100%; 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 { button {
@ -221,7 +155,6 @@ button {
@include shadow(1); @include shadow(1);
border-radius: 0.9em; border-radius: 0.9em;
background: rgba(255, 255, 255, 0.7); background: rgba(255, 255, 255, 0.7);
color: #333;
border: none; border: none;
cursor: pointer; cursor: pointer;
height: 1.8em; height: 1.8em;
@ -242,7 +175,7 @@ button:hover {
} }
button:active { button:active {
box-shadow: inset 0 5px 20px rgba(0, 0, 0, 0.19), inset 0 3px 6px rgba(0, 0, 0, 0.23); box-shadow: inset 0px 5px 20px rgba(0, 0, 0, 0.19), inset 0px 3px 6px rgba(0, 0, 0, 0.23);
} }
button::-moz-focus-inner { button::-moz-focus-inner {
@ -261,8 +194,6 @@ button.close {
border-radius: 0; border-radius: 0;
color: rgba(0, 0, 0, 0.5); color: rgba(0, 0, 0, 0.5);
font-family: "ionicons"; font-family: "ionicons";
position: absolute;
right: 0;
&:hover { &:hover {
color: #dc0067; color: #dc0067;
@ -353,10 +284,6 @@ table {
color: #D43E2A !important; color: #D43E2A !important;
} }
.unseen {
color: #D89100 !important;
}
.sidebar { .sidebar {
z-index: 5; z-index: 5;
width: $sidebarwidth; width: $sidebarwidth;
@ -364,7 +291,7 @@ table {
position: absolute; position: absolute;
top: $buttondistance; top: $buttondistance;
left: $buttondistance; left: $buttondistance;
padding-bottom: $buttondistance; margin-bottom: $buttondistance;
transition: left 0.5s; transition: left 0.5s;
} }
@ -449,9 +376,9 @@ table {
@media screen and (max-width: 80em) { @media screen and (max-width: 80em) {
.sidebar { .sidebar {
font-size: 0.8em; font-size: 0.8em;
top: 0; top: 0pt;
left: 0; left: 0pt;
margin: 0; margin: 0pt;
width: $sidebarwidthsmall; width: $sidebarwidthsmall;
min-height: 100vh; min-height: 100vh;
@include shadow(2); @include shadow(2);
@ -482,7 +409,7 @@ table {
.sidebar { .sidebar {
position: static; position: static;
margin: 0 !important; margin: 0em !important;
width: auto; width: auto;
height: auto; height: auto;
min-height: 0; min-height: 0;

View file

@ -1,6 +1,6 @@
module.exports = function(grunt) { module.exports = function(grunt) {
grunt.config.merge({ grunt.config.merge({
nodedir: "node_modules", bowerdir: "bower_components",
copy: { copy: {
html: { html: {
options: { options: {
@ -19,21 +19,26 @@ module.exports = function(grunt) {
dest: "build/" dest: "build/"
}, },
vendorjs: { vendorjs: {
src: ["es6-shim/es6-shim.min.js", src: [ "es6-shim/es6-shim.min.js" ],
"es6-shim/es6-shim.map"],
expand: true, expand: true,
cwd: "node_modules/", cwd: "bower_components/",
dest: "build/vendor/" dest: "build/vendor/"
}, },
roboto: { robotoSlab: {
src: [ "fonts/roboto/*", src: [ "fonts/*",
"fonts/roboto-slab/*", "roboto-slab-fontface.css"
"css/roboto/roboto-fontface.css",
"css/roboto-slab/roboto-slab-fontface.css"
], ],
expand: true, expand: true,
dest: "build/", dest: "build/",
cwd: "node_modules/roboto-fontface/" cwd: "bower_components/roboto-slab-fontface"
},
roboto: {
src: [ "fonts/*",
"roboto-fontface.css"
],
expand: true,
dest: "build/",
cwd: "bower_components/roboto-fontface"
}, },
ionicons: { ionicons: {
src: [ "fonts/*", src: [ "fonts/*",
@ -41,55 +46,53 @@ module.exports = function(grunt) {
], ],
expand: true, expand: true,
dest: "build/", dest: "build/",
cwd: "node_modules/ionicons/" cwd: "bower_components/ionicons/"
}, },
leafletImages: { leafletImages: {
src: [ "images/*" ], src: [ "images/*" ],
expand: true, expand: true,
dest: "build/", dest: "build/",
cwd: "node_modules/leaflet/dist/" cwd: "bower_components/leaflet/dist/"
} }
}, },
sass: { sass: {
options: {
sourceMap: true,
outputStyle: "compressed",
implementation: require("dart-sass")
},
dist: { dist: {
options: {
style: "compressed"
},
files: { files: {
"build/style.css": "scss/main.scss" "build/style.css": "scss/main.scss"
} }
} }
}, },
postcss: {
options: {
map: true,
processors: [
require("autoprefixer")({
browsers: ["last 2 versions"]
})
]
},
dist: {
src: "build/style.css"
}
},
cssmin: { cssmin: {
target: { target: {
files: { files: {
"build/style.css": [ "node_modules/leaflet/dist/leaflet.css", "build/style.css": [ "bower_components/leaflet/dist/leaflet.css",
"node_modules/leaflet-label/dist/leaflet.label.css", "bower_components/Leaflet.label/dist/leaflet.label.css",
"style.css" "style.css"
] ]
} }
} }
}, },
"bower-install-simple": {
options: {
directory: "<%=bowerdir%>",
color: true,
interactive: false,
production: true
},
"prod": {
options: {
production: true
}
}
},
requirejs: { requirejs: {
compile: { compile: {
options: { options: {
baseUrl: "lib", baseUrl: "lib",
name: "../node_modules/almond/almond", name: "../bower_components/almond/almond",
mainConfigFile: "app.js", mainConfigFile: "app.js",
include: "../app", include: "../app",
wrap: true, wrap: true,
@ -100,8 +103,8 @@ module.exports = function(grunt) {
} }
}) })
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-contrib-sass")
grunt.loadNpmTasks("grunt-postcss")
} }

View file

@ -4,6 +4,11 @@ module.exports = function (grunt) {
options: { options: {
install: true install: true
}, },
bower: {
options: {
packageManager: "bower"
}
},
npm: {} npm: {}
}, },
eslint: { eslint: {

3451
yarn.lock

File diff suppressed because it is too large Load diff