Compare commits

..

66 commits

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

Update main.scss

Update _base.scss

Update gui.js

Update index.html

removed trailing spaces

removed trailing white spaces for real
2017-03-16 19:54:57 +01:00
Milan Paessler b2c2627d73 infobox: fix error when gateway could not be resolved 2017-02-14 21:33:36 +01:00
kb-light a7756c44e8 infobox/link: linkinfos {SOURCE_NAME} and {TARGET_NAME} support (#82) 2017-02-11 01:21:20 +01:00
Daniel Krah 5f98da2717 Update README.md (#86)
change installation of sass on mac.
Version via gem is outdated
2017-02-11 01:20:04 +01:00
PetaByteBoy // Milan Pässler 1a6a4329b5 forcegraph: only draw clients when zoomed in 2017-02-08 09:35:26 +01:00
vsandre ccc5f0f526 nodelist: Add node id to the href of node name in the table (#84)
So it is possible to open multiple node pages in a new window/tab
2017-02-03 23:02:20 +01:00
Marvin W 5cb88e8d06 Use server side resolving only, add proper support for nexthop resolving (#81)
* Revert "Use resolved data if gateway was already resolved (#78)"

This reverts commit 44bb8e9d3d.

* Revert "proportions: lookup gateway name"

This reverts commit 94662cb3dc.

* Revert "infobox/node: change mac used to lookup nodes"

This reverts commit 2ca2604403.

* Revert "infobox/node: resolve nexthop and gateway from data"

This reverts commit 9e7049c9e3.

* Resolve nexthop full chain if possible

requires hopglass/hopglass-server#79

* Correctly handle sidecase when nodes on route report inconsistent gateway
2017-01-07 18:43:04 +01:00
Marvin W 44bb8e9d3d Use resolved data if gateway was already resolved (#78)
This is now very dirty, I think the old approach was better (although it requires a server side change)
2016-12-10 11:45:21 +01:00
Jonas Platte 7e6d054e98 scss: Fix a few CSS problems (#73)
* Set text color on body

* Fix not being able to scroll more then sidebar height in Firefox

* Fix button color being set by browser stylesheet instead of site stylesheet
2016-11-26 00:10:27 +01:00
Milan Pssler 94662cb3dc proportions: lookup gateway name 2016-11-19 11:01:25 +01:00
Milan Pssler 2ca2604403 infobox/node: change mac used to lookup nodes 2016-11-19 10:57:24 +01:00
Milan Paessler 0171ebe7e1 infobox/node: non-clickable mac adresses should be black 2016-11-19 02:27:21 +01:00
Milan Paessler 9e7049c9e3 infobox/node: resolve nexthop and gateway from data
not perfect yet: the mac address doesn't always match
2016-11-19 02:17:08 +01:00
Milan Paessler 7d145141c1 infobox/node: handle unknown model when showing router img 2016-11-19 02:05:28 +01:00
Milan Pssler 5a5ce1d346 forcegraph: decrease line radius to increase click accuracy 2016-11-17 23:06:30 +01:00
Milan Pssler 224240c1c4 forcegraph: bidirectional links 2016-11-17 19:47:56 +01:00
Milan Pssler 7eb0675be0 fix meshclients for orange nodes 2016-11-06 17:13:22 +01:00
Milan Pssler 1641bc2437 router pics hopglass changes
- don't use display block on the table, instead make the close button
  position absolute
- show the old style heading when no source is defined
- clean up the indention and some small things
2016-11-06 00:11:49 +01:00
Moorviper 84aee48229 Router pics (#37)
* add a padding to sidebar for better use on mobile devices

* allow other things as text in attributeEntry

Now it is possible to use pictures and other stuff in this function

* Show Router-Pic with double fallback

3 Möglichkeiten
1. in config wird externe Quelle angegeben
2. wenn nicht wird in ./nodes/ geseucht
3. ansonsten Knotenname + Knotenname in erster Zeile

extern via:
cdn

"hwImg": [
  { "thumbnail": "https://cdn.rawgit.com/Moorviper/meshviewer_hwpics/master/nodes/{MODELHASH}.svg",
    "caption": "Knoten {MODELHASH}"
  }
 ]

aktueller nicht cdn:
"hwImg": [
  { "thumbnail": "https://rawgit.com/Moorviper/meshviewer_hwpics/master/nodes/{MODELHASH}.svg",
    "caption": "Knoten {MODELHASH}"
  }
 ]
2016-11-05 23:03:46 +01:00
Milan Pssler e9711c6efc new example config 2016-11-05 23:01:25 +01:00
Milan Pssler 5599be5cd6 re-add uplink detection, this time generic
fixes l2tp uplink detection
2016-11-05 21:54:47 +01:00
Daniel Krah 808b8c1986 Count Clients in a Mesh (#61)
This functions count all clients in a meshcloud.

It count over wireless and cable links not over VPN links via l2tp or fastd.
2016-11-05 21:24:38 +01:00
Daniel Krah 00a8e5117d fix es6-shim/es6-shim.map (#62) 2016-09-03 01:31:26 +02:00
Marvin W cb065d8d07 Fully resolve gateway names in hopglass (#57)
Depends on hopglass/hopglass-server#56
2016-08-05 19:47:25 +02:00
Marvin W bf2e858c24 Make sure hopglass does not break when server reports nodes without hostname. (#58) 2016-08-01 23:25:29 +02:00
Milan Paessler 46de672dc9 main: support for ffmap-backend vpn link notation 2016-07-23 16:33:49 +00:00
Daniel Krah 07d5e3f636 check IPv4/IPv6 (#56) 2016-07-23 14:52:49 +00:00
codedust cfd778dadb Fix number of decimal places being displayed in the CPU load bar (#54) 2016-07-23 14:49:35 +00:00
Milan Paessler 8f7b1e15ce Revert "Router pics (#37)"
This reverts commit 8435885e5c.
2016-07-04 12:45:19 +02:00
Marvin W 760cca6806 Properly handle link types (#47)
* Properly handle link types

* fixup! Properly handle link types
2016-07-04 11:29:16 +02:00
eberhab 2844a203d5 iframe width relates to sidebar width (#44) 2016-07-04 11:00:43 +02:00
Marvin W 1b332508a0 Shorten device name in sidebar stats (#46)
This way, version codes on TP-Link devices and processor names are visible
2016-07-04 10:58:26 +02:00
Moorviper 8435885e5c Router pics (#37)
* add a padding to sidebar for better use on mobile devices

* allow other things as text in attributeEntry

Now it is possible to use pictures and other stuff in this function

* Show Router-Pic with double fallback

3 Möglichkeiten
1. in config wird externe Quelle angegeben
2. wenn nicht wird in ./nodes/ geseucht
3. ansonsten Knotenname + Knotenname in erster Zeile

extern via:
cdn

"hwImg": [
  { "thumbnail": "https://cdn.rawgit.com/Moorviper/meshviewer_hwpics/master/nodes/{MODELHASH}.svg",
    "caption": "Knoten {MODELHASH}"
  }
 ]

aktueller nicht cdn:
"hwImg": [
  { "thumbnail": "https://rawgit.com/Moorviper/meshviewer_hwpics/master/nodes/{MODELHASH}.svg",
    "caption": "Knoten {MODELHASH}"
  }
 ]
2016-07-04 10:57:04 +02:00
eberhab f80ea1ca8f infobox: added wifi channel, airtime and batctl nexthop (#38)
* infobox: added wifi channel, airtime and batctl nexthop

* simplify

* kill all semicolons

* update data locations

I'd suggest that channel information go under nodeinfo. Airtime and nexthop are changing variables which should go under statistics.

* filter out nodes with faulty nodeinfo

* additional nexthop key

The nexthop to the batman gateway has now been made available in gluon master where it will be called "gateway_nexthop" the "nexthop" key is also currently in use and should eventually be removed

* sidebar/node: cosmetic changes to gateway/nexthop

* remove data validity check

* move wifi helper function to sidebar/node

* cosmetics
2016-06-08 23:23:56 +02:00
Johannes Rudolph 2bbdd1e077 main: filter duplicate nodes from different data sources
* add jquery

* filter duplicate nodes by online state

* remove old stuff

* remove debug output

* remove curly braces
2016-06-05 22:55:26 +02:00
Moritz 2da97bca16 meshstats: show offline nodes (#39) 2016-06-03 00:43:08 +02:00
Milan Paessler 4c6ba69dd3 meshstats: don't count undefined as gateway 2016-06-02 23:17:06 +02:00
Milan Pässler 93355f28ba meshstats: dynamic gateway count 2016-06-02 22:44:42 +02:00
Xaver Maierhofer 1bcd2b797d remove obsolete Gemfile (#28) 2016-05-22 18:18:07 +02:00
Xaver Maierhofer 3971c36250 add .editorconfig (#25) 2016-05-22 12:30:44 +02:00
Marvin W 9719385076 map: dynamically select maxLayerZoom (#27)
Fixes clients/names being hidden accidentally (in certain conditions). Also added auto-zoom-out to ensure zoom is in bounds
2016-05-22 12:30:31 +02:00
Marvin W 20f0f08de3 Replace MapQuest with OpenStreetMap.HOT in example config (#26)
MapQuest Open Tiles service is announced to be discontinued and already started removing tiles at the highest zoom level. MapQuest provides a replacement service requiring custom javascript code to be injected, which I don't consider a good idea.
OpenStreetMap.HOT is a OpenStreetMap map style using pastel colours and thus is lot better than Mapnik for rendering stuff on it.
2016-05-22 03:12:08 +02:00
Milan Pässler 81379a8263 meshstats: fix NaN client count 2016-05-22 00:26:36 +02:00
Xaver Maierhofer 31d1464805 clean scss (#21)
* [TASK] CGL - Formation

* [TASK] Generic font fallback

* [TASK] Remove swp file
2016-05-19 17:47:32 +02:00
Xaver Maierhofer e1e510c308 use libsass (#22)
Avoid ruby dependency
2016-05-19 17:47:13 +02:00
PetaByteBoy // Milan Pässler ce8853c0fa fix examples 2016-05-09 18:17:45 +02:00
PetaByteBoy // Milan Pässler 075076a2fe infobox/*infos: fix iframe height option 2016-04-24 20:10:53 +02:00
Milan Pässler 2f28c51da1 forcegraph, main: l2tp support 2016-04-24 01:20:01 +02:00
Milan Pässler 2a78af4208 proportions: correct use of online vs all nodes 2016-04-22 01:05:32 +02:00
anoy 8497a5f833 reimplement feature 'encoded url' (#16)
change symbol names for better readability
beautify location-sidebar
cleanup sourcecode

* change symbols to unicode-names

* reimplement feature 'encoded url'
2016-04-21 22:34:21 +02:00
Marvin W 51c1c57e48 Save selected layer in local storage (#17) 2016-04-21 22:32:22 +02:00
eberhab bc7e7888a5 helper: str replace function should replace all instances of placeholder 2016-04-20 14:04:46 +02:00
anoy 31545c758d infobox/location: beautify location-sidebar
and cleanup sourcecode
2016-04-02 22:47:57 +02:00
45 changed files with 4324 additions and 740 deletions

16
.editorconfig Normal file
View file

@ -0,0 +1,16 @@
# 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
before_install:
- gem install sass
- npm install -g grunt-cli
node_js:
- "lts/*"
- "node"
install:
- npm install
script: grunt
- yarn
script: node_modules/.bin/grunt

View file

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

View file

@ -1,13 +0,0 @@
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", ["bower-install-simple", "lint", "saveRevision", "copy", "sass", "requirejs"])
grunt.registerTask("default", ["lint", "saveRevision", "copy", "sass", "postcss", "requirejs"])
grunt.registerTask("lint", ["eslint"])
grunt.registerTask("dev", ["default", "connect:server", "watch"])
}

View file

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

27
app.js
View file

@ -1,21 +1,20 @@
require.config({
baseUrl: "lib",
paths: {
"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",
"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",
"helper": "../helper",
"jshashes": "../bower_components/jshashes/hashes",
"c3": "../bower_components/c3/c3.min"
"jshashes": "../node_modules/jshashes/hashes"
},
shim: {
"leaflet.label": ["leaflet"],

View file

@ -1,40 +0,0 @@
{
"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",
"c3": "~0.4.10"
},
"authors": [
"Milan Pässler <me@petabyteboy.de>",
"Nils Schneider <nils@nilsschneider.net>"
],
"license": "AGPL3",
"private": true,
"resolutions": {
"d3": "~3.5.5"
}
}

View file

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

View file

@ -1,62 +1,62 @@
{
"dataPath": [
"http://map.ffgl.eu/data/",
"http://karte.ffdus.de/data/",
"http://karte.freifunk-iserlohn.de/data/"
],
"siteName": "Freifunk Fluss",
"dataPath": "https://map.ffdus.de/data/",
"siteName": "Freifunk Flingern",
"mapSigmaScale": 0.5,
"showContact": true,
"maxAge": 14,
"mapLayers": [
{ "name": "MapQuest",
"url": "https://otile{s}-s.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg",
{ "name": "CartoDB",
"url": "https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png",
"config": {
"subdomains": "1234",
"type": "osm",
"attribution": "Tiles &copy; <a href=\"https://www.mapquest.com/\" target=\"_blank\">MapQuest</a>, Data CC-BY-SA OpenStreetMap",
"maxZoom": 18
"maxZoom": 18,
"attribution": "&copy; <a href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a>, &copy; | <a href=\"https://carto.com/attribution\">CARTO</a>"
}
},
{
"name": "Stamen.TonerLite"
"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}"
}
],
"siteNames": [
{ "site": "ffgl-bcd", "name": "Burscheid" },
{ "site": "ffgl-bgl", "name": "Bergisch Gladbach" },
{ "site": "ffgl-lln", "name": "Leichlingen" },
{ "site": "ffgl-ode", "name": "Odenthal" },
{ "site": "ffgl-ovr", "name": "Overath" },
{ "site": "ffgl-rrh", "name": "Rösrath" },
{ "site": "ffdus", "name": "Flingern" }
{ "site": "ffis", "name": "Iserlohn" }
{ "site": "dus", "name": "Flingern" }
],
"nodeCharts": [
{
"name": "Statistik",
"metrics": [
{
"id": "clients",
"color": "#1566A9",
"label": "Clients",
"query": "sum(statistics_clients_total{nodeid=%22{{NODE_ID}}%22})"
},
{
"id": "load",
"color": "1566A9",
"label": "Systemlast",
"query": "sum(statistics_loadavg{nodeid=%22{{NODE_ID}}%22})"
},
{
"id": "rx",
"color": "#1566A9",
"label": "Traffic (RX, kbps)",
"query": "sum(rate(statistics_traffic_rx_bytes{nodeid=%22{{NODE_ID}}%22}[30m])/125)"
}
],
"defaultMetric": "clients",
"url": "https://prometheus.map.eulenfunk.de/api/v1/query_range"
"hwImg": [
{ "thumbnail": "https://cdn.rawgit.com/Moorviper/meshviewer_hwpics/master/nodes/{MODELHASH}.svg",
"caption": "Knoten {MODELHASH}"
}
]
}

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) {
@ -75,7 +75,8 @@ function localStorageTest() {
function listReplace(s, subst) {
for (key in subst) {
s = s.replace(key, subst[key])
var re = new RegExp(key, 'g')
s = s.replace(re, subst[key])
}
return s
}
@ -129,7 +130,13 @@ 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"
}
tr.appendChild(th)
var td = document.createElement("td")
@ -148,8 +155,8 @@ function attributeEntry(el, label, value) {
function createIframe(opt, width, height) {
el = document.createElement("iframe")
width = typeof width !== 'undefined' ? width : '525px';
height = typeof height !== 'undefined' ? width : '350px';
width = typeof width !== 'undefined' ? width : '100%'
height = typeof height !== 'undefined' ? height : '350px'
if (opt.src)
el.src = opt.src
@ -179,7 +186,7 @@ function createIframe(opt, width, height) {
function showStat(o, subst) {
var content, caption
subst = typeof subst !== 'undefined' ? subst : {};
subst = typeof subst !== 'undefined' ? subst : {}
if (o.thumbnail) {
content = document.createElement("img")
@ -194,7 +201,7 @@ function showStat(o, subst) {
}
if (o.iframe) {
content = createIframe(o.iframe)
content = createIframe(o.iframe, o.width, o.height)
if (o.iframe.src)
content.src = listReplace(o.iframe.src, subst)
else

View file

@ -4,9 +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="roboto-slab-fontface.css">
<link rel="stylesheet" href="roboto-fontface.css">
<link rel="stylesheet" href="c3.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="style.css">
<script src="vendor/es6-shim/es6-shim.min.js"></script>
<script src="app.js"></script>
@ -15,5 +14,15 @@
</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,15 +3,14 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="stylesheet" href="bower_components/roboto-slab-fontface/roboto-slab-fontface.css">
<link rel="stylesheet" href="bower_components/roboto-fontface/roboto-fontface.css">
<link rel="stylesheet" href="bower_components/leaflet/dist/leaflet.css">
<link rel="stylesheet" href="bower_components/Leaflet.label/dist/leaflet.label.css">
<link rel="stylesheet" href="bower_components/ionicons/css/ionicons.min.css">
<link rel="stylesheet" href="bower_components/c3/c3.min.css">
<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="style.css">
<script src="bower_components/es6-shim/es6-shim.min.js"></script>
<script src="bower_components/requirejs/require.js" data-main="app"></script>
<script src="node_modules/es6-shim/es6-shim.min.js"></script>
<script src="node_modules/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/plumpudding/hopglass\">"
s += "https://github.com/plumpudding/hopglass</a>."
s += "<a href=\"https://github.com/hopglass/hopglass\">"
s += "https://github.com/hopglass/hopglass</a>."
el.innerHTML = s
}

View file

@ -4,9 +4,8 @@ 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()

View file

@ -1,7 +1,7 @@
define(["d3"], function (d3) {
var margin = 200
var NODE_RADIUS = 15
var LINE_RADIUS = 12
var LINE_RADIUS = 7
return function (config, linkScale, sidebar, router) {
var self = this
@ -214,7 +214,7 @@ define(["d3"], function (d3) {
function drawLabel(d) {
var neighbours = d.neighbours.filter(function (d) {
return d.link.o.type !== "VPN"
return !d.link.o.isVPN
})
var sum = neighbours.reduce(function (a, b) {
@ -242,7 +242,8 @@ define(["d3"], function (d3) {
}
function visibleLinks(d) {
return (d.source.x > screenRect.left && d.source.x < screenRect.right &&
return (d.o.isVPN ||
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)
@ -324,16 +325,19 @@ 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)
var a = Math.sqrt(dx * dx + dy * dy) * 2
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 - dx * nodeRadius, d.target.y - dy * nodeRadius)
ctx.lineTo(d.target.x - (distancex / 2) - dx * nodeRadius, d.target.y - (distancey / 2) - dy * nodeRadius)
ctx.strokeStyle = d.o.type === "Kabel" ? cableColor : d.color
ctx.globalAlpha = d.o.type === "VPN" ? 0.1 : 0.8
ctx.lineWidth = d.o.type === "VPN" ? 1.5 : 2.5
ctx.globalAlpha = d.o.isVPN ? 0.1 : 0.8
ctx.lineWidth = d.o.isVPN ? 1.5 : 2.5
ctx.stroke()
})
@ -376,6 +380,7 @@ define(["d3"], function (d3) {
// -- 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)
@ -470,32 +475,34 @@ define(["d3"], function (d3) {
requestAnimationFrame(redraw)
}
function distance(a, b) {
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)
function distance(ax, ay, bx, by) {
return Math.pow(ax - bx, 2) + Math.pow(ay - by, 2)
}
function distancePoint(a, b) {
return Math.sqrt(distance(a, b))
return Math.sqrt(distance(a.x, a.y, b.x, b.y))
}
function distanceLink(p, a, b) {
/* http://stackoverflow.com/questions/849211 */
var l2 = distance(a, b)
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)
if (l2 === 0)
return distance(p, a)
return distance(p.x, p.y, a.x, a.y)
var t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / l2
var t = ((p.x - a.x) * (bx - a.x) + (p.y - a.y) * (by - a.y)) / l2
if (t < 0)
return distance(p, a)
return distance(p.x, p.y, a.x, a.y)
if (t > 1)
return distance(p, b)
return distance(p.x, p.y, bx, by)
return Math.sqrt(distance(p, { x: a.x + t * (b.x - a.x),
y: a.y + t * (b.y - a.y) }))
return Math.sqrt(distance(p.x, p.y, a.x + t * (bx - a.x), a.y + t * (by - a.y) ))
}
function translateXY(d) {
@ -523,7 +530,7 @@ define(["d3"], function (d3) {
}
var links = intLinks.filter(function (d) {
return d.o.type !== "VPN"
return !d.o.isVPN
}).filter(function (d) {
return distanceLink(e, d.source, d.target) < LINE_RADIUS
})
@ -584,13 +591,13 @@ define(["d3"], function (d3) {
.charge(-250)
.gravity(0.1)
.linkDistance(function (d) {
if (d.o.type === "VPN")
if (d.o.isVPN)
return 0
else
return LINK_DISTANCE
})
.linkStrength(function (d) {
if (d.o.type === "VPN")
if (d.o.isVPN)
return 0
else
return Math.max(0.5, 1 / d.o.tq)
@ -644,7 +651,7 @@ define(["d3"], function (d3) {
e.source = newNodesDict[d.source.id]
e.target = newNodesDict[d.target.id]
if (d.type === "VPN")
if (d.isVPN)
e.color = "rgba(255, 255, 255, " + (0.6 / d.tq) + ")"
else
e.color = linkScale(d.tq).hex()
@ -696,6 +703,15 @@ define(["d3"], function (d3) {
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]

View file

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

View file

@ -1,255 +0,0 @@
define(["c3", "d3"], function (c3, d3) {
var charts = function (node, config) {
this.node = node
this.chartConfig = config
this.chart = null
this.zoomConfig = {
"levels": [
{"label": "8h", "from": 8 * 3600, "interval": 15 * 60}
/*{"label": "24h", "from": "26h", "interval": "1h"},
{"label": "1m", "from": "1mon", "interval": "1d"},
{"label": "1y", "from": 31536000, "interval": 2628000}*/
]
}
this.zoomLevel = 0
this.c3Config = {
"size": {
"height": 240
},
padding: {
bottom: 30
},
"legend": {
"item": {
"onclick": function (id) {
this.api.hide()
this.api.show(id)
}
}
},
"tooltip": {
"format": {
"value": this.c3FormatToolTip.bind(this)
}
},
"axis": {
"x": {
"type": "timeseries",
"tick": {
"format": this.c3FormatXAxis.bind(this),
"rotate": -45
}
},
"y": {
"min": 0,
"padding": {
"bottom": 0
}
}
}
}
this.cache = []
this.init()
}
charts.prototype = {
init: function () {
// Workaround for endless loop bug
if (this.c3Config.axis.x.tick && this.c3Config.axis.x.tick.format && typeof this.c3Config.axis.x.tick.format === "function") {
if (this.c3Config.axis.x.tick.format && !this.c3Config.axis.x.tick._format)
this.c3Config.axis.x.tick._format = this.c3Config.axis.x.tick.format
this.c3Config.axis.x.tick.format = function (val) {
return this.c3Config.axis.x.tick._format(val)
}.bind(this)
}
// Configure metrics
this.c3Config.data = {
"keys": {
"x": "time",
"value": this.chartConfig.metrics.map(function (metric) {
return metric.id
})
},
"colors": this.chartConfig.metrics.reduce(function (collector, metric) {
collector[metric.id] = metric.color
return collector
}, {}),
"names": this.chartConfig.metrics.reduce(function (collector, metric) {
collector[metric.id] = metric.label
return collector
}, {}),
"hide": this.chartConfig.metrics.map(function (metric) {
return metric.id
}).filter(function (id) {
return id !== this.chartConfig.defaultMetric
}.bind(this))
}
},
render: function () {
var div = document.createElement("div")
div.classList.add("chart")
var h4 = document.createElement("h4")
h4.textContent = this.chartConfig.name
div.appendChild(h4)
// Render chart
this.load(function (data) {
div.appendChild(this.renderChart(data))
// Render zoom controls
if (this.zoomConfig.levels.length > 0)
div.appendChild(this.renderZoomControls())
}.bind(this))
return div
},
renderChart: function (data) {
this.c3Config.data.json = data
this.chart = c3.generate(this.c3Config)
return this.chart.element
},
updateChart: function (data) {
this.c3Config.data.json = data
this.chart.load(this.c3Config.data)
},
renderZoomControls: function () {
// Draw zoom controls
var zoomDiv = document.createElement("div")
zoomDiv.classList.add("zoom-buttons")
var zoomButtons = []
this.zoomConfig.levels.forEach(function (v, level) {
var btn = document.createElement("button")
btn.classList.add("zoom-button")
btn.setAttribute("data-zoom-level", level)
if (level === this.zoomLevel)
btn.classList.add("active")
btn.onclick = function () {
if (level !== this.zoomLevel) {
zoomButtons.forEach(function (v, k) {
if (level !== k)
v.classList.remove("active")
else
v.classList.add("active")
})
this.setZoomLevel(level)
}
}.bind(this)
btn.textContent = v.label
zoomButtons[level] = btn
zoomDiv.appendChild(btn)
}.bind(this))
return zoomDiv
},
setZoomLevel: function (level) {
if (level !== this.zoomLevel) {
this.zoomLevel = level
this.load(this.updateChart.bind(this))
}
},
load: function (callback) {
if (this.cache[this.zoomLevel])
callback(this.cache[this.zoomLevel])
else {
var urls = []
var id = this.node.nodeinfo.node_id
var unixStamp = Math.floor(Date.now() / 1000)
var zoomConfig = this.zoomConfig.levels[this.zoomLevel]
this.chartConfig.metrics.forEach(function(metric) {
var parameters = [
"start=" + (unixStamp - zoomConfig.from),
"end=" + unixStamp,
"step=" + zoomConfig.interval,
"query=" + metric.query.replace("{{NODE_ID}}", id)
]
var url = this.chartConfig.url + "?" + parameters.join("&")
urls.push(url)
}.bind(this))
Promise.all(urls.map(getJSON)).then(function (data) {
this.cache[this.zoomLevel] = this.parse(data)
callback(this.cache[this.zoomLevel])
}.bind(this))
}
},
parse: function (results) {
var data = []
results[0].data.result[0].values.forEach(function (tp) {
var time = {"time": new Date(tp[0] * 1000)}
results.forEach(function(result) {
var metric = this.chartConfig.metrics[results.indexOf(result)]
var index = results[0].data.result[0].values.indexOf(tp)
time[metric.id] = this.formatValue(metric.id, result.data.result[0].values[index][1])
}.bind(this))
data.push(time)
}.bind(this))
return data
},
c3FormatToolTip: function (d, ratio, id) {
switch (id) {
case "uptime":
return d.toFixed(1) + " Tage"
default:
return d
}
},
c3FormatXAxis: function (d) {
var pad = function (number, pad) {
var N = Math.pow(10, pad)
return number < N ? ("" + (N + number)).slice(1) : "" + number
}
switch (this.zoomLevel) {
case 0: // 8h
case 1: // 24h
return pad(d.getHours(), 2) + ":" + pad(d.getMinutes(), 2)
case 2: // 1m
return pad(d.getDate(), 2) + "." + pad(d.getMonth() + 1, 2)
case 3: // 1y
return ["Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"][d.getMonth()]
default:
break
}
},
formatValue: function (id, value) {
switch (id) {
case "loadavg":
return (d3.format(".2r")(value))
case "clientcount":
return (Math.ceil(value))
case "uptime":
return (value / 86400)
default:
return value
}
}
}
return charts
})

View file

@ -1,8 +1,10 @@
define(function () {
function showStatImg(o, source, target) {
function showStatImg(o, d) {
var subst = {}
subst["{SOURCE}"] = source
subst["{TARGET}"] = target
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)
}
@ -35,15 +37,12 @@ define(function () {
attributeEntry(attributes, "Hardware", (hw1 != null ? hw1 : "unbekannt") + " " + (hw2 != null ? hw2 : "unbekannt"))
el.appendChild(attributes)
if (config.linkInfos) {
var source = d.source.node_id
var target = d.target.node_id
if (config.linkInfos)
config.linkInfos.forEach( function (linkInfo) {
var h4 = document.createElement("h4")
h4.textContent = linkInfo.name
el.appendChild(h4)
el.appendChild(showStatImg(linkInfo, source, target))
el.appendChild(showStatImg(linkInfo, d))
})
}
}
})

View file

@ -1,80 +1,83 @@
define(function () {
return function (config, el, router, d) {
var h2 = document.createElement("h2")
h2.textContent = "Location: " + d.toString()
el.appendChild(h2)
var sidebarTitle = document.createElement("h2")
sidebarTitle.textContent = "Location: " + d.toString()
el.appendChild(sidebarTitle)
getJSON("https://nominatim.openstreetmap.org/reverse?format=json&lat=" + d.lat + "&lon=" + d.lng + "&zoom=18&addressdetail=0")
getJSON("https://nominatim.openstreetmap.org/reverse?format=json&lat=" + d.lat + "&lon=" + d.lng + "&zoom=18&addressdetails=0")
.then(function(result) {
h2.textContent = result.display_name
if(result.display_name)
sidebarTitle.textContent = result.display_name
})
var latDiv = document.createElement("div")
var h3lat = document.createElement("h3")
h3lat.textContent = "Breitengrad"
h3lat.id = "h3-latitude"
latDiv.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))
latDiv.appendChild(p)
el.appendChild(latDiv)
var editLat = document.createElement("input")
editLat.type = "text"
editLat.value = d.lat.toFixed(9)
el.appendChild(createBox("lat", "Breitengrad", editLat))
var longDiv = document.createElement("div")
var h3lng = document.createElement("h3")
h3lng.textContent = "Längengrad"
longDiv.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))
longDiv.appendChild(p2)
longDiv.id = "div-longitude"
el.appendChild(longDiv)
var editLng = document.createElement("input")
editLng.type = "text"
editLng.value = d.lng.toFixed(9)
el.appendChild(createBox("lng", "Längengrad", editLng))
var a1 = document.createElement("a")
a1.textContent = "plain"
a1.onclick = function() {
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
}
a1.href = "#"
var a2 = document.createElement("a")
a2.textContent = "uci"
a2.onclick = function() {
linkPlain.href = "#"
var linkUci = document.createElement("a")
linkUci.textContent = "uci"
linkUci.onclick = function() {
switch2uci()
return false
}
a2.href = "#"
linkUci.href = "#"
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)
var hintText = document.createElement("p")
hintText.appendChild(document.createTextNode("Du kannst zwischen "))
hintText.appendChild(linkPlain)
hintText.appendChild(document.createTextNode(" und "))
hintText.appendChild(linkUci)
hintText.appendChild(document.createTextNode(" wechseln."))
el.appendChild(hintText)
function createCopyButton(id) {
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(id)
}
return btn
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 copy2clip(id) {
var copyTextarea = document.querySelector("#" + id)
copyTextarea.select()
var copyField = document.querySelector("#" + id)
copyField.select()
try {
document.execCommand("copy")
} catch (err) {
@ -83,18 +86,15 @@ define(function () {
}
function switch2plain() {
var box1 = document.getElementById("location-latitude")
box1.value = d.lat.toFixed(9)
var box2 = document.getElementById("location-longitude")
box2.value = d.lng.toFixed(9)
document.getElementById("h3-latitude").textContent = "Breitengrad"
document.getElementById("div-longitude").style.display = "block"
document.getElementById("box-uci").style.display = "none"
document.getElementById("box-lat").style.display = "block"
document.getElementById("box-lng").style.display = "block"
}
function switch2uci() {
document.getElementById("location-latitude").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"
document.getElementById("h3-latitude").textContent = "Befehl"
document.getElementById("div-longitude").style.display = "none"
document.getElementById("box-uci").style.display = "block"
document.getElementById("box-lat").style.display = "none"
document.getElementById("box-lng").style.display = "none"
}
}
})

View file

@ -1,5 +1,5 @@
define(["moment", "numeral", "tablesort", "infobox/charts", "tablesort.numeric"],
function (moment, numeral, Tablesort, Charts) {
define(["moment", "numeral", "tablesort", "tablesort.numeric"],
function (moment, numeral, Tablesort) {
function showGeoURI(d) {
function showLatitude(d) {
var suffix = Math.sign(d) > -1 ? "'N" : "'S"
@ -81,18 +81,124 @@ define(["moment", "numeral", "tablesort", "infobox/charts", "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"))
}
}
@ -112,6 +218,9 @@ define(["moment", "numeral", "tablesort", "infobox/charts", "tablesort.numeric"]
if (link) {
var a = document.createElement("a")
if (ip.includes("."))
a.href = "http://" + ip + "/"
else
a.href = "http://[" + ip + "]/"
a.textContent = ip
el.appendChild(a)
@ -156,7 +265,7 @@ define(["moment", "numeral", "tablesort", "infobox/charts", "tablesort.numeric"]
}
var label = document.createElement("label")
label.textContent = (v)
label.textContent = +(Math.round(v + "e+2") + "e-2")
span.appendChild(label)
return span
@ -180,6 +289,65 @@ define(["moment", "numeral", "tablesort", "infobox/charts", "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)
@ -219,6 +387,37 @@ define(["moment", "numeral", "tablesort", "infobox/charts", "tablesort.numeric"]
return au.enabled ? "aktiviert (" + au.branch + ")" : "deaktiviert"
}
function showNodeImg(o, model) {
if (!model)
return document.createTextNode("Knotenname")
var content, caption
var modelhash = model.split("").reduce(function(a, b) {
a = ((a << 5) - a) + b.charCodeAt(0)
return a & a
}, 0)
content = document.createElement("img")
content.id = "routerpicture"
content.classList.add("nodeImg")
content.src = o.thumbnail.replace("{MODELHASH}", modelhash)
content.onerror = function() {
document.getElementById("routerpicdiv").outerHTML = "Knotenname"
}
if (o.caption) {
caption = o.caption.replace("{MODELHASH}", modelhash)
if (!content)
content = document.createTextNode(caption)
}
var p = document.createElement("p")
p.appendChild(content)
return content
}
function showStatImg(o, d) {
var subst = {}
subst["{NODE_ID}"] = d.nodeinfo.node_id ? d.nodeinfo.node_id : "unknown"
@ -227,12 +426,29 @@ define(["moment", "numeral", "tablesort", "infobox/charts", "tablesort.numeric"]
}
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")
h2.textContent = d.nodeinfo.hostname
el.appendChild(h2)
var attributes = document.createElement("table")
attributes.classList.add("attributes")
}
attributeEntry(attributes, "Status", showStatus(d))
attributeEntry(attributes, "Gateway", d.flags.gateway ? "ja" : null)
@ -248,20 +464,20 @@ define(["moment", "numeral", "tablesort", "infobox/charts", "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", dictGet(d.statistics, ["gateway"]))
attributeEntry(attributes, "Gewähltes Gateway", showGateway(d, router))
attributeEntry(attributes, "Autom. Updates", showAutoupdate(d))
attributeEntry(attributes, "Clients", showClients(d))
attributeEntry(attributes, "Clients", showClients(d), showMeshClients(d))
el.appendChild(attributes)
if (!d.flags.gateway && config.nodeCharts)
config.nodeCharts.forEach( function (config) {
el.appendChild((new Charts(d, config)).render())
})
if (config.nodeInfos)
config.nodeInfos.forEach( function (nodeInfo) {
@ -315,13 +531,7 @@ define(["moment", "numeral", "tablesort", "infobox/charts", "tablesort.numeric"]
tr.appendChild(td1)
var td2 = document.createElement("td")
var a1 = document.createElement("a")
a1.classList.add("hostname")
a1.textContent = unknown ? d.id : d.node.nodeinfo.hostname
if (!unknown)
a1.href = "#"
a1.onclick = router.node(d.node)
td2.appendChild(a1)
td2.appendChild(createLink(d, router))
if (!unknown && has_location(d.node)) {
var span = document.createElement("span")

View file

@ -4,12 +4,14 @@ 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
@ -32,11 +34,22 @@ function (moment, Router, L, GUI, numeral) {
vererr = "Unsupported nodes version: " + data[i].version
console.log(vererr) //silent fail
} else {
dataNodes.nodes = dataNodes.nodes.concat(data[i].nodes)
data[i].nodes.forEach(fillData)
dataNodes.timestamp = data[i].timestamp
}
}
function fillData (node) {
var position = dataNodes.nodeIds.indexOf(node.nodeinfo.node_id)
if(position === -1){
dataNodes.nodes.push(node)
dataNodes.nodeIds.push(node.nodeinfo.node_id)
}
else
if(node.flags.online === true)
dataNodes.nodes[position] = node
}
var nodes = dataNodes.nodes.filter( function (d) {
return "firstseen" in d && "lastseen" in d
})
@ -110,17 +123,44 @@ 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")
if (d.type === "tunnel" || d.vpn) {
d.type = "VPN"
else if (d.type === "wireless")
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"
else if (d.type === "other")
d.isVPN = false
} else if (d.type === "other") {
d.type = "Kabel"
else
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 })
@ -128,7 +168,7 @@ function (moment, Router, L, GUI, numeral) {
}
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.type !== "VPN")
if (!d.isVPN)
d.source.node.meshlinks = d.source.node.meshlinks ? d.source.node.meshlinks + 1 : 1
})

View file

@ -18,7 +18,7 @@ define(["map/clientlayer", "map/labelslayer",
onAdd: function () {
var button = L.DomUtil.create("button", "add-layer")
button.textContent = ""
button.textContent = "\uF2C7"
// 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 = ""
button.textContent = "\uF2E9"
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 = ""
button.textContent = "\uF2A6"
// 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 = new Set()
var customLayers = {}
var baseLayers = {}
var locateUserButton = new LocateButton(function (d) {
@ -259,16 +259,16 @@ define(["map/clientlayer", "map/labelslayer",
if (layerName in baseLayers)
return
if (customLayers.has(layerName))
if (layerName in customLayers)
return
try {
var layer = L.tileLayer.provider(layerName)
layerControl.addBaseLayer(layer, layerName)
customLayers.add(layerName)
customLayers[layerName] = layer
if (localStorageTest())
localStorage.setItem("map/customLayers", JSON.stringify(Array.from(customLayers)))
localStorage.setItem("map/customLayers", JSON.stringify(Object.keys(customLayers)))
} catch (e) {
return
}
@ -286,16 +286,10 @@ define(["map/clientlayer", "map/labelslayer",
var layers = config.mapLayers.map( function (d) {
return {
"name": d.name,
"layer": "url" in d ? L.tileLayer(d.url, d.config) : L.tileLayer.provider(d.name)
"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)
}
})
var maxLayerZoom = Math.max.apply(Math, config.mapLayers.map(
function(d) {
return (typeof d.config !== "undefined" && typeof d.config.maxZoom !== "undefined") ? d.config.maxZoom : 18
}))
layers[0].layer.addTo(map)
layers.forEach( function (d) {
@ -324,16 +318,33 @@ 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, maxZoom: maxLayerZoom})
var clientLayer = new ClientLayer({minZoom: 15})
clientLayer.addTo(map)
clientLayer.setZIndex(5)
var labelsLayer = new LabelsLayer({maxZoom: maxLayerZoom})
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

View file

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

View file

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

View file

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

View file

@ -128,7 +128,13 @@ 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"])
var hwDict = count(nodes, ["nodeinfo", "hardware", "model"], function (d) {
if (d) {
d = d.replace(/\(r\)|\(tm\)/gi, "").replace(/AMD |Intel |TP-Link | CPU| Processor/g, "")
if (d.indexOf("@") > 0) d = d.substring(0, d.indexOf("@"))
}
return d
})
var geoDict = count(nodes, ["nodeinfo", "location"], function (d) {
return d && d.longitude && d.latitude ? "ja" : "nein"
})
@ -146,12 +152,15 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "verc
return d ? "ja" : "nein"
})
var gwNodesDict = count(nodes, ["statistics", "gateway"], function (d) {
var gwNodesDict = count(onlineNodes, ["statistics", "gateway"], function (d) {
if (d === null)
return null
if (d in nodeDict)
return nodeDict[d].nodeinfo.hostname
if (d.node)
return d.node.nodeinfo.hostname
if (d.id)
return d.id
return d
})
@ -160,13 +169,16 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "verc
if (d === null)
return null
if (d in nodeDict)
return nodeDict[d].nodeinfo.hostname
if (d.node)
return d.node.nodeinfo.hostname
if (d.id)
return d.id
return d
})
var siteDict = count(onlineNodes, ["nodeinfo", "system", "site_code"], function (d) {
var siteDict = count(nodes, ["nodeinfo", "system", "site_code"], function (d) {
var rt = d
if (config.siteNames)
config.siteNames.forEach( function (t) {
@ -182,8 +194,8 @@ define(["chroma-js", "virtual-dom", "numeral-intl", "filters/genericnode", "verc
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("Nodes an Gateway", gwNodesTable, gwNodesDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Clients an Gateway", gwClientsTable, gwClientsDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Gateway", gwNodesTable, gwNodesDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Gateway", gwClientsTable, gwClientsDict.sort(function (a, b) { return b[1] - a[1] }))
fillTable("Site", siteTable, siteDict.sort(function (a, b) { return b[1] - a[1] }))
}

View file

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

View file

@ -1,22 +1,41 @@
{
"name": "hopglass",
"version": "1.0.0",
"scripts": {
"test": "node -e \"require('grunt').cli()\" '' clean lint"
},
"devDependencies": {
"grunt": "^0.4.5",
"autoprefixer": "^6.3.3",
"dart-sass": "^1.16.1",
"grunt": "^1.0.3",
"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-bower-install-simple": "^1.1.2",
"grunt-git-describe": "^2.3.2"
"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"
},
"eslintConfig": {
"env": {

View file

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

View file

@ -1,28 +0,0 @@
.infobox .chart {
position: relative;
& > .c3 {
margin-right: 20px;
}
.zoom-buttons {
position: absolute;
top: 0px;
right: 20px;
button {
font-size: 10pt;
width: 3em;
height: 3em;
border-radius: 1.5em;
margin-left: 6px;
}
}
.c3-tooltip-container {
width: 150px;
.c3-tooltip {
border-collapse: collapse;
}
}
}

View file

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

View file

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

View file

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

View file

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

23
scss/_loader.scss Normal file
View file

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

View file

@ -35,13 +35,25 @@
}
@-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,25 +22,31 @@ 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,19 +1,15 @@
/* 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

@ -0,0 +1,36 @@
.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,6 +4,7 @@
@import '_leaflet';
@import '_leaflet.label';
@import '_filters';
@import '_loader';
$minscreenwidth: 630pt;
$sidebarwidth: 420pt;
@ -14,7 +15,6 @@ $buttondistance: 12pt;
@import '_map';
@import '_forcegraph';
@import '_legend';
@import '_chart';
.content {
position: fixed;
@ -45,7 +45,7 @@ $buttondistance: 12pt;
margin: 0;
list-style: none;
display: flex;
font-family: Roboto;
font-family: Roboto, sans-serif;
@include shadow(1);
}
@ -70,8 +70,9 @@ $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 {
@ -117,6 +118,38 @@ 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);
@ -144,6 +177,13 @@ table.attributes td {
white-space: normal;
}
.infobox .clientsMesh {
font-family: "ionicons";
color: #dbdbdb;
word-spacing: -0.2em;
white-space: normal;
}
.infobox {
position: relative;
padding: 0.25em 0;
@ -152,6 +192,27 @@ table.attributes td {
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 {
@ -160,6 +221,7 @@ button {
@include shadow(1);
border-radius: 0.9em;
background: rgba(255, 255, 255, 0.7);
color: #333;
border: none;
cursor: pointer;
height: 1.8em;
@ -180,7 +242,7 @@ button:hover {
}
button:active {
box-shadow: inset 0px 5px 20px rgba(0, 0, 0, 0.19), inset 0px 3px 6px rgba(0, 0, 0, 0.23);
box-shadow: inset 0 5px 20px rgba(0, 0, 0, 0.19), inset 0 3px 6px rgba(0, 0, 0, 0.23);
}
button::-moz-focus-inner {
@ -199,6 +261,8 @@ button.close {
border-radius: 0;
color: rgba(0, 0, 0, 0.5);
font-family: "ionicons";
position: absolute;
right: 0;
&:hover {
color: #dc0067;
@ -300,7 +364,7 @@ table {
position: absolute;
top: $buttondistance;
left: $buttondistance;
margin-bottom: $buttondistance;
padding-bottom: $buttondistance;
transition: left 0.5s;
}
@ -385,9 +449,9 @@ table {
@media screen and (max-width: 80em) {
.sidebar {
font-size: 0.8em;
top: 0pt;
left: 0pt;
margin: 0pt;
top: 0;
left: 0;
margin: 0;
width: $sidebarwidthsmall;
min-height: 100vh;
@include shadow(2);
@ -418,7 +482,7 @@ table {
.sidebar {
position: static;
margin: 0em !important;
margin: 0 !important;
width: auto;
height: auto;
min-height: 0;

View file

@ -1,6 +1,6 @@
module.exports = function(grunt) {
grunt.config.merge({
bowerdir: "bower_components",
nodedir: "node_modules",
copy: {
html: {
options: {
@ -19,26 +19,21 @@ module.exports = function(grunt) {
dest: "build/"
},
vendorjs: {
src: [ "es6-shim/es6-shim.min.js" ],
src: ["es6-shim/es6-shim.min.js",
"es6-shim/es6-shim.map"],
expand: true,
cwd: "bower_components/",
cwd: "node_modules/",
dest: "build/vendor/"
},
robotoSlab: {
src: [ "fonts/*",
"roboto-slab-fontface.css"
],
expand: true,
dest: "build/",
cwd: "bower_components/roboto-slab-fontface"
},
roboto: {
src: [ "fonts/*",
"roboto-fontface.css"
src: [ "fonts/roboto/*",
"fonts/roboto-slab/*",
"css/roboto/roboto-fontface.css",
"css/roboto-slab/roboto-slab-fontface.css"
],
expand: true,
dest: "build/",
cwd: "bower_components/roboto-fontface"
cwd: "node_modules/roboto-fontface/"
},
ionicons: {
src: [ "fonts/*",
@ -46,59 +41,55 @@ module.exports = function(grunt) {
],
expand: true,
dest: "build/",
cwd: "bower_components/ionicons/"
cwd: "node_modules/ionicons/"
},
leafletImages: {
src: [ "images/*" ],
expand: true,
dest: "build/",
cwd: "bower_components/leaflet/dist/"
},
c3: {
src: ["c3.min.css"],
expand: true,
dest: "build/",
cwd: "bower_components/c3/"
cwd: "node_modules/leaflet/dist/"
}
},
sass: {
dist: {
options: {
style: "compressed"
sourceMap: true,
outputStyle: "compressed",
implementation: require("dart-sass")
},
dist: {
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": [ "bower_components/leaflet/dist/leaflet.css",
"bower_components/Leaflet.label/dist/leaflet.label.css",
"build/style.css": [ "node_modules/leaflet/dist/leaflet.css",
"node_modules/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: "../bower_components/almond/almond",
name: "../node_modules/almond/almond",
mainConfigFile: "app.js",
include: "../app",
wrap: true,
@ -109,8 +100,8 @@ module.exports = function(grunt) {
}
})
grunt.loadNpmTasks("grunt-bower-install-simple")
grunt.loadNpmTasks("grunt-contrib-copy")
grunt.loadNpmTasks("grunt-contrib-requirejs")
grunt.loadNpmTasks("grunt-contrib-sass")
grunt.loadNpmTasks("grunt-sass")
grunt.loadNpmTasks("grunt-postcss")
}

View file

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

3451
yarn.lock Normal file

File diff suppressed because it is too large Load diff