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
node_js:
- "lts/*"
- "node"
before_install:
- gem install sass
- npm install -g grunt-cli
install:
- yarn
script: node_modules/.bin/grunt
- npm install
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.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("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 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
@ -14,32 +14,31 @@ HopGlass is a frontend for the [HopGlass Server](https://github.com/hopglass/hop
# Dependencies
- NodeJS
- yarn (recommended) or npm
- npm
- bower
- grunt-cli
- Sass (>= 3.2)
# 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)"
brew install node
brew install yarn
On Arch Linux you can install nodejs and yarn via pacman:
sudo pacman -S nodejs yarn
sudo gem install sass
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
yarn install # or `npm install`
npm install
npm install grunt-cli
# Building
@ -55,7 +54,7 @@ Copy `config.json.example` to `build/config.json` and change it to match your co
## 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!
Also, proxying the data through a webserver will allow GZip and thus will greatly reduce bandwidth consumption.
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
[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)
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
- `href` absolute or relative URL to statistics image
@ -120,12 +103,12 @@ Examples for `nodeInfos`:
{ "name": "Clientstatistik",
"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}"
},
{ "name": "Uptime",
"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}"
}
]
@ -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)
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({
baseUrl: "lib",
paths: {
"leaflet": "../node_modules/leaflet/dist/leaflet",
"leaflet.label": "../node_modules/leaflet-label/dist/leaflet.label",
"leaflet.providers": "../node_modules/leaflet-providers/leaflet-providers",
"chroma-js": "../node_modules/chroma-js/chroma.min",
"moment": "../node_modules/moment/min/moment-with-locales.min",
"tablesort": "../node_modules/tablesort/tablesort.min",
"tablesort.numeric": "../node_modules/tablesort/src/sorts/tablesort.numeric",
"d3": "../node_modules/d3/d3.min",
"numeral": "../node_modules/numeraljs/min/numeral.min",
"numeral-intl": "../node_modules/numeraljs/min/languages.min",
"virtual-dom": "../node_modules/virtual-dom/dist/virtual-dom",
"rbush": "../node_modules/rbush/rbush",
"leaflet": "../bower_components/leaflet/dist/leaflet",
"leaflet.label": "../bower_components/Leaflet.label/dist/leaflet.label",
"leaflet.providers": "../bower_components/leaflet-providers/leaflet-providers",
"chroma-js": "../bower_components/chroma-js/chroma.min",
"moment": "../bower_components/moment/min/moment-with-locales.min",
"tablesort": "../bower_components/tablesort/tablesort.min",
"tablesort.numeric": "../bower_components/tablesort/src/sorts/tablesort.numeric",
"d3": "../bower_components/d3/d3.min",
"numeral": "../bower_components/numeraljs/min/numeral.min",
"numeral-intl": "../bower_components/numeraljs/min/languages.min",
"virtual-dom": "../bower_components/virtual-dom/dist/virtual-dom",
"rbush": "../bower_components/rbush/rbush",
"helper": "../helper",
"jshashes": "../node_modules/jshashes/hashes"
"jshashes": "../bower_components/jshashes/hashes"
},
shim: {
"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",
name: "../node_modules/almond/almond",
name: "../bower_components/almond/almond",
mainConfigFile: "app.js",
include: "../app",
wrap: true,

View file

@ -1,62 +1,27 @@
{
"dataPath": "https://map.ffdus.de/data/",
"siteName": "Freifunk Flingern",
"dataPath": "https://map.luebeck.freifunk.net/data/",
"siteName": "Freifunk Lübeck",
"mapSigmaScale": 0.5,
"showContact": true,
"showContact": true,
"maxAge": 14,
"mapLayers": [
{ "name": "CartoDB",
"url": "https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png",
{ "name": "MapQuest",
"url": "https://otile{s}-s.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg",
"config": {
"maxZoom": 18,
"attribution": "&copy; <a href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a>, &copy; | <a href=\"https://carto.com/attribution\">CARTO</a>"
"subdomains": "1234",
"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": "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}"
"name": "Stamen.TonerLite"
}
],
"siteNames": [
{ "site": "dus", "name": "Flingern" }
],
"hwImg": [
{ "thumbnail": "https://cdn.rawgit.com/Moorviper/meshviewer_hwpics/master/nodes/{MODELHASH}.svg",
"caption": "Knoten {MODELHASH}"
}
{ "site": "ffhl", "name": "Lübeck" },
{ "site": "ffeh", "name": "Entenhausen" },
{ "site": "ffgt", "name": "Gothamcity" },
{ "site": "ffal", "name": "Atlantis" }
]
}

108
helper.js
View file

@ -1,23 +1,23 @@
function get(url) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest()
req.open('GET', url)
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function() {
if (req.status == 200) {
resolve(req.response)
resolve(req.response);
}
else {
reject(Error(req.statusText))
reject(Error(req.statusText));
}
}
};
req.onerror = function() {
reject(Error("Network Error"))
}
reject(Error("Network Error"));
};
req.send()
})
req.send();
});
}
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 */
function offline(d) {
@ -130,13 +122,7 @@ function attributeEntry(el, label, value) {
var tr = document.createElement("tr")
var th = document.createElement("th")
if (typeof label === "string")
th.textContent = label
else {
th.appendChild(label)
tr.className = "routerpic"
}
th.textContent = label
tr.appendChild(th)
var td = document.createElement("td")
@ -152,77 +138,3 @@ function attributeEntry(el, label, value) {
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 name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" href="css/ionicons.min.css">
<link rel="stylesheet" href="css/roboto-slab/roboto-slab-fontface.css">
<link rel="stylesheet" href="css/roboto/roboto-fontface.css">
<link rel="stylesheet" href="roboto-slab-fontface.css">
<link rel="stylesheet" href="roboto-fontface.css">
<link rel="stylesheet" href="style.css">
<script src="vendor/es6-shim/es6-shim.min.js"></script>
<script src="app.js"></script>
@ -14,15 +14,5 @@
</script>
</head>
<body>
<div class="loader">
<p>
Lade<br />
<span class="spinner"></span><br />
Karte &amp; Knoten...
</p>
<noscript>
<strong>JavaScript required</strong>
</noscript>
</div>
</body>
</html>

View file

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

View file

@ -29,8 +29,8 @@ define(function () {
s += "https://www.gnu.org/licenses/</a>.</p>"
s += "<p>The source code is available at "
s += "<a href=\"https://github.com/hopglass/hopglass\">"
s += "https://github.com/hopglass/hopglass</a>."
s += "<a href=\"https://github.com/plumpudding/hopglass\">"
s += "https://github.com/plumpudding/hopglass</a>."
el.innerHTML = s
}

View file

@ -4,18 +4,18 @@ define([], function () {
var n = Object.create(data)
n.nodes = {}
for (var key in data.nodes)
for (var key in data.nodes) {
n.nodes[key] = data.nodes[key].filter(filter)
}
var filteredIds = new Set()
n.graph = {}
n.graph.nodes = data.graph.nodes.filter( function (d) {
var r
if (d.node)
r = filter(d.node)
else
r = filter({})
if (!d.node)
return true
var r = filter(d.node)
if (r)
filteredIds.add(d.id)

View file

@ -1,7 +1,7 @@
define(["d3"], function (d3) {
var margin = 200
var NODE_RADIUS = 15
var LINE_RADIUS = 7
var LINE_RADIUS = 12
return function (config, linkScale, sidebar, router) {
var self = this
@ -19,7 +19,6 @@ define(["d3"], function (d3) {
var nodes = []
var uplinkNodes = []
var nonUplinkNodes = []
var unseenNodes = []
var unknownNodes = []
var savedPanZoom
@ -214,7 +213,7 @@ define(["d3"], function (d3) {
function drawLabel(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) {
@ -242,8 +241,7 @@ define(["d3"], function (d3) {
}
function visibleLinks(d) {
return (d.o.isVPN ||
d.source.x > screenRect.left && d.source.x < screenRect.right &&
return (d.source.x > screenRect.left && d.source.x < screenRect.right &&
d.source.y > screenRect.top && d.source.y < screenRect.bottom) ||
(d.target.x > screenRect.left && d.target.x < screenRect.right &&
d.target.y > screenRect.top && d.target.y < screenRect.bottom)
@ -315,7 +313,6 @@ define(["d3"], function (d3) {
var unknownColor = "#D10E2A"
var nonUplinkColor = "#F2E3C6"
var uplinkColor = "#5BAAEB"
var unseenColor = "#FFA726"
var highlightColor = "rgba(252, 227, 198, 0.15)"
var nodeRadius = 6
var cableColor = "#50B0F0"
@ -325,19 +322,16 @@ define(["d3"], function (d3) {
links.forEach(function (d) {
var dx = d.target.x - d.source.x
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
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.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.globalAlpha = d.o.isVPN ? 0.1 : 0.8
ctx.lineWidth = d.o.isVPN ? 1.5 : 2.5
ctx.globalAlpha = d.o.type === "VPN" ? 0.1 : 0.8
ctx.lineWidth = d.o.type === "VPN" ? 1.5 : 2.5
ctx.stroke()
})
@ -370,42 +364,36 @@ define(["d3"], function (d3) {
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()
// -- draw clients --
ctx.save()
ctx.beginPath()
if (scale > 0.9)
nodes.filter(visibleNodes).forEach(function (d) {
var clients = d.o.node.statistics.clients
if (clients === 0)
return
nodes.filter(visibleNodes).forEach(function (d) {
var clients = d.o.node.statistics.clients
if (clients === 0)
return
var startDistance = 16
var radius = 3
var a = 1.2
var startAngle = Math.PI
var startDistance = 16
var radius = 3
var a = 1.2
var startAngle = Math.PI
for (var orbit = 0, i = 0; i < clients; orbit++) {
var distance = startDistance + orbit * 2 * radius * a
var n = Math.floor((Math.PI * distance) / (a * radius))
var delta = clients - i
for (var orbit = 0, i = 0; i < clients; orbit++) {
var distance = startDistance + orbit * 2 * radius * a
var n = Math.floor((Math.PI * distance) / (a * radius))
var delta = clients - i
for (var j = 0; j < Math.min(delta, n); i++, j++) {
var angle = 2 * Math.PI / n * j
var x = d.x + distance * Math.cos(angle + startAngle)
var y = d.y + distance * Math.sin(angle + startAngle)
for (var j = 0; j < Math.min(delta, n); i++, j++) {
var angle = 2 * Math.PI / n * j
var x = d.x + distance * Math.cos(angle + startAngle)
var y = d.y + distance * Math.sin(angle + startAngle)
ctx.moveTo(x, y)
ctx.arc(x, y, radius, 0, 2 * Math.PI)
}
ctx.moveTo(x, y)
ctx.arc(x, y, radius, 0, 2 * Math.PI)
}
})
}
})
ctx.fillStyle = clientColor
ctx.fill()
@ -475,34 +463,32 @@ define(["d3"], function (d3) {
requestAnimationFrame(redraw)
}
function distance(ax, ay, bx, by) {
return Math.pow(ax - bx, 2) + Math.pow(ay - by, 2)
function distance(a, b) {
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)
}
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) {
/* http://stackoverflow.com/questions/849211 */
var bx = b.x - ((b.x - a.x) / 2)
var by = b.y - ((b.y - a.y) / 2)
var l2 = distance(a.x, a.y, bx, by)
var l2 = distance(a, b)
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)
return distance(p.x, p.y, a.x, a.y)
return distance(p, a)
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) {
@ -530,7 +516,7 @@ define(["d3"], function (d3) {
}
var links = intLinks.filter(function (d) {
return !d.o.isVPN
return d.o.type !== "VPN"
}).filter(function (d) {
return distanceLink(e, d.source, d.target) < LINE_RADIUS
})
@ -591,13 +577,13 @@ define(["d3"], function (d3) {
.charge(-250)
.gravity(0.1)
.linkDistance(function (d) {
if (d.o.isVPN)
if (d.o.type === "VPN")
return 0
else
return LINK_DISTANCE
})
.linkStrength(function (d) {
if (d.o.isVPN)
if (d.o.type === "VPN")
return 0
else
return Math.max(0.5, 1 / d.o.tq)
@ -651,7 +637,7 @@ define(["d3"], function (d3) {
e.source = newNodesDict[d.source.id]
e.target = newNodesDict[d.target.id]
if (d.isVPN)
if (d.type === "VPN")
e.color = "rgba(255, 255, 255, " + (0.6 / d.tq) + ")"
else
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.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
})
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) {
d.neighbours = Object.keys(d.neighbours).map(function (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 })
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 })
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.classList.add("content")
document.body.appendChild(contentDiv)
@ -60,7 +57,7 @@ function (chroma, Map, Sidebar, Tabs, Container, Meshstats, Legend, Linklist,
contentDiv.appendChild(buttons)
var buttonToggle = document.createElement("button")
buttonToggle.textContent = "\uF133"
buttonToggle.textContent = ""
buttonToggle.onclick = function () {
if (content.constructor === Map)
router.view("g")

View file

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

View file

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

View file

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

View file

@ -38,11 +38,8 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
function showStatus(d) {
return function (el) {
el.classList.add(d.flags.unseen ? "unseen" : (d.flags.online ? "online" : "offline"))
if (d.flags.online)
el.textContent = "online, letzte Nachricht " + d.lastseen.fromNow() + " (" + d.lastseen.format("DD.MM.YYYY, H:mm:ss") + ")"
else
el.textContent = "offline, letzte Nachricht " + d.lastseen.fromNow() + " (" + d.lastseen.format("DD.MM.YYYY, H:mm:ss") + ")"
el.classList.add(d.flags.online ? "online" : "offline")
el.textContent = (d.flags.online ? "online " : "offline, " + d.lastseen.fromNow(true)) + " (Stand " + d.lastseen.format("DD.MM.YYYY, H:mm:ss)")
}
}
@ -81,124 +78,18 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
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) {
if (!d.flags.online)
return undefined
var meshclients = getMeshClients(d)
resetMeshClients(d)
var before = " ("
var after = " in der lokalen Wolke)"
return function (el) {
el.appendChild(document.createTextNode(d.statistics.clients > 0 ? d.statistics.clients : "keine"))
el.appendChild(document.createTextNode(before))
el.appendChild(document.createTextNode(meshclients > 0 ? meshclients : "keine"))
el.appendChild(document.createTextNode(after))
el.appendChild(document.createElement("br"))
var span = document.createElement("span")
span.classList.add("clients")
span.textContent = " ".repeat(d.statistics.clients)
el.appendChild(span)
var spanmesh = document.createElement("span")
spanmesh.classList.add("clientsMesh")
spanmesh.textContent = " ".repeat(meshclients - d.statistics.clients)
el.appendChild(spanmesh)
}
}
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,10 +109,7 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
if (link) {
var a = document.createElement("a")
if (ip.includes("."))
a.href = "http://" + ip + "/"
else
a.href = "http://[" + ip + "]/"
a.href = "http://[" + ip + "]/"
a.textContent = ip
el.appendChild(a)
} else
@ -265,7 +153,7 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
}
var label = document.createElement("label")
label.textContent = +(Math.round(v + "e+2") + "e-2")
label.textContent = (v)
span.appendChild(label)
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) {
var webpages = dictGet(d.nodeinfo, ["pages"])
if (webpages === null)
@ -387,69 +216,47 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
return au.enabled ? "aktiviert (" + au.branch + ")" : "deaktiviert"
}
function showNodeImg(o, model) {
if (!model)
return document.createTextNode("Knotenname")
function showStatImg(o, nodeId) {
var content, caption
var modelhash = model.split("").reduce(function(a, b) {
a = ((a << 5) - a) + b.charCodeAt(0)
return a & a
}, 0)
content = document.createElement("img")
content.id = "routerpicture"
content.classList.add("nodeImg")
content.src = o.thumbnail.replace("{MODELHASH}", modelhash)
content.onerror = function() {
document.getElementById("routerpicdiv").outerHTML = "Knotenname"
if (o.thumbnail) {
content = document.createElement("img")
content.src = o.thumbnail.replace("{NODE_ID}", nodeId)
}
if (o.caption) {
caption = o.caption.replace("{MODELHASH}", modelhash)
caption = o.caption.replace("{NODE_ID}", nodeId)
if (!content)
content = document.createTextNode(caption)
}
var p = document.createElement("p")
p.appendChild(content)
return content
}
if (o.href) {
var link = document.createElement("a")
link.target = "_blank"
link.href = o.href.replace("{NODE_ID}", nodeId)
link.appendChild(content)
function showStatImg(o, d) {
var subst = {}
subst["{NODE_ID}"] = d.nodeinfo.node_id ? d.nodeinfo.node_id : "unknown"
subst["{NODE_NAME}"] = d.nodeinfo.hostname ? d.nodeinfo.hostname : "unknown"
return showStat(o, subst)
if (caption && o.thumbnail)
link.title = caption
p.appendChild(link)
} else
p.appendChild(content)
return p
}
return function(config, el, router, d) {
var h2 = document.createElement("h2")
h2.textContent = d.nodeinfo.hostname
el.appendChild(h2)
var attributes = document.createElement("table")
attributes.classList.add("attributes")
if (config.hwImg) {
var top = document.createElement("div")
top.id = "routerpicdiv"
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")
h2.textContent = d.nodeinfo.hostname
el.appendChild(h2)
}
attributeEntry(attributes, "Status", showStatus(d))
attributeEntry(attributes, "Gateway", d.flags.gateway ? "ja" : null)
attributeEntry(attributes, "Koordinaten", showGeoURI(d))
@ -464,17 +271,13 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
attributeEntry(attributes, "Site", showSite(d, config))
attributeEntry(attributes, "Uptime", showUptime(d))
attributeEntry(attributes, "Teil des Netzes", showFirstseen(d))
attributeEntry(attributes, "Kanal 2.4 GHz", showWifiChannel(dictGet(d.nodeinfo, ["wireless", "chan2"])))
attributeEntry(attributes, "Kanal 5 GHz", showWifiChannel(dictGet(d.nodeinfo, ["wireless", "chan5"])))
attributeEntry(attributes, "Airtime 2.4 GHz", showAirtime(2, dictGet(d.statistics, ["wireless", "airtime2"])))
attributeEntry(attributes, "Airtime 5 GHz", showAirtime(5, dictGet(d.statistics, ["wireless", "airtime5"])))
attributeEntry(attributes, "Systemlast", showLoad(d))
attributeEntry(attributes, "Arbeitsspeicher", showRAM(d))
attributeEntry(attributes, "IP Adressen", showIPs(d))
attributeEntry(attributes, "Webseite", showPages(d))
attributeEntry(attributes, "Gewähltes Gateway", showGateway(d, router))
attributeEntry(attributes, "Gewähltes Gateway", dictGet(d.statistics, ["gateway"]))
attributeEntry(attributes, "Autom. Updates", showAutoupdate(d))
attributeEntry(attributes, "Clients", showClients(d), showMeshClients(d))
attributeEntry(attributes, "Clients", showClients(d))
el.appendChild(attributes)
@ -484,7 +287,7 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
var h4 = document.createElement("h4")
h4.textContent = nodeInfo.name
el.appendChild(h4)
el.appendChild(showStatImg(nodeInfo, d))
el.appendChild(showStatImg(nodeInfo, d.nodeinfo.node_id))
})
if (d.neighbours.length > 0) {
@ -523,7 +326,6 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
var tbody = document.createElement("tbody")
d.neighbours.forEach( function (d) {
var unknown = !(d.node)
var tr = document.createElement("tr")
var td1 = document.createElement("td")
@ -531,9 +333,14 @@ define(["moment", "numeral", "tablesort", "tablesort.numeric"],
tr.appendChild(td1)
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")
span.classList.add("icon")
span.classList.add("ion-location")

View file

@ -1,6 +1,6 @@
define(["sorttable", "virtual-dom"], function (SortTable, V) {
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",

View file

@ -4,14 +4,12 @@ function (moment, Router, L, GUI, numeral) {
function handleData(data) {
var dataNodes = {}
dataNodes.nodes = []
dataNodes.nodeIds = []
var dataGraph = {}
dataGraph.batadv = {}
dataGraph.batadv.nodes = []
dataGraph.batadv.links = []
function rearrangeLinks(d) {
d.source += dataGraph.batadv.nodes.length
d.target += dataGraph.batadv.nodes.length
@ -34,22 +32,11 @@ function (moment, Router, L, GUI, numeral) {
vererr = "Unsupported nodes version: " + data[i].version
console.log(vererr) //silent fail
} else {
data[i].nodes.forEach(fillData)
dataNodes.nodes = dataNodes.nodes.concat(data[i].nodes)
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) {
return "firstseen" in d && "lastseen" in d
})
@ -74,17 +61,15 @@ function (moment, Router, L, GUI, numeral) {
var graph = dataGraph.batadv
graph.nodes.forEach( function (d) {
if (d.node_id in graphnodes) {
if (d.node_id in graphnodes)
d.node = graphnodes[d.node_id]
if (d.unseen) {
d.node.flags.online = true
d.node.flags.unseen = true
}
}
})
graph.links.forEach( function (d) {
d.source = graph.nodes[d.source]
if (graph.nodes[d.source].node)
d.source = graph.nodes[d.source]
else
d.source = undefined
if (graph.nodes[d.target].node)
d.target = graph.nodes[d.target]
@ -93,25 +78,14 @@ function (moment, Router, L, GUI, numeral) {
})
var links = graph.links.filter( function (d) {
return d.target !== undefined
return d.source !== undefined && d.target !== undefined
})
links.forEach( function (d) {
var unknown = (d.source.node === undefined)
var ids
if (unknown)
ids = [d.source.id.replace(/:/g, ""), d.target.node.nodeinfo.node_id]
else
ids = [d.source.node.nodeinfo.node_id, d.target.node.nodeinfo.node_id]
d.id = ids.join("-")
var ids = [d.source.node.nodeinfo.node_id, d.target.node.nodeinfo.node_id]
d.id = ids.sort().join("-")
if (unknown ||
!d.source.node.nodeinfo.location ||
!d.target.node.nodeinfo.location ||
isNaN(d.source.node.nodeinfo.location.latitude) ||
isNaN(d.source.node.nodeinfo.location.longitude) ||
isNaN(d.target.node.nodeinfo.location.latitude) ||
isNaN(d.target.node.nodeinfo.location.longitude))
if (!("location" in d.source.node.nodeinfo && "location" in d.target.node.nodeinfo))
return
d.latlngs = []
@ -123,53 +97,21 @@ function (moment, Router, L, GUI, numeral) {
nodes.forEach( function (d) {
d.neighbours = []
if (d.statistics) {
/*eslint camelcase:0*/
if ("gateway" in d.statistics && d.statistics.gateway in graphnodes)
d.statistics.gateway = {"node": graphnodes[d.statistics.gateway], "id": d.statistics.gateway}
if ("nexthop" in d.statistics && d.statistics.nexthop in graphnodes)
d.statistics.nexthop = {"node": graphnodes[d.statistics.nexthop], "id": d.statistics.nexthop}
if ("gateway_nexthop" in d.statistics && d.statistics.gateway_nexthop in graphnodes)
d.statistics.gateway_nexthop = {"node": graphnodes[d.statistics.gateway_nexthop], "id": d.statistics.gateway_nexthop}
}
})
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.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
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) {

View file

@ -18,7 +18,7 @@ define(["map/clientlayer", "map/labelslayer",
onAdd: function () {
var button = L.DomUtil.create("button", "add-layer")
button.textContent = "\uF2C7"
button.textContent = ""
// L.DomEvent.disableClickPropagation(button)
// Click propagation isn't disabled as this causes problems with the
@ -46,7 +46,7 @@ define(["map/clientlayer", "map/labelslayer",
onAdd: function () {
var button = L.DomUtil.create("button", "locate-user")
button.textContent = "\uF2E9"
button.textContent = ""
L.DomEvent.disableClickPropagation(button)
L.DomEvent.addListener(button, "click", this.onClick, this)
@ -85,7 +85,7 @@ define(["map/clientlayer", "map/labelslayer",
onAdd: function () {
var button = L.DomUtil.create("button", "coord-picker")
button.textContent = "\uF2A6"
button.textContent = ""
// Click propagation isn't disabled as this causes problems with the
// location picking mode; instead propagation is stopped in onClick().
@ -172,7 +172,7 @@ define(["map/clientlayer", "map/labelslayer",
var map, userLocation
var layerControl
var customLayers = {}
var customLayers = new Set()
var baseLayers = {}
var locateUserButton = new LocateButton(function (d) {
@ -259,22 +259,23 @@ define(["map/clientlayer", "map/labelslayer",
if (layerName in baseLayers)
return
if (layerName in customLayers)
if (customLayers.has(layerName))
return
try {
var layer = L.tileLayer.provider(layerName)
layerControl.addBaseLayer(layer, layerName)
customLayers[layerName] = layer
customLayers.add(layerName)
if (localStorageTest())
localStorage.setItem("map/customLayers", JSON.stringify(Object.keys(customLayers)))
localStorage.setItem("map/customLayers", JSON.stringify(Array.from(customLayers)))
} catch (e) {
return
}
}
function contextMenuGotoLocation(e) {
console.log("context menu called at ", e)
router.gotoLocation(e.latlng)
}
@ -286,7 +287,7 @@ define(["map/clientlayer", "map/labelslayer",
var layers = config.mapLayers.map( function (d) {
return {
"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)
d.forEach(addLayer)
d = JSON.parse(localStorage.getItem("map/selectedLayer"))
d = d && d.name in baseLayers ? baseLayers[d.name] : d && d.name in customLayers ? customLayers[d.name] : false
if (d) {
map.removeLayer(layers[0].layer)
map.addLayer(d)
}
}
var clientLayer = new ClientLayer({minZoom: 15})
clientLayer.addTo(map)
clientLayer.setZIndex(5)
var labelsLayer = new LabelsLayer({})
var labelsLayer = new LabelsLayer()
labelsLayer.addTo(map)
labelsLayer.setZIndex(6)
map.on("baselayerchange", function(e) {
map.options.maxZoom = e.layer.options.maxZoom
clientLayer.options.maxZoom = map.options.maxZoom
labelsLayer.options.maxZoom = map.options.maxZoom
if (map.getZoom() > map.options.maxZoom) map.setZoom(map.options.maxZoom)
if (localStorageTest())
localStorage.setItem("map/selectedLayer", JSON.stringify({name: e.name}))
})
var nodeDict = {}
var linkDict = {}
var highlight
@ -464,10 +448,7 @@ define(["map/clientlayer", "map/labelslayer",
var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router)
groupLines = L.featureGroup(lines).addTo(map)
if (typeof config.fixedCenter === "undefined")
barycenter = calcBarycenter(data.nodes.all.filter(has_location))
else
barycenter = L.circle(L.latLng(new L.LatLng(config.fixedCenter.lat, config.fixedCenter.lng)), config.fixedCenter.radius * 1000)
barycenter = calcBarycenter(data.nodes.all.filter(has_location))
var nodesOnline = subtract(data.nodes.all.filter(online), data.nodes.new)
var nodesOffline = subtract(data.nodes.all.filter(offline), data.nodes.lost)

View file

@ -1,12 +1,15 @@
define(["leaflet"],
function (L) {
define(["leaflet", "jshashes"],
function (L, jsHashes) {
var MD5 = new jsHashes.MD5()
return L.TileLayer.Canvas.extend({
setData: function (d) {
this.data = d
//pre-calculate start angles
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()
},

View file

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

View file

@ -21,11 +21,7 @@ define(["sorttable", "virtual-dom", "numeral"], function (SortTable, V, numeral)
var headings = [{ name: "Knoten",
sort: function (a, b) {
var aname = typeof a.nodeinfo.hostname === "string" ? a.nodeinfo.hostname : a.nodeinfo.node_id
var bname = typeof b.nodeinfo.hostname === "string" ? b.nodeinfo.hostname : b.nodeinfo.node_id
if (typeof aname === "string" && typeof bname === "string")
return aname.localeCompare(bname)
return typeof aname === "string" ? 1 : typeof bname === "string" ? -1 : 0
return a.nodeinfo.hostname.localeCompare(b.nodeinfo.hostname)
},
reverse: false
},
@ -56,7 +52,7 @@ define(["sorttable", "virtual-dom", "numeral"], function (SortTable, V, numeral)
td1Content.push(V.h("a", { className: aClass.join(" "),
onclick: router.node(d),
href: "#!n:" + d.nodeinfo.node_id
href: "#"
}, d.nodeinfo.hostname))
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")
uplinkTable.classList.add("proportion")
var gwNodesTable = document.createElement("table")
gwNodesTable.classList.add("proportion")
var gwClientsTable = document.createElement("table")
gwClientsTable.classList.add("proportion")
var gwTable = document.createElement("table")
gwTable.classList.add("proportion")
var siteTable = document.createElement("table")
siteTable.classList.add("proportion")
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) {
@ -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] })
}
function countClients(nodes, key, f) {
var dict = {}
nodes.forEach( function (d) {
var v = dictGet(d, key.slice(0))
if (f !== undefined)
v = f(v)
if (v === null)
return
dict[v] = d.statistics.clients + (v in dict ? dict[v] : 0)
})
return Object.keys(dict).map(function (d) { return [d, dict[d], key, f] })
}
function addFilter(filter) {
return function () {
filterManager.addFilter(filter)
@ -128,17 +135,10 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "verc
return d ? "online" : "offline"
})
var fwDict = count(nodes, ["nodeinfo", "software", "firmware", "release"])
var hwDict = count(nodes, ["nodeinfo", "hardware", "model"], function (d) {
if (d) {
d = d.replace(/\(r\)|\(tm\)/gi, "").replace(/AMD |Intel |TP-Link | CPU| Processor/g, "")
if (d.indexOf("@") > 0) d = d.substring(0, d.indexOf("@"))
}
return d
})
var hwDict = count(nodes, ["nodeinfo", "hardware", "model"])
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) {
if (d === null)
return null
@ -147,38 +147,21 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "verc
else
return "(deaktiviert)"
})
var uplinkDict = count(nodes, ["flags", "uplink"], function (d) {
return d ? "ja" : "nein"
})
var gwNodesDict = count(onlineNodes, ["statistics", "gateway"], function (d) {
var gwDict = count(nodes, ["statistics", "gateway"], function (d) {
if (d === null)
return null
if (d.node)
return d.node.nodeinfo.hostname
if (d.id)
return d.id
if (d in nodeDict)
return nodeDict[d].nodeinfo.hostname
return d
})
var gwClientsDict = countClients(onlineNodes, ["statistics", "gateway"], function (d) {
if (d === null)
return null
if (d.node)
return d.node.nodeinfo.hostname
if (d.id)
return d.id
return d
})
var siteDict = count(nodes, ["nodeinfo", "system", "site_code"], function (d) {
var siteDict = count(onlineNodes, ["nodeinfo", "system", "site_code"], function (d) {
var rt = d
if (config.siteNames)
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("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("Uplink", uplinkTable, uplinkDict.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] }))
}
self.render = function (el) {
var h2
self.renderSingle(el, "Status", statusTable)
self.renderSingle(el, "Nodes an Gateway", gwNodesTable)
self.renderSingle(el, "Clients an Gateway", gwClientsTable)
self.renderSingle(el, "Firmwareversionen", fwTable)
self.renderSingle(el, "Uplink", uplinkTable)
self.renderSingle(el, "Hardwaremodelle", hwTable)
self.renderSingle(el, "Auf der Karte sichtbar", geoTable)
self.renderSingle(el, "Autoupdater", autoTable)
self.renderSingle(el, "Site", siteTable)
h2 = document.createElement("h2")
h2.textContent = "Status"
el.appendChild(h2)
el.appendChild(statusTable)
if (config.globalInfos)
config.globalInfos.forEach(function (globalInfo) {
h2 = document.createElement("h2")
h2.textContent = globalInfo.name
el.appendChild(h2)
el.appendChild(showStatGlobal(globalInfo))
})
h2 = document.createElement("h2")
h2.textContent = "Firmwareversionen"
el.appendChild(h2)
el.appendChild(fwTable)
if(config.siteNames || config.showSites) {
h2 = document.createElement("h2")
h2.textContent = "Orte"
el.appendChild(h2)
el.appendChild(siteTable)
}
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
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)
config.globalInfos.forEach( function (globalInfo) {
h2 = document.createElement("h2")
h2.textContent = globalInfo.name
el.appendChild(h2)
el.appendChild(showStatGlobal(globalInfo))
})
}
return self
}
})

View file

@ -78,8 +78,6 @@ define(function () {
if (!s)
return false
s = decodeURIComponent(s)
if (!s.startsWith("#!"))
return false
@ -96,7 +94,7 @@ define(function () {
var id
if (args[0] === "n") {
id = args[1]
id = decodeURIComponent(args[1])
if (id in objects.nodes) {
currentObject = { node: objects.nodes[id] }
gotoNode(objects.nodes[id])
@ -105,7 +103,7 @@ define(function () {
}
if (args[0] === "l") {
id = args[1]
id = decodeURIComponent(args[1])
if (id in objects.links) {
currentObject = { link: 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(" "),
onclick: router.node(d),
href: "#!n:" + d.nodeinfo.node_id
href: "#"
}, d.nodeinfo.hostname))
if (has_location(d))

View file

@ -20,7 +20,7 @@ define(function () {
this.gotoLink = function (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() {

View file

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

View file

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

View file

@ -2,15 +2,15 @@
margin: 0;
display: flex;
flex-wrap: wrap;
font-family: Roboto, sans-serif;
font-family: Roboto;
font-size: 0.83em;
font-weight: bold;
padding: 0 6pt 6pt !important;
li {
li {
border-radius: 20pt;
display: flex;
padding: 0 0 0 8pt;
padding: 0pt 0 0pt 8pt;
margin: 3pt;
align-items: center;
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,23 +1,28 @@
.legend .symbol {
width: 1em;
height: 1em;
border-radius: 50%;
display: inline-block;
vertical-align: -5%;
.legend .symbol
{
width: 1em;
height: 1em;
border-radius: 50%;
display: inline-block;
vertical-align: -5%;
}
.legend-new .symbol {
background-color: #93E929;
.legend-new .symbol
{
background-color: #93E929;
}
.legend-online .symbol {
background-color: #1566A9;
.legend-online .symbol
{
background-color: #1566A9;
}
.legend-offline .symbol {
background-color: #D43E2A;
.legend-offline .symbol
{
background-color: #D43E2A;
}
.legend-online, .legend-offline {
margin-left: 1em;
.legend-online, .legend-offline
{
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

@ -10,7 +10,7 @@
width: 100%;
height: 100%;
.node-alert {
.node-alert {
-webkit-animation: blink 2s linear;
-webkit-animation-iteration-count: infinite;
animation: blink 2s linear;
@ -35,25 +35,13 @@
}
@-webkit-keyframes blink {
0% {
opacity: 1.0;
}
80% {
opacity: 1.0;
}
90% {
opacity: 0.0;
}
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;
}
0% { opacity: 1.0; }
80% { opacity: 1.0; }
90% { opacity: 0.0; }
}

View file

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

View file

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

View file

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

View file

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

3451
yarn.lock

File diff suppressed because it is too large Load diff