Compare commits

..

1 Commits

Author SHA1 Message Date
Xaver Maierhofer
613f2ba3ec
[TASK] Exo2 font and adjust custom scss 2017-05-11 00:34:02 +02:00
104 changed files with 4289 additions and 6806 deletions

26
.bithoundrc Normal file
View File

@ -0,0 +1,26 @@
{
"dependencies": {
"mute": [
],
"unused-ignores": [
"almond",
"d3-*",
"leaflet",
"moment",
"navigo",
"node-polyglot",
"promise-polyfill",
"rbush",
"requirejs",
"snabbdom"
]
},
"critics": {
"wc": {
"limit": 5000
}
},
"ignore": [
"polyfill.js"
]
}

View File

@ -1,14 +0,0 @@
kind: pipeline
name: meshviewer build
steps:
- name: docker
image: plugins/docker
settings:
repo: fftdf/meshviewer
target: meshviewer
username:
from_secret: docker_username
password:
from_secret: docker_password

View File

@ -2,19 +2,15 @@
# top-most EditorConfig file
root = true
[*]
end_of_line = lf
insert_final_newline = 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,yml,md}]
indent_size = 2
indent_style = space
[assets/favicon/manifest.json]
indent_size = 4

View File

@ -9,6 +9,5 @@ rules:
"func-names": 0
"guard-for-in": 0
"no-undefined": 0
"consistent-return": 0
"no-nested-ternary": 0
"no-extend-native": ["error", { "exceptions": ["String"] }]

View File

@ -1,6 +1,5 @@
## Contributing is welcome
Pull requests are welcome without the need of opening an issue. If you're unsure
about your feature or your implementation open an issue and discuss your
suggested changes. Meshviewer is a frontend application and the code needs to be
loaded fast and perform with many nodes and clients on slow mobile devices.
Pull requests are welcome without an opening an issue. If you unsure about the feature or implementation open an issue and
discuss your suggested changes. Meshviewer is a frontend application and the code needs to be loaded and
perform on slow mobile devices with many nodes and clients.

View File

@ -1,9 +1,3 @@
---
name: Bug report
about: Create a report to help us improve
---
<!--- Provide a general summary of the issue in the Title above -->
<!--- This template should help to improve the report, unneeded parts can be remvoed -->
@ -19,7 +13,7 @@ about: Create a report to help us improve
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->
## Steps to Reproduce
## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
@ -37,6 +31,3 @@ about: Create a report to help us improve
* Browser Name and version:
* Operating System and version (desktop or mobile):
* Link to your project:
## Screenshots
<!--- If applicable, add screenshots to help explain your problem. -->

View File

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
---
<!--- Provide a general summary of the issue in the Title above -->
<!--- This template should help to improve the report, unneeded parts can be remvoed -->
## Is your feature request related to a problem? Please describe.
<!--- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
## Describe the solution you'd like
<!--- A clear and concise description of what you want to happen. -->
## Describe alternatives you've considered
<!--- A clear and concise description of any alternative solutions or features you've considered. -->
## Additional context
<!--- Add any other context or screenshots about the feature request here. -->

View File

@ -1,24 +1,31 @@
sudo: false
dist: trusty
language: node_js
node_js:
- "12"
- "7"
os:
- linux
- osx
- macosx
- windows
matrix:
include:
- node_js: "8"
- node_js: 7
os: linux
- node_js: "10"
env: USE_NPM=true
- node_js: 6
os: linux
- node_js: 4
os: linux
cache:
yarn: true
before_install:
- if [ "$USE_NPM" == "true" ]; then rm yarn.lock; fi
before_script:
- if git status | grep -q "modified. \.travis\.yml"; then echo "Dirty yarn.lock"; exit 1; fi

66
CHANGELOG.md Normal file
View File

@ -0,0 +1,66 @@
# Change Log
## Switched to rolling release
- All major changes can be found in README.md and everything else in git history https://github.com/ffrgb/meshviewer
- Lot of parts of codebase have been changed
## v4
- add a legend (map)
- new graph theme
- performance improvements in graph view
- various UI changes
- various map fixes
- moved config from config.js to config.json
- online/offline statistics
- define layers for map in config
- graph: zoom by keyboard (+ and - keys)
- direct links to graph and map views
### Bugfixes
- map works with little or no nodes
## v3
### Implemented enhancements:
- Make clients in map start at a random angle
- On statistics page: show how many nodes supply geoinformation
- Allow additional statistics (global and per node) configured in config.js
- Improve node count information (total, online, clients, ...)
- Show hardware model in link infobox
- Introduce maxAge setting
- Graph: show VPN links in grayscale
### Removed features:
- Don't show contact information in node lists
### Fixed bugs:
- Fixed off-by-one when drawing clients
- Match labels order to node order in map
- Statistics: count only nodes that are present
## v2
### General changes:
- License change from GPL 3 to AGPL 3
### Implemented enhancements:
- Improved performance on Firefox
- Labels in graph view
- infobox: link to geouri with node's coordinates
- infobox: show node id
- map: locate user
- map: adding custom layers from leaflet.providers
- nodelist: sort by uptime fixed
- graph: circles for clients
### Fixed bugs:
- Links disappeared on graph on refresh

View File

@ -1,21 +0,0 @@
# builder
FROM node:12.13.1-stretch as builder
COPY . /mesh
WORKDIR /mesh
# show versions
RUN node --version && npm --version && yarn --version
# install gulp
RUN npm i gulp-cli -g
RUN npm i gulp -g
# run yarn for prerequisits
RUN yarn
# run gulp to build app
RUN gulp
# build docker container
FROM nginx:1.17.6-alpine as meshviewer
COPY --from=builder /mesh/build /usr/share/nginx/html/

View File

@ -1,20 +1,64 @@
# Meshviewer
[![Build Status](https://ci.freifunk-rhein-sieg.net/api/badges/Freifunk-Troisdorf/meshviewer/status.svg)](https://ci.freifunk-rhein-sieg.net/Freifunk-Troisdorf/meshviewer)
[![Build Status](https://img.shields.io/travis/ffrgb/meshviewer/develop.svg?style=flat-square)](https://travis-ci.org/ffrgb/meshviewer)
[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/ffrgb/meshviewer/develop.svg?style=flat-square)](https://scrutinizer-ci.com/g/ffrgb/meshviewer/?branch=develop)
[![License: AGPL v3](https://img.shields.io/github/license/ffrgb/meshviewer.svg?style=flat-square)](https://www.gnu.org/licenses/agpl-3.0)
[![Documentation](https://img.shields.io/badge/gitbooks.io-documentation-brightgreen.svg?style=flat-square)](https://meshviewer.gitbooks.io/documentation/content/)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg?style=flat-square)](https://www.gnu.org/licenses/agpl-3.0)
Meshviewer is an online visualization app to represent nodes and links on a map for Freifunk open mesh network.
#### Main differences to https://github.com/ffnord/meshviewer
_Some similar features might have been implemented/merged_
- Replaced router - including language, mode, node, link, location
- Leaflet upgraded to v1 - faster on mobile
- Forcegraph rewrite with d3.js v4
- Map layer modes (Allow to set a default layer based on time combined with a stylesheet)
- Automatic updates for selected node or list (incl. image stats cache-breaker)
- Node filter
- Zoom level for clicking on a node (`nodeZoom`) is definable independently from the maximum zoom level 22
- Formatted Code
- Translation support - https://crowdin.com/project/meshviewer - Contact us for new languages
- Currently available: en, de, fr & ru
- Gulp inline for some css and js - fewer requests and instant load indicator
- Icon font with needed icons only
- Switch to Gulp (Tested with Node.js 4/6 LTS, 7 on Linux, 7 OSX & W**)
- css and some js moved inline
- Yarn/npm in favour of bower
- Load only moment.js without languages (Languages are included in translations)
- unneeded components removed (es6-shim, tablesort, numeraljs, leaflet-providers, leaflet-label jshashes, chroma-js)
- RBush v2 - performance boost in last versions (positions, labels and clients on the map)
- Ruby dependency removed
- FixedCenter is required
- Sass-lint, scss and variables rewritten for easy customizations/adjustments
- Cross browser/device support improved (THX@BrowserStack)
- Yarn package manager in favour of npm (npm still works)
- Configurable reverse geocoding server
- [A lot more in the commit history](https://github.com/ffrgb/meshviewer/commits/develop)
### Demo
Embedded: https://regensburg.freifunk.net/netz/karte/
Standalone: https://regensburg.freifunk.net/meshviewer/
## Documentation
Documentation moved to [meshviewer.gitbooks.io](https://meshviewer.gitbooks.io/documentation/content/).
- Read: https://meshviewer.gitbooks.io/documentation/content/
- PDF, Mobi, ePub & edit: https://www.gitbook.com/book/meshviewer/documentation/details
#### Why move the documentation?
- Search available
- Multiple pages
- Less doc commits, faster changes
- Export as PDF, Mobi, ePub
## Sponsoring / Supporting
- [BrowserStack](https://www.browserstack.com/) for providing an awesome testing service for hundreds of browsers
- [Travis CI](https://travis-ci.com/) for building meshviewer on every push and pull request
- [Travis CI](https://travis-ci.org/) for building meshviewer on every push and pull request
- [Scrutinizer CI](https://scrutinizer-ci.com/g/ffrgb/meshviewer/) for testing code quality on every push and pull request
- [POEditor](https://poeditor.com/join/project/VZBjPNNic9) for providing an easy non-developer translation environment
- [Crowdin](https://crowdin.com/) for providing an easy non-developer translation environment
These tools need a lot of infrastructures and provide a free account for open source software.

26
app.js
View File

@ -9,21 +9,21 @@ require.config({
'moment': '../node_modules/moment/moment',
// d3 modules indirect dependencies
// by d3-zoom: d3-drag
'd3-ease': '../node_modules/d3-ease/dist/d3-ease',
'd3-transition': '../node_modules/d3-transition/dist/d3-transition',
'd3-color': '../node_modules/d3-color/dist/d3-color',
'd3-interpolate': '../node_modules/d3-interpolate/dist/d3-interpolate',
'd3-ease': '../node_modules/d3-ease/build/d3-ease',
'd3-transition': '../node_modules/d3-transition/build/d3-transition',
'd3-color': '../node_modules/d3-color/build/d3-color',
'd3-interpolate': '../node_modules/d3-interpolate/build/d3-interpolate',
// by d3-force
'd3-collection': '../node_modules/d3-collection/dist/d3-collection',
'd3-dispatch': '../node_modules/d3-dispatch/dist/d3-dispatch',
'd3-quadtree': '../node_modules/d3-quadtree/dist/d3-quadtree',
'd3-timer': '../node_modules/d3-timer/dist/d3-timer',
'd3-collection': '../node_modules/d3-collection/build/d3-collection',
'd3-dispatch': '../node_modules/d3-dispatch/build/d3-dispatch',
'd3-quadtree': '../node_modules/d3-quadtree/build/d3-quadtree',
'd3-timer': '../node_modules/d3-timer/build/d3-timer',
// by d3-drag: d3-selection
// d3 modules dependencies
'd3-selection': '../node_modules/d3-selection/dist/d3-selection',
'd3-force': '../node_modules/d3-force/dist/d3-force',
'd3-zoom': '../node_modules/d3-zoom/dist/d3-zoom',
'd3-drag': '../node_modules/d3-drag/dist/d3-drag',
'd3-selection': '../node_modules/d3-selection/build/d3-selection',
'd3-force': '../node_modules/d3-force/build/d3-force',
'd3-zoom': '../node_modules/d3-zoom/build/d3-zoom',
'd3-drag': '../node_modules/d3-drag/build/d3-drag',
'snabbdom': '../node_modules/snabbdom/dist/snabbdom-patch',
'rbush': '../node_modules/rbush/rbush',
'helper': 'utils/helper'
@ -37,5 +37,5 @@ require.config({
});
require(['main'], function (main) {
main();
main(jsonData);
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 485 B

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 886 B

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,6 +1,5 @@
{
"name": "Meshviewer",
"short_name": "Meshviewer",
"name": "Freifunk Regensburg",
"icons": [
{
"src": "./android-chrome-192x192.png",
@ -15,7 +14,5 @@
],
"theme_color": "#dc0067",
"background_color": "#dc0067",
"start_url": ".",
"display": "standalone",
"orientation": "portrait"
"display": "standalone"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -1 +1 @@
{"result":{"status":"success"},"favicon":{"package_url":"https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/favicon_package_v0.16.zip","files_urls":["https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/android-chrome-192x192.png","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/android-chrome-512x512.png","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/apple-touch-icon.png","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/browserconfig.xml","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/favicon-16x16.png","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/favicon-32x32.png","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/favicon.ico","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/mstile-144x144.png","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/mstile-150x150.png","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/mstile-310x150.png","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/mstile-310x310.png","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/mstile-70x70.png","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/safari-pinned-tab.svg","https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/package_files/site.webmanifest"],"html_code":"<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"./apple-touch-icon.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"./favicon-32x32.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"./favicon-16x16.png\">\n<link rel=\"manifest\" href=\"./site.webmanifest\">\n<link rel=\"mask-icon\" href=\"./safari-pinned-tab.svg\" color=\"#dc0067\">\n<link rel=\"shortcut icon\" href=\"./favicon.ico\">\n<meta name=\"apple-mobile-web-app-title\" content=\"<!-- inject:title --><!-- endinject -->\">\n<meta name=\"application-name\" content=\"<!-- inject:title --><!-- endinject -->\">\n<meta name=\"msapplication-TileColor\" content=\"#dc0067\">\n<meta name=\"msapplication-TileImage\" content=\"./mstile-144x144.png\">\n<meta name=\"msapplication-config\" content=\"./browserconfig.xml\">\n<meta name=\"theme-color\" content=\"#dc0067\">","compression":"true","overlapping_markups":["link[rel=\"apple-touch-icon\"]","meta[name=\"apple-mobile-web-app-title\"]","link[rel=\"shortcut\"]","link[rel=\"shortcut icon\"]","link[rel=\"icon\",sizes=\"16x16\"]","link[rel=\"icon\",sizes=\"32x32\"]","meta[name=\"msapplication-TileColor\"]","meta[name=\"msapplication-TileImage\"]","meta[name=\"msapplication-config\"]","meta[name=\"application-name\"]","link[rel=\"manifest\"]","meta[name=\"theme-color\"]","link[rel=\"mask-icon\"]"]},"files_location":{"type":"path","path":"."},"preview_picture_url":"https://realfavicongenerator.net/files/ed9ef5a59ae048602fb9a5b74436696e43a575ce/favicon_preview.png","version":"0.16"}
{"result":{"status":"success"},"favicon":{"package_url":"https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/favicons.zip","files_urls":["https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/android-chrome-192x192.png","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/android-chrome-512x512.png","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/apple-touch-icon.png","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/browserconfig.xml","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/favicon-16x16.png","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/favicon-32x32.png","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/favicon.ico","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/manifest.json","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/mstile-144x144.png","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/mstile-150x150.png","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/mstile-310x150.png","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/mstile-310x310.png","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/mstile-70x70.png","https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/package_files/safari-pinned-tab.svg"],"html_code":"<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"./apple-touch-icon.png\">\n<link rel=\"icon\" type=\"image/png\" href=\"./favicon-32x32.png\" sizes=\"32x32\">\n<link rel=\"icon\" type=\"image/png\" href=\"./favicon-16x16.png\" sizes=\"16x16\">\n<link rel=\"manifest\" href=\"./manifest.json\">\n<link rel=\"mask-icon\" href=\"./safari-pinned-tab.svg\" color=\"#dc0067\">\n<link rel=\"shortcut icon\" href=\"./favicon.ico\">\n<meta name=\"apple-mobile-web-app-title\" content=\"Freifunk Regensburg\">\n<meta name=\"application-name\" content=\"Freifunk Regensburg\">\n<meta name=\"msapplication-TileColor\" content=\"#dc0067\">\n<meta name=\"msapplication-TileImage\" content=\"./mstile-144x144.png\">\n<meta name=\"msapplication-config\" content=\"./browserconfig.xml\">\n<meta name=\"theme-color\" content=\"#dc0067\">","compression":"true","overlapping_markups":["link[rel=\"apple-touch-icon\"]","meta[name=\"apple-mobile-web-app-title\"]","link[rel=\"shortcut\"]","link[rel=\"shortcut icon\"]","link[rel=\"icon\",sizes=\"16x16\"]","link[rel=\"icon\",sizes=\"32x32\"]","meta[name=\"msapplication-TileColor\"]","meta[name=\"msapplication-TileImage\"]","meta[name=\"msapplication-config\"]","meta[name=\"application-name\"]","link[rel=\"manifest\"]","meta[name=\"theme-color\"]","link[rel=\"mask-icon\"]"]},"files_location":{"type":"path","path":"."},"preview_picture_url":"https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/favicon_preview.png","version":"0.14"}

BIN
assets/icons/fonts/icon.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -7,9 +7,9 @@ $cache-breaker: unique-id();
font-family: 'ionicons';
font-style: normal;
font-weight: normal;
src: url('fonts/meshviewer.woff2?rel=#{$cache-breaker}') format('woff2'),
url('fonts/meshviewer.woff?rel=#{$cache-breaker}') format('woff'),
url('fonts/meshviewer.ttf?rel=#{$cache-breaker}') format('truetype');
src: url('fonts/icon.woff2?rel=#{$cache-breaker}') format('woff2'),
url('fonts/icon.woff?rel=#{$cache-breaker}') format('woff'),
url('fonts/icon.ttf?rel=#{$cache-breaker}') format('truetype');
}
[class^='ion-'],
@ -49,5 +49,3 @@ $cache-breaker: unique-id();
@include icon('arrow-resize', '\f264');
@include icon('arrow-left-c', '\f108');
@include icon('arrow-right-c', '\f10b');
@include icon('full-enter', '\e901');
@include icon('full-exit', '\e900');

View File

@ -1,194 +0,0 @@
module.exports = function () {
return {
'reverseGeocodingApi': 'https://nominatim.openstreetmap.org/reverse',
'maxAge': 14,
'maxAgeAlert': 3,
'nodeZoom': 20,
'labelZoom': 13,
'clientZoom': 15,
'fullscreen': true,
'fullscreenFrame': true,
'nodeAttr': [
// value can be a node attribute (1 depth) or a a function in utils/node with prefix show
{
'name': 'node.status',
'value': 'Status'
},
{
'name': 'node.gateway',
'value': 'Gateway'
},
{
'name': 'node.coordinates',
'value': 'GeoURI'
},
// {
// "name": "node.contact",
// "value": "owner"
// },
// Examples for functions
// {
// // no name will remove first column
// 'value': function (d) {
// var moment = require('moment');
// var V = require('snabbdom').default;
// return V.h('td', { props: { colSpan: 2 }, style: { background: '#49a' } },
// _.t('sidebar.nodeOnline') + ' translate, ' + moment(d.firstseen).get('month') +
// ' Month require libs like moment, access config ' + config.siteName);
// }
// },
// {
// 'name': 'Neighbour first seen',
// 'value': function (d, nodeDict) {
// return nodeDict[d.gateway_nexthop].firstseen.format() + 'access node object';
// }
// },
{
'name': 'node.hardware',
'value': 'model'
},
{
'name': 'node.primaryMac',
'value': 'mac'
},
{
'name': 'node.firmware',
'value': 'Firmware'
},
{
'name': 'node.uptime',
'value': 'Uptime'
},
{
'name': 'node.firstSeen',
'value': 'FirstSeen'
},
{
'name': 'node.systemLoad',
'value': 'Load'
},
{
'name': 'node.ram',
'value': 'RAM'
},
{
'name': 'node.ipAddresses',
'value': 'IPs'
},
{
'name': 'node.update',
'value': 'Autoupdate'
},
{
'name': 'node.domain',
'value': 'Domain'
},
{
'name': 'node.clients',
'value': 'Clients'
}
],
'supportedLocale': [
'en',
'de',
'cz',
'fr',
'tr',
'ru'
],
// Color configs
'icon': {
'base': {
'fillOpacity': 0.6,
'opacity': 0.6,
'weight': 2,
'radius': 6,
'className': 'stroke-first'
},
'online': {
'color': '#1566A9',
'fillColor': '#1566A9'
},
'offline': {
'color': '#D43E2A',
'fillColor': '#D43E2A',
'radius': 3
},
'lost': {
'color': '#D43E2A',
'fillColor': '#D43E2A',
'radius': 4
},
'alert': {
'color': '#D43E2A',
'fillColor': '#D43E2A',
'radius': 5
},
'new': {
'color': '#1566A9',
'fillColor': '#93E929'
}
},
'client': {
'wifi24': 'rgba(220, 0, 103, 0.7)',
'wifi5': 'rgba(10, 156, 146, 0.7)',
'other': 'rgba(227, 166, 25, 0.7)'
},
'map': {
'labelNewColor': '#459c18',
'tqFrom': '#F02311',
'tqTo': '#04C714',
'highlightNode': {
'color': '#ad2358',
'weight': 8,
'fillOpacity': 1,
'opacity': 0.4,
'className': 'stroke-first'
},
'highlightLink': {
'weight': 4,
'opacity': 1,
'dashArray': '5, 10'
}
},
'forceGraph': {
'nodeColor': '#fff',
'nodeOfflineColor': '#D43E2A',
'highlightColor': 'rgba(255, 255, 255, 0.2)',
'labelColor': '#fff',
'tqFrom': '#770038',
'tqTo': '#dc0067',
'zoomModifier': 1
},
'locate': {
'outerCircle': {
'stroke': false,
'color': '#4285F4',
'opacity': 1,
'fillOpacity': 0.3,
'clickable': false,
'radius': 16
},
'innerCircle': {
'stroke:': true,
'color': '#ffffff',
'fillColor': '#4285F4',
'weight': 1.5,
'clickable': false,
'opacity': 1,
'fillOpacity': 1,
'radius': 7
},
'accuracyCircle': {
'stroke': true,
'color': '#4285F4',
'weight': 1,
'clickable': false,
'opacity': 0.7,
'fillOpacity': 0.2
}
},
'cacheBreaker': '<!-- inject:cache-breaker -->'
};
};

52
config.default.json Normal file
View File

@ -0,0 +1,52 @@
// Gulp will remove all comments
{
"reverseGeocodingApi": "https://nominatim.openstreetmap.org/reverse",
"maxAge": 14,
"maxAgeAlert": 3,
"nodeZoom": 18,
"labelZoom": 13,
"clientZoom": 15,
"nodeInfobox": {
"contact": false,
"hardwareUsage": true
},
"supportedLocale": [
"en",
"de",
"fr",
"ru"
],
"icon": {
"base": {
"fillOpacity": 0.6,
"opacity": 0.6,
"weight": 2,
"radius": 6,
"className": "stroke-first"
},
"online": {
"color": "#1566A9",
"fillColor": "#1566A9"
},
"offline": {
"color": "#D43E2A",
"fillColor": "#D43E2A",
"radius": 3
},
"lost": {
"color": "#D43E2A",
"fillColor": "#D43E2A",
"radius": 4
},
"alert": {
"color": "#D43E2A",
"fillColor": "#D43E2A",
"radius": 5
},
"new": {
"color": "#1566A9",
"fillColor": "#93E929"
}
},
"cacheBreaker": "<!-- inject:cache-breaker -->"
}

View File

@ -1,89 +0,0 @@
module.exports = function () {
return {
// Variables are NODE_ID and NODE_NAME (only a-z0-9\- other chars are replaced with _)
'nodeInfos': [
{
'name': 'Clientstatistik',
'href': 'https://statistik.freifunk-troisdorf.de/d/000000001/node-stats?orgId=1&var-node={NODE_ID}&var-saveinterval=60',
'image': 'https://statistik.freifunk-troisdorf.de/render/d-solo/000000001/node-stats?orgId=1&var-node={NODE_ID}&var-saveinterval=60&theme=light&panelId=1&width=1000&height=500&tz=Europe%2FBerlin',
'title': 'Knoten {NODE_ID}'
},
{
'name': 'Traffic',
'href': 'https://statistik.freifunk-troisdorf.de/d/000000001/node-stats?orgId=1&var-node={NODE_ID}&var-saveinterval=60',
'image': 'https://statistik.freifunk-troisdorf.de/render/d-solo/000000001/node-stats?orgId=1&var-node={NODE_ID}&var-saveinterval=60&theme=light&panelId=2&width=1000&height=500&tz=Europe%2FBerlin',
'title': 'Knoten {NODE_ID}'
}
],
// Array of data provider are supported
'dataPath': [
// 'https://map.freifunk-troisdorf.de/data/tdf4/',
// 'https://map.freifunk-troisdorf.de/data/tdf5/',
// 'https://map.freifunk-troisdorf.de/data/tdf6/',
// 'https://map.freifunk-troisdorf.de/data/rifu/'
'https://map.freifunk-troisdorf.de/data/api/'
],
'siteName': 'Freifunk Troisdorf',
'maxAge': 7,
'mapLayers': [
{
'name': 'Freifunk Rhein-Sieg',
// Please ask Freifunk Rhein Sieg before using its tile server c- example with retina tiles
'url': 'https://tile.freifunk-rhein-sieg.net/tile/{z}/{x}/{y}.png',
'config': {
'maxZoom': 20,
'attribution': '<a href="https://freifunk-rhein-sieg.net/" target="_blank">&copy; Freifunk-Rhein-Sieg</a> <a href="http://www.openstreetmap.org/about/" target="_blank">&copy; OpenStreetMap contributors</a>'
}
},
{
'name': 'OSM',
'url': 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
'config': {
'maxZoom': 20,
'attribution': '<a href="http://www.openmaptiles.org/" target="_blank">&copy; OpenMapTiles</a> <a href="http://www.openstreetmap.org/about/" target="_blank">&copy; OpenStreetMap contributors</a>'
}
}
],
// Set a visible frame
'fixedCenter': [
// Northwest
[
50.8428,
7.0367
],
// Southeast
[
50.776,
7.1919
]
],
'domainNames': [
{
'site': 'tdf',
'name': 'Troisdorf'
},
{
'site': 'inn',
'name': 'Innenstadt'
},
{
'site': 'rifu',
'name': 'Richtfunk'
},
{
'site': 'flu',
'name': 'Soziale Netze'
}
],
'linkList': [
{
'title': 'Impressum',
'href': 'http://freifunk-troisdorf.de/kontakt/impressum/'
},
{
'title': 'Datenschutz',
'href': 'http://freifunk-troisdorf.de/datenschutz/'
}
]
};
};

90
config.json Normal file
View File

@ -0,0 +1,90 @@
// Gulp will remove all comments
{
// Variables are NODE_ID and NODE_NAME (only a-z0-9\- other chars are replaced with _)
"nodeInfos": [
{
"name": "Clientstatistik",
"href": "https://regensburg.freifunk.net/netz/statistik/node/{NODE_ID}/",
"image": "https://grafana.regensburg.freifunk.net/render/dashboard-solo/db/ffrgb-all-nodes-integration?panelId=1&from=now-1d&var-nodeid={NODE_ID}&var-host=All&width=650&height=350&theme=light&_t={TIME}",
"title": "Knoten {NODE_ID} - weiteren Statistiken"
},
{
"name": "Trafficstatistik",
"href": "https://regensburg.freifunk.net/netz/statistik/node/{NODE_ID}/",
"image": "https://grafana.regensburg.freifunk.net/render/dashboard-solo/db/ffrgb-all-nodes-integration?panelId=2&from=now-1d&var-nodeid={NODE_ID}&var-host=All&width=650&height=350&theme=light&_t={TIME}",
"title": "Knoten {NODE_ID} - weiteren Statistiken"
}
],
"globalInfos": [
{
"name": "Statistik",
"href": "https://regensburg.freifunk.net/netz/statistik/",
"image": "https://grafana.regensburg.freifunk.net/render/dashboard-solo/db/ffrgb-network-wide-stats?panelId=11&from=now-1y&width=600&height=350&theme=light",
"title": "Jahresstatistik - weiteren Statistiken"
}
],
// String or array of data provider are supported
"dataPath": "https://regensburg.freifunk.net/data/",
"reverseGeocodingApi": "https://regensburg.freifunk.net/geocoding/reverse",
"siteName": "Freifunk Regensburg",
"mapLayers": [
{
"name": "Freifunk Regensburg",
// Please ask Freifunk Regensburg before using its tile server - example with retina tiles
"url": "https://{s}.tiles.ffrgb.net/{z}/{x}/{y}{retina}.png",
"config": {
"maxZoom": 22,
"subdomains": "1",
"attribution": "<a href=\"https://www.mapbox.com/about/maps/\" target=\"_blank\">&copy; Mapbox</a> <a href=\"https://openstreetmap.org/about/\" target=\"_blank\">&copy; OpenStreetMap</a> <a class=\"mapbox-improve-map\" href=\"https://www.mapbox.com/map-feedback/\" target=\"_blank\">Improve this map</a>",
"start": 6
}
},
{
"name": "Freifunk Regensburg Night",
// Please ask Freifunk Regensburg before using its tile server - example with retina and dark tiles
"url": "https://{s}.tiles.ffrgb.net/n/{z}/{x}/{y}{retina}.png",
"config": {
"maxZoom": 22,
"subdomains": "1",
"attribution": "<a href=\"https://www.mapbox.com/about/maps/\" target=\"_blank\">&copy; Mapbox</a> <a href=\"https://openstreetmap.org/about/\" target=\"_blank\">&copy; OpenStreetMap</a> <a class=\"mapbox-improve-map\" href=\"https://www.mapbox.com/map-feedback/\" target=\"_blank\">Improve this map</a>",
"mode": "night",
"start": 19,
"end": 7
}
},
{
"name": "HERE Satellit Hybrid",
// Please use your own API key - Free plan is on right side after the pay plans
"url": "https://{s}.aerial.maps.api.here.com/maptile/2.1/maptile/newest/{variant}/{z}/{x}/{y}/256/png8?app_id=Q40ik5rnMQOpOQ6RrHCr&app_code=kIPJpCtUZMTiQQJiCemX6Q&lg=deu",
"config": {
"attribution": "Map &copy; 1987-2014 <a href=\"http://developer.here.com\">HERE</a>",
"subdomains": "1234",
"variant": "hybrid.day",
"maxZoom": 20
}
}
],
// Set a visible frame
"fixedCenter": [
// Northwest
[
49.3522,
11.7752
],
// Southeast
[
48.7480,
12.8917
]
],
"siteNames": [
{
"site": "ffrgb-bat15",
"name": "Regensburg"
},
{
"site": "ffrgb",
"name": "Regensburg"
}
]
}

3
crowdin.yml Normal file
View File

@ -0,0 +1,3 @@
files:
- source: /locale/en.json
translation: /locale/%two_letters_code%.json

View File

@ -8,11 +8,11 @@ module.exports = function () {
sass: 'scss/**/*.scss',
javascript: ['./app.js', 'lib/**/*.js'],
json: 'locale/*.json',
html: ['html/*.html', './config*.js']
html: ['html/*.html', './config*.json']
},
clean: [build + '/*.map', build + '/vendor', build + '/main.css'],
autoprefixer: ['> 1% in DE'],
browsersync: {
open: false,
server: {
baseDir: build
},

View File

@ -2,12 +2,10 @@ module.exports = function (gulp, plugins, config) {
return function copy() {
gulp.src(['html/*.html', 'assets/favicon/*'])
.pipe(gulp.dest(config.build));
gulp.src(['assets/logo.svg', 'service-worker.js'])
gulp.src(['assets/logo.svg'])
.pipe(gulp.dest(config.build));
gulp.src(['polyfill.js'])
gulp.src(['node_modules/promise-polyfill/promise.js', 'polyfill.js'])
.pipe(gulp.dest(config.build + '/vendor'));
gulp.src(['node_modules/promise-polyfill/dist/polyfill.js'])
.pipe(gulp.dest(config.build + '/vendor/promise'));
return gulp.src(['assets/fonts/*', 'assets/icons/fonts/*'])
.pipe(gulp.dest(config.build + '/fonts'));
};

View File

@ -7,7 +7,7 @@ module.exports = function (gulp, plugins, config) {
design: {
ios: {
pictureAspect: 'backgroundAndMargin',
backgroundColor: '#ffffff',
backgroundColor: '#000000',
margin: '14%',
assets: {
ios6AndPriorIcons: false,
@ -19,7 +19,7 @@ module.exports = function (gulp, plugins, config) {
},
desktopBrowser: {},
windows: {
pictureAspect: 'whiteSilhouette',
pictureAspect: 'noChange',
backgroundColor: '#dc0067',
onConflict: 'override',
assets: {
@ -39,7 +39,7 @@ module.exports = function (gulp, plugins, config) {
manifest: {
name: 'Meshviewer',
display: 'standalone',
orientation: 'portrait',
orientation: 'notSet',
onConflict: 'override',
declared: true
},

View File

@ -1,52 +1,25 @@
const fs = require('fs');
// stringify functions https://gist.github.com/cowboy/3749767
var stringify = function (obj) {
var placeholder = '____PLACEHOLDER____';
var fns = [];
var json = JSON.stringify(obj, function (key, value) {
if (typeof value === 'function') {
fns.push(value);
return placeholder;
}
return value;
}, 2);
json = json.replace(new RegExp('"' + placeholder + '"', 'g'), function () {
return fns.shift();
});
return json;
};
module.exports = function (gulp, plugins, config, env) {
return function html() {
return gulp.src(env.production() ? config.build + '/*.html' : 'html/*.html')
.pipe(plugins.realFavicon.injectFaviconMarkups(JSON.parse(fs.readFileSync(config.faviconData)).favicon.html_code))
.pipe(env.production(plugins.inlineSource({ compress: false })))
.pipe(plugins.inject(gulp.src(['config.js']), {
removeTags: true,
.pipe(plugins.inject(gulp.src(['config.json']), {
starttag: '<!-- inject:config -->',
transform: function () {
delete require.cache[require.resolve('../../config.default')];
delete require.cache[require.resolve('../../config')];
var buildConfig = Object.assign({}, require('../../config.default')(), require('../../config')());
return '<title>' + buildConfig.siteName + ' - loading...</title>' +
'<script>window.config =' +
stringify(buildConfig)
transform: function (filePath, customConfig) {
var defaultConfig = fs.readFileSync('config.default.json', 'utf8');
var buildConfig = Object.assign(
JSON.parse(JSON.minify(defaultConfig)),
JSON.parse(JSON.minify(customConfig.contents.toString('utf8')))
);
return '<script>var jsonData =' +
JSON.stringify(buildConfig)
.replace('<!-- inject:cache-breaker -->',
Math.random().toString(12).substring(7)) +
';</script>';
}
}))
.pipe(plugins.inject(gulp.src(['config.js']), {
removeTags: true,
starttag: '<!-- inject:title -->',
transform: function () {
delete require.cache[require.resolve('../../config.default')];
delete require.cache[require.resolve('../../config')];
var buildConfig = Object.assign({}, require('../../config.default')(), require('../../config')());
return buildConfig.siteName;
';</script>'
;
}
}))
.pipe(env.production(plugins.kyhInlineSource({ compress: false })))
.pipe(plugins.realFavicon.injectFaviconMarkups(JSON.parse(fs.readFileSync(config.faviconData)).favicon.html_code))
.pipe(plugins.cacheBust({
type: 'timestamp'
}))

View File

@ -3,10 +3,7 @@ module.exports = function (gulp, plugins, config, env) {
return gulp.src('app.js')
.pipe(env.development(plugins.sourcemaps.init()))
.pipe(plugins.requirejsOptimize(env.production() ? config.requireJs.prod : config.requireJs.dev))
.on('error', function () {
this.emit('end');
})
.pipe(env.production(plugins.uglify({ output: { comments: 'all' } })))
.pipe(env.production(plugins.uglify({ preserveComments: 'license' })))
.pipe(env.development(plugins.sourcemaps.write('.', { addComment: true })))
.pipe(gulp.dest(config.build));
};

View File

@ -6,9 +6,6 @@ module.exports = function (gulp, plugins, config, env) {
outputStyle: 'compressed',
sourceMap: false
}))
.on('error', function () {
this.emit('end');
})
.pipe(plugins.autoprefixer({
browsers: config.autoprefixer
}))

View File

@ -1,43 +1,23 @@
<!DOCTYPE html>
<html itemscope itemtype="http://schema.org/WebPage">
<html>
<head>
<meta charset="utf-8">
<title>Freifunk Regensburg e.V. - Meshviewer</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<!--<meta name="image" content="https://regensburg.freifunk.net/meshviewer/apple-touch-icon.png">-->
<meta itemprop="name" content="<!-- inject:title --><!-- endinject --> Meshviewer">
<meta name="description" itemprop="description" content="<!-- inject:title --><!-- endinject --> Knotenkarte - Zeigt alle Knoten, Statistiken und Verbindungen auf Karte oder Topologie">
<!--Uncomment & adjust local urls-->
<!--<meta itemprop="image" content="https://regensburg.freifunk.net/meshviewer/android-chrome-512x512.png">-->
<!--<meta property="business:contact_data:locality" content="Regensburg">-->
<!--<meta property="business:contact_data:region" content="Bayern">-->
<meta property="business:contact_data:country_name" content="Germany">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@freifunk">
<meta name="og:title" content="<!-- inject:title --><!-- endinject -->">
<meta name="og:description" content="<!-- inject:title --><!-- endinject --> Knotenkarte - Zeigt alle Knoten, Statistiken und Verbindungen auf Karte oder Topologie">
<!--<meta name="og:image" content="https://regensburg.freifunk.net/meshviewer/android-chrome-512x512.png">-->
<!--<meta name="og:url" content="https://regensburg.freifunk.net/meshviewer/">-->
<meta name="og:site_name" content="<!-- inject:title --><!-- endinject -->">
<meta name="og:type" content="website">
<link rel="stylesheet" href="main.css" inline>
<link rel="stylesheet" class="css-mode night" media="not" href="night.css" inline>
<!-- inject:config -->
<!-- contents of html partials will be injected here -->
<!-- endinject -->
<script src="vendor/polyfill.js" inline></script>
<script src="vendor/promise/polyfill.js" inline></script>
<script src="vendor/promise.js" inline></script>
<script src="app.js"></script>
</head>
<body>
<div class="loader">
<p>
Lade<br />
<img inline src="logo.svg" class="spinner" alt="Loading ..."/>
<img inline src="logo.svg" class="spinner" />
<br />
Karten &amp; Knoten...
</p>

View File

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><!-- inject:title --><!-- endinject --></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="main.css" inline>
</head>
<body>
<div class="loader">
<p>
You are Offline!<br />
<img inline src="logo.svg" class="spinner" alt="Loading ..."/>
<br />
No connection available.
<br /><br /><button onclick="location.reload(true)" class="btn text" aria-label="Try to reload">Try to reload</button><br />
</p>
<noscript>
<strong>JavaScript required</strong>
</noscript>
</div>
</body>
</html>

View File

@ -4,18 +4,7 @@ define(function () {
return function () {
this.render = function render(d) {
d.innerHTML = _.t('sidebar.aboutInfo') +
'<h4>' + _.t('node.nodes') + '</h4>' +
'<p class="legend">' +
'<span class="legend-new"><span class="symbol"></span> ' + _.t('sidebar.nodeNew') + '</span>' +
'<span class="legend-online"><span class="symbol"></span> ' + _.t('sidebar.nodeOnline') + '</span>' +
'<span class="legend-offline"><span class="symbol"></span> ' + _.t('sidebar.nodeOffline') + '</span>' +
'</p>' +
'<h4>' + _.t('node.clients') + '</h4>' +
'<p class="legend">' +
'<span class="legend-24ghz"><span class="symbol"></span> 2.4 GHz</span>' +
'<span class="legend-5ghz"><span class="symbol"></span> 5 GHz</span>' +
'<span class="legend-others"><span class="symbol"></span> ' + _.t('others') + '</span>' +
'</p>' +
'<h3>AGPL 3</h3>' +
'<p>Copyright (C) Milan Pässler</p>' +

View File

@ -97,7 +97,8 @@ define(['filters/nodefilter'], function (NodeFilter) {
setData: setData,
addFilter: addFilter,
removeFilter: removeFilter,
watchFilters: watchFilters
watchFilters: watchFilters,
refresh: refresh
};
};
});

View File

@ -22,7 +22,6 @@ define(function () {
var button = document.createElement('button');
button.classList.add('ion-close');
button.setAttribute('aria-label', _.t('remove'));
button.onclick = function onclick() {
distributor.removeFilter(d);
};

View File

@ -16,7 +16,7 @@ define(function () {
}
function run(d) {
return d.hostname.toLowerCase().includes(input.value.toLowerCase());
return (d.nodeinfo !== undefined ? d.nodeinfo.hostname.toLowerCase().includes(input.value.toLowerCase()) : '');
}
function setRefresh(f) {
@ -26,7 +26,6 @@ define(function () {
function render(el) {
input.type = 'search';
input.placeholder = _.t('sidebar.nodeFilter');
input.setAttribute('aria-label', _.t('sidebar.nodeFilter'));
input.addEventListener('input', refresh);
el.classList.add('filter-node');
el.classList.add('ion-filter');

View File

@ -12,8 +12,26 @@ define(function () {
}
}
n.links = data.links.filter(function (d) {
return filter(d.source) && filter(d.target);
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 (r) {
filteredIds.add(d.id);
}
return r;
});
n.graph.links = data.graph.links.filter(function (d) {
return filteredIds.has(d.source.id) && filteredIds.has(d.target.id);
});
return n;

View File

@ -1,8 +1,8 @@
define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'd3-timer', 'd3-ease', 'd3-interpolate', 'utils/math', 'forcegraph/draw'],
function (d3Selection, d3Force, d3Zoom, d3Drag, d3Timer, d3Ease, d3Interpolate, math, draw) {
define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegraph/draw'],
function (d3Selection, d3Force, d3Zoom, d3Drag, math, draw) {
'use strict';
return function (linkScale, sidebar) {
return function (config, linkScale, sidebar, router) {
var self = this;
var el;
var canvas;
@ -14,18 +14,15 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'd3-timer', 'd3-ease',
var intNodes = [];
var dictNodes = {};
var intLinks = [];
var movetoTimer;
var initial = 1.8;
var NODE_RADIUS_DRAG = 10;
var NODE_RADIUS_SELECT = 15;
var LINK_RADIUS_SELECT = 12;
var ZOOM_ANIMATE_DURATION = 350;
var ZOOM_MIN = 1 / 8;
var ZOOM_MAX = 3;
var FORCE_ALPHA = 0.01;
var FORCE_ALPHA = 0.3;
draw.setTransform(transform);
@ -35,43 +32,9 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'd3-timer', 'd3-ease',
draw.setMaxArea(canvas.width, canvas.height);
}
function transformPosition(p) {
transform.x = p.x;
transform.y = p.y;
transform.k = p.k;
}
function moveTo(callback, forceMove) {
clearTimeout(movetoTimer);
if (!forceMove && force.alpha() > 0.3) {
movetoTimer = setTimeout(function timerOfMoveTo() {
moveTo(callback);
}, 300);
return;
}
var result = callback();
var x = result[0];
var y = result[1];
var k = result[2];
var end = { k: k };
end.x = (canvas.width + sidebar.getWidth()) / 2 - x * k;
end.y = canvas.height / 2 - y * k;
var start = { x: transform.x, y: transform.y, k: transform.k };
var interpolate = d3Interpolate.interpolateObject(start, end);
var timer = d3Timer.timer(function (t) {
if (t >= ZOOM_ANIMATE_DURATION) {
timer.stop();
return;
}
var v = interpolate(d3Ease.easeQuadInOut(t / ZOOM_ANIMATE_DURATION));
transformPosition(v);
window.requestAnimationFrame(redraw);
});
function moveTo(x, y) {
transform.x = (canvas.width + sidebar()) / 2 - x * transform.k;
transform.y = canvas.height / 2 - y * transform.k;
}
function onClick() {
@ -83,7 +46,7 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'd3-timer', 'd3-ease',
var n = force.find(e[0], e[1], NODE_RADIUS_SELECT);
if (n !== undefined) {
router.fullUrl({ node: n.o.node_id });
router.fullUrl({ node: n.o.node.nodeinfo.node_id });
return;
}
@ -122,16 +85,16 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'd3-timer', 'd3-ease',
forceLink = d3Force.forceLink()
.distance(function (d) {
if (d.o.type.indexOf('vpn') === 0) {
if (d.o.vpn) {
return 0;
}
return 75;
})
.strength(function (d) {
if (d.o.type.indexOf('vpn') === 0) {
if (d.o.vpn) {
return 0.02;
}
return Math.max(0.5, d.o.source_tq);
return Math.max(0.5, 1 / d.o.tq);
});
var zoom = d3Zoom.zoom()
@ -148,8 +111,7 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'd3-timer', 'd3-ease',
.force('x', d3Force.forceX().strength(0.02))
.force('y', d3Force.forceY().strength(0.02))
.force('collide', d3Force.forceCollide())
.on('tick', redraw)
.alphaDecay(0.025);
.on('tick', redraw);
var drag = d3Drag.drag()
.subject(function () {
@ -198,11 +160,13 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'd3-timer', 'd3-ease',
});
self.setData = function setData(data) {
intNodes = data.nodes.all.map(function (d) {
var e = dictNodes[d.node_id];
if (!e) {
intNodes = data.graph.nodes.map(function (d) {
var e;
if (d.id in dictNodes) {
e = dictNodes[d.id];
} else {
e = {};
dictNodes[d.node_id] = e;
dictNodes[d.id] = e;
}
e.o = d;
@ -210,67 +174,65 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'd3-timer', 'd3-ease',
return e;
});
intLinks = data.links.filter(function (d) {
return data.nodeDict[d.source.node_id].is_online && data.nodeDict[d.target.node_id].is_online;
}).map(function (d) {
return {
o: d,
source: dictNodes[d.source.node_id],
target: dictNodes[d.target.node_id],
color: linkScale(d.source_tq),
color_to: linkScale(d.target_tq)
};
intLinks = data.graph.links.map(function (d) {
var e = {};
e.o = d;
e.source = dictNodes[d.source.id];
e.target = dictNodes[d.target.id];
e.color = linkScale(1 / d.tq);
return e;
});
force.nodes(intNodes);
forceLink.links(intLinks);
force.alpha(initial).velocityDecay(0.15).restart();
if (initial === 1.8) {
initial = 0.5;
}
force.alpha(1).restart();
resizeCanvas();
};
self.resetView = function resetView() {
moveTo(function calcToReset() {
draw.setHighlight(null);
return [0, 0, (ZOOM_MIN + config.forceGraph.zoomModifier) / 2];
}, true);
draw.setHighlight(null);
transform.k = (ZOOM_MIN + 1) / 2;
moveTo(0, 0);
redraw();
};
self.gotoNode = function gotoNode(d) {
moveTo(function calcToNode() {
draw.setHighlight({ type: 'node', id: d.node_id });
var n = dictNodes[d.node_id];
if (n) {
return [n.x, n.y, (ZOOM_MAX + 1) / 2];
for (var i = 0; i < intNodes.length; i++) {
var n = intNodes[i];
if (n.o.node.nodeinfo.node_id !== d.nodeinfo.node_id) {
continue;
}
return self.resetView();
});
draw.setHighlight({ type: 'node', o: n.o.node });
transform.k = (ZOOM_MAX + 1) / 2;
moveTo(n.x, n.y);
break;
}
redraw();
};
self.gotoLink = function gotoLink(d) {
moveTo(function calcToLink() {
draw.setHighlight({ type: 'link', id: d[0].id });
var l = intLinks.find(function (link) {
return link.o.id === d[0].id;
});
if (l) {
return [(l.source.x + l.target.x) / 2, (l.source.y + l.target.y) / 2, (ZOOM_MAX / 2) + ZOOM_MIN];
draw.setHighlight({ type: 'link', o: d });
for (var i = 0; i < intLinks.length; i++) {
var l = intLinks[i];
if (l.o !== d) {
continue;
}
return self.resetView();
});
moveTo((l.source.x + l.target.x) / 2, (l.source.y + l.target.y) / 2);
break;
}
redraw();
};
self.gotoLocation = function gotoLocation() {
// ignore
};
self.destroy = function destroy() {
force.stop();
canvas.parentNode.removeChild(canvas);
canvas.remove();
force = null;
if (el.parentNode) {

View File

@ -4,39 +4,50 @@ define(['helper'], function (helper) {
var ctx;
var width;
var height;
var transform;
var highlight;
var nodeColor = '#fff';
var clientColor = '#e6324b';
var highlightColor = 'rgba(255, 255, 255, 0.2)';
var labelColor = '#fff';
var NODE_RADIUS = 15;
var LINE_RADIUS = 12;
function drawDetailNode(d) {
if (transform.k > 1 && d.o.is_online) {
helper.positionClients(ctx, d, Math.PI, d.o, 15);
if (transform.k > 1) {
ctx.beginPath();
helper.positionClients(ctx, d, Math.PI, d.o.node.statistics.clients, 15);
ctx.fillStyle = clientColor;
ctx.fill();
ctx.beginPath();
var name = d.o.node_id;
if (d.o) {
name = d.o.hostname;
if (d.o.node && d.o.node.nodeinfo) {
name = d.o.node.nodeinfo.hostname;
}
ctx.textAlign = 'center';
ctx.fillStyle = config.forceGraph.labelColor;
ctx.fillStyle = labelColor;
ctx.fillText(name, d.x, d.y + 20);
}
}
function drawHighlightNode(d) {
if (highlight && highlight.type === 'node' && d.o.node_id === highlight.id) {
if (highlight && highlight.type === 'node' && d.o.node === highlight.o) {
ctx.arc(d.x, d.y, NODE_RADIUS * 1.5, 0, 2 * Math.PI);
ctx.fillStyle = config.forceGraph.highlightColor;
ctx.fillStyle = highlightColor;
ctx.fill();
ctx.beginPath();
}
}
function drawHighlightLink(d, to) {
if (highlight && highlight.type === 'link' && d.o.id === highlight.id) {
if (highlight && highlight.type === 'link' && d.o === highlight.o) {
ctx.lineTo(to[0], to[1]);
ctx.strokeStyle = config.forceGraph.highlightColor;
ctx.strokeStyle = highlightColor;
ctx.lineWidth = LINE_RADIUS * 2;
ctx.lineCap = 'round';
ctx.stroke();
@ -53,17 +64,9 @@ define(['helper'], function (helper) {
drawHighlightNode(d);
if (d.o.is_online) {
ctx.arc(d.x, d.y, 8, 0, 2 * Math.PI);
if (d.o.is_gateway) {
ctx.rect(d.x - 9, d.y - 9, 18, 18);
}
ctx.fillStyle = config.forceGraph.nodeColor;
} else {
ctx.arc(d.x, d.y, 6, 0, 2 * Math.PI);
ctx.fillStyle = config.forceGraph.nodeOfflineColor;
}
ctx.moveTo(d.x + 3, d.y);
ctx.arc(d.x, d.y, 8, 0, 2 * Math.PI);
ctx.fillStyle = nodeColor;
ctx.fill();
drawDetailNode(d);
@ -73,7 +76,7 @@ define(['helper'], function (helper) {
var zero = transform.invert([0, 0]);
var area = transform.invert([width, height]);
if (d.source.x < zero[0] && d.target.x < zero[0] || d.source.y < zero[1] && d.target.y < zero[1] ||
d.source.x > area[0] && d.target.x > area[0] || d.source.y > area[1] && d.target.y > area[1]) {
d.source.x > area[0] && d.target.x > area[0] || d.source.y > area[1] && d.target.y > area[1]) {
return;
}
ctx.beginPath();
@ -82,13 +85,9 @@ define(['helper'], function (helper) {
to = drawHighlightLink(d, to);
var grd = ctx.createLinearGradient(d.source.x, d.source.y, d.target.x, d.target.y);
grd.addColorStop(0.45, d.color);
grd.addColorStop(0.55, d.color_to);
ctx.lineTo(to[0], to[1]);
ctx.strokeStyle = grd;
if (d.o.type.indexOf('vpn') === 0) {
ctx.strokeStyle = d.color;
if (d.o.vpn) {
ctx.globalAlpha = 0.2;
ctx.lineWidth = 1.5;
} else {

View File

@ -1,143 +1,131 @@
define(['d3-interpolate', 'map', 'sidebar', 'tabs', 'container', 'legend',
'linklist', 'nodelist', 'simplenodelist', 'infobox/main',
'proportions', 'forcegraph', 'title', 'about', 'datadistributor',
'filters/filtergui', 'filters/hostname', 'helper'],
function (d3Interpolate, Map, Sidebar, Tabs, Container, Legend, Linklist,
Nodelist, SimpleNodelist, Infobox, Proportions, ForceGraph,
Title, About, DataDistributor, FilterGUI, HostnameFilter, helper) {
'use strict';
'filters/filtergui', 'filters/hostname'],
function (d3Interpolate, Map, Sidebar, Tabs, Container, Legend, Linklist,
Nodelist, SimpleNodelist, Infobox, Proportions, ForceGraph,
Title, About, DataDistributor, FilterGUI, HostnameFilter) {
'use strict';
return function (language) {
var self = this;
var content;
var contentDiv;
return function (config, router, language) {
var self = this;
var content;
var contentDiv;
var linkScale = d3Interpolate.interpolate(config.map.tqFrom, config.map.tqTo);
var sidebar;
var linkScale = d3Interpolate.interpolate('#F02311', '#04C714');
var sidebar;
var buttons = document.createElement('div');
buttons.classList.add('buttons');
var buttons = document.createElement('div');
buttons.classList.add('buttons');
var fanout = new DataDistributor();
var fanoutUnfiltered = new DataDistributor();
fanoutUnfiltered.add(fanout);
var fanout = new DataDistributor();
var fanoutUnfiltered = new DataDistributor();
fanoutUnfiltered.add(fanout);
function removeContent() {
if (!content) {
return;
function removeContent() {
if (!content) {
return;
}
router.removeTarget(content);
fanout.remove(content);
content.destroy();
content = null;
}
router.removeTarget(content);
fanout.remove(content);
function addContent(K) {
removeContent();
content.destroy();
content = new K(config, linkScale, sidebar.getWidth, router, buttons);
content.render(contentDiv);
content = null;
}
fanout.add(content);
router.addTarget(content);
}
function addContent(K) {
removeContent();
function mkView(K) {
return function () {
addContent(K);
};
}
content = new K(linkScale, sidebar, buttons);
content.render(contentDiv);
var loader = document.getElementsByClassName('loader')[0];
loader.classList.add('hide');
fanout.add(content);
router.addTarget(content);
}
contentDiv = document.createElement('div');
contentDiv.classList.add('content');
document.body.appendChild(contentDiv);
function mkView(K) {
return function () {
addContent(K);
sidebar = new Sidebar(document.body);
contentDiv.appendChild(buttons);
var buttonToggle = document.createElement('button');
buttonToggle.classList.add('ion-eye', 'shadow');
buttonToggle.setAttribute('data-tooltip', _.t('button.switchView'));
buttonToggle.onclick = function onclick() {
var data;
if (content.constructor === Map) {
data = { view: 'graph', lat: undefined, lng: undefined, zoom: undefined };
} else {
data = { view: 'map' };
}
router.fullUrl(data, false, true);
};
}
var loader = document.getElementsByClassName('loader')[0];
loader.classList.add('hide');
buttons.appendChild(buttonToggle);
contentDiv = document.createElement('div');
contentDiv.classList.add('content');
document.body.appendChild(contentDiv);
var title = new Title(config);
sidebar = new Sidebar(document.body);
var header = new Container('header');
var infobox = new Infobox(config, sidebar, router);
var tabs = new Tabs();
var overview = new Container();
var legend = new Legend(config, language);
var newnodeslist = new SimpleNodelist('new', 'firstseen', router, _.t('node.new'));
var lostnodeslist = new SimpleNodelist('lost', 'lastseen', router, _.t('node.missing'));
var nodelist = new Nodelist(router);
var linklist = new Linklist(linkScale, router);
var statistics = new Proportions(config, fanout);
var about = new About();
contentDiv.appendChild(buttons);
fanoutUnfiltered.add(legend);
fanoutUnfiltered.add(newnodeslist);
fanoutUnfiltered.add(lostnodeslist);
fanout.add(nodelist);
fanout.add(linklist);
fanout.add(statistics);
var buttonToggle = document.createElement('button');
buttonToggle.classList.add('ion-eye');
buttonToggle.setAttribute('aria-label', _.t('button.switchView'));
buttonToggle.onclick = function onclick() {
var data;
if (content.constructor === Map) {
data = { view: 'graph', lat: undefined, lng: undefined, zoom: undefined };
} else {
data = { view: 'map' };
}
router.fullUrl(data, false, true);
sidebar.add(header);
header.add(legend);
overview.add(newnodeslist);
overview.add(lostnodeslist);
var filterGUI = new FilterGUI(fanout);
fanout.watchFilters(filterGUI);
header.add(filterGUI);
var hostnameFilter = new HostnameFilter();
fanout.addFilter(hostnameFilter);
sidebar.add(tabs);
tabs.add('sidebar.actual', overview);
tabs.add('node.nodes', nodelist);
tabs.add('node.links', linklist);
tabs.add('sidebar.stats', statistics);
tabs.add('sidebar.about', about);
router.addTarget(title);
router.addTarget(infobox);
router.addView('map', mkView(Map));
router.addView('graph', mkView(ForceGraph));
self.setData = fanoutUnfiltered.setData;
return self;
};
buttons.appendChild(buttonToggle);
if (config.fullscreen || config.fullscreenFrame && window.frameElement) {
var buttonFullscreen = document.createElement('button');
buttonFullscreen.classList.add('ion-full-enter');
buttonFullscreen.setAttribute('aria-label', _.t('button.fullscreen'));
buttonFullscreen.onclick = function onclick() {
helper.fullscreen(buttonFullscreen);
};
buttons.appendChild(buttonFullscreen);
}
var title = new Title();
var header = new Container('header');
var infobox = new Infobox(sidebar, linkScale);
var tabs = new Tabs();
var overview = new Container();
var legend = new Legend(language);
var newnodeslist = new SimpleNodelist('new', 'firstseen', _.t('node.new'));
var lostnodeslist = new SimpleNodelist('lost', 'lastseen', _.t('node.missing'));
var nodelist = new Nodelist();
var linklist = new Linklist(linkScale);
var statistics = new Proportions(fanout);
var about = new About();
fanoutUnfiltered.add(legend);
fanoutUnfiltered.add(newnodeslist);
fanoutUnfiltered.add(lostnodeslist);
fanoutUnfiltered.add(infobox);
fanout.add(nodelist);
fanout.add(linklist);
fanout.add(statistics);
sidebar.add(header);
header.add(legend);
overview.add(newnodeslist);
overview.add(lostnodeslist);
var filterGUI = new FilterGUI(fanout);
fanout.watchFilters(filterGUI);
header.add(filterGUI);
var hostnameFilter = new HostnameFilter();
fanout.addFilter(hostnameFilter);
sidebar.add(tabs);
tabs.add('sidebar.actual', overview);
tabs.add('node.nodes', nodelist);
tabs.add('node.links', linklist);
tabs.add('sidebar.stats', statistics);
tabs.add('sidebar.about', about);
router.addTarget(title);
router.addTarget(infobox);
router.addView('map', mkView(Map));
router.addView('graph', mkView(ForceGraph));
self.setData = fanoutUnfiltered.setData;
return self;
};
});
});

View File

@ -1,89 +1,53 @@
define(['helper', 'snabbdom'], function (helper, V) {
define(['helper'], function (helper) {
'use strict';
V = V.default;
function showStatImg(img, o, d, time) {
var subst = {
'{SOURCE_ID}': d.source.node_id,
'{SOURCE_NAME}': d.source.hostname.replace(/[^a-z0-9\-]/ig, '_'),
'{SOURCE_ADDR}': d.source_addr,
'{SOURCE_MAC}': d.source_mac ? d.source_mac : d.source_addr,
'{TARGET_ID}': d.target.node_id,
'{TARGET_NAME}': d.target.hostname.replace(/[^a-z0-9\-]/ig, '_'),
'{TARGET_ADDR}': d.target_addr,
'{TARGET_MAC}': d.target_mac ? d.target_mac : d.target_addr,
'{TYPE}': d.type,
'{TIME}': time,
'{LOCALE}': _.locale()
};
img.push(V.h('h4', helper.listReplace(o.name, subst)));
img.push(helper.showStat(V, o, subst));
function showStatImg(o, d, time) {
var subst = {};
subst['{SOURCE_ID}'] = d.source.node_id;
subst['{SOURCE_NAME}'] = d.source.node.nodeinfo.hostname.replace(/[^a-z0-9\-]/ig, '_');
subst['{TARGET_ID}'] = d.target.node_id;
subst['{TARGET_NAME}'] = d.target.node.nodeinfo.hostname.replace(/[^a-z0-9\-]/ig, '_');
subst['{TIME}'] = time;
subst['{LOCALE}'] = _.locale();
return helper.showStat(o, subst);
}
return function (el, d, linkScale) {
var self = this;
var header = document.createElement('div');
var table = document.createElement('table');
var images = document.createElement('div');
el.appendChild(header);
el.appendChild(table);
el.appendChild(images);
return function (config, el, router, d) {
var h2 = document.createElement('h2');
var a1 = document.createElement('a');
a1.href = router.generateLink({ node: d.source.node_id });
a1.textContent = d.source.node.nodeinfo.hostname;
h2.appendChild(a1);
self.render = function render() {
var children = [];
var img = [];
var time = d[0].target.lastseen.format('DDMMYYYYHmmss');
var arrow = document.createElement('span');
arrow.classList.add('ion-arrow-right-c');
h2.appendChild(arrow);
header = V.patch(header, V.h('div', V.h('h2', [
V.h('a', {
props: { href: router.generateLink({ node: d[0].source.node_id }) }
}, d[0].source.hostname),
V.h('span', ' - '),
V.h('a', {
props: { href: router.generateLink({ node: d[0].target.node_id }) }
}, d[0].target.hostname)
])));
var a2 = document.createElement('a');
a2.href = router.generateLink({ node: d.target.node_id });
a2.textContent = d.target.node.nodeinfo.hostname;
h2.appendChild(a2);
el.appendChild(h2);
helper.attributeEntry(V, children, 'node.hardware', (d[0].source.model ? d[0].source.model + ' ' : '') +
(d[0].target.model ? d[0].target.model : ''));
helper.attributeEntry(V, children, 'node.distance', helper.showDistance(d[0]));
var attributes = document.createElement('table');
attributes.classList.add('attributes');
d.forEach(function (link) {
children.push(V.h('tr', { props: { className: 'header' } }, [
V.h('th', _.t('node.connectionType')),
V.h('th', link.type)
]));
helper.attributeEntry(V, children, 'node.tq', V.h('span',
{ style: { color: linkScale((link.source_tq + link.target_tq) / 2) } },
helper.showTq(link.source_tq) + ' - ' + helper.showTq(link.target_tq))
);
helper.attributeEntry(attributes, 'node.tq', helper.showTq(d));
helper.attributeEntry(attributes, 'node.distance', helper.showDistance(d));
var hw1 = helper.dictGet(d.source.node.nodeinfo, ['hardware', 'model']);
var hw2 = helper.dictGet(d.target.node.nodeinfo, ['hardware', 'model']);
helper.attributeEntry(attributes, 'node.hardware', hw1 + ' ' + hw2);
if (config.linkTypeInfos) {
config.linkTypeInfos.forEach(function (o) {
showStatImg(img, o, link, time);
});
}
el.appendChild(attributes);
if (config.linkInfos) {
var time = d.target.node.lastseen.format('DDMMYYYYHmmss');
config.linkInfos.forEach(function (linkInfo) {
var h4 = document.createElement('h4');
h4.textContent = linkInfo.name;
el.appendChild(h4);
el.appendChild(showStatImg(linkInfo, d, time));
});
if (config.linkInfos) {
config.linkInfos.forEach(function (o) {
showStatImg(img, o, d[0], time);
});
}
var elNew = V.h('table', children);
table = V.patch(table, elNew);
table.elm.classList.add('attributes');
images = V.patch(images, V.h('div', img));
};
self.setData = function setData(data) {
d = data.links.filter(function (a) {
return a.id === d[0].id;
});
self.render();
};
return self;
}
};
});

View File

@ -1,7 +1,7 @@
define(['helper'], function (helper) {
'use strict';
return function (el, d) {
return function (config, el, router, d) {
var sidebarTitle = document.createElement('h2');
sidebarTitle.textContent = _.t('location.location');
el.appendChild(sidebarTitle);
@ -14,19 +14,16 @@ define(['helper'], function (helper) {
});
var editLat = document.createElement('input');
editLat.setAttribute('aria-label', _.t('location.latitude'));
editLat.type = 'text';
editLat.value = d.lat.toFixed(9);
el.appendChild(createBox('lat', _.t('location.latitude'), editLat));
var editLng = document.createElement('input');
editLng.setAttribute('aria-label', _.t('location.longitude'));
editLng.type = 'text';
editLng.value = d.lng.toFixed(9);
el.appendChild(createBox('lng', _.t('location.longitude'), editLng));
var editUci = document.createElement('textarea');
editUci.setAttribute('aria-label', 'Uci');
editUci.value =
"uci set gluon-node-info.@location[0]='location'; " +
"uci set gluon-node-info.@location[0].share_location='1';" +
@ -44,7 +41,6 @@ define(['helper'], function (helper) {
var btn = document.createElement('button');
btn.classList.add('ion-clipboard');
btn.title = _.t('location.copy');
btn.setAttribute('aria-label', _.t('location.copy'));
btn.onclick = function onclick() {
copy2clip(inputElem.id);
};

View File

@ -1,16 +1,14 @@
define(['infobox/link', 'infobox/node', 'infobox/location'], function (Link, Node, location) {
define(['infobox/link', 'infobox/node', 'infobox/location'], function (link, node, location) {
'use strict';
return function (sidebar, linkScale) {
return function (config, sidebar, router) {
var self = this;
var el;
var node;
var link;
function destroy() {
if (el && el.parentNode) {
el.parentNode.removeChild(el);
node = link = el = undefined;
el = undefined;
sidebar.reveal();
}
}
@ -30,7 +28,6 @@ define(['infobox/link', 'infobox/node', 'infobox/location'], function (Link, Nod
var closeButton = document.createElement('button');
closeButton.classList.add('close');
closeButton.classList.add('ion-close');
closeButton.setAttribute('aria-label', _.t('close'));
closeButton.onclick = function () {
router.fullUrl();
};
@ -39,30 +36,19 @@ define(['infobox/link', 'infobox/node', 'infobox/location'], function (Link, Nod
self.resetView = destroy;
self.gotoNode = function gotoNode(d, nodeDict) {
self.gotoNode = function gotoNode(d) {
create();
node = new Node(el, d, linkScale, nodeDict);
node.render();
node(config, el, router, d);
};
self.gotoLink = function gotoLink(d) {
create();
link = new Link(el, d, linkScale);
link.render();
link(config, el, router, d);
};
self.gotoLocation = function gotoLocation(d) {
create();
location(el, d);
};
self.setData = function setData(d) {
if (typeof node === 'object') {
node.setData(d);
}
if (typeof link === 'object') {
link.setData(d);
}
location(config, el, router, d);
};
return self;

View File

@ -1,194 +1,297 @@
define(['sorttable', 'snabbdom', 'd3-interpolate', 'helper', 'utils/node'],
function (SortTable, V, d3Interpolate, helper, nodef) {
define(['sorttable', 'snabbdom', 'd3-interpolate', 'moment', 'helper'],
function (SortTable, V, d3Interpolate, moment, helper) {
'use strict';
V = V.default;
function showStatImg(o, d) {
var subst = {
'{NODE_ID}': d.node_id,
'{NODE_NAME}': d.hostname.replace(/[^a-z0-9\-]/ig, '_'),
'{TIME}': d.lastseen.format('DDMMYYYYHmmss'),
'{LOCALE}': _.locale()
function showGeoURI(d) {
if (!helper.hasLocation(d)) {
return undefined;
}
return function (el) {
var a = document.createElement('a');
a.textContent = Number(d.nodeinfo.location.latitude.toFixed(6)) + ', ' + Number(d.nodeinfo.location.longitude.toFixed(6));
a.href = 'geo:' + d.nodeinfo.location.latitude + ',' + d.nodeinfo.location.longitude;
el.appendChild(a);
};
return helper.showStat(V, o, subst);
}
return function (el, d, linkScale, nodeDict) {
function nodeLink(node) {
return V.h('a', {
props: {
className: node.is_online ? 'online' : 'offline',
href: router.generateLink({ node: node.node_id })
}, on: {
click: function (e) {
router.fullUrl({ node: node.node_id }, e);
}
}
}, node.hostname);
function showStatus(d) {
return function (el) {
el.classList.add(d.flags.unseen ? 'unseen' : (d.flags.online ? 'online' : 'offline'));
el.textContent = _.t((d.flags.online ? 'node.lastOnline' : 'node.lastOffline'), {
time: d.lastseen.fromNow(),
date: d.lastseen.format('DD.MM.YYYY, H:mm:ss')
});
};
}
function showFirmware(d) {
var release = helper.dictGet(d.nodeinfo, ['software', 'firmware', 'release']);
var base = helper.dictGet(d.nodeinfo, ['software', 'firmware', 'base']);
if (release === null || base === null) {
return undefined;
}
function nodeIdLink(nodeId) {
if (nodeDict[nodeId]) {
return nodeLink(nodeDict[nodeId]);
}
return nodeId;
}
return release + ' / ' + base;
}
function showGateway(node) {
var gatewayCols = [
V.h('span', [
nodeIdLink(node.gateway_nexthop),
V.h('br'),
_.t('node.nexthop')
]),
V.h('span', { props: { className: 'ion-arrow-right-c' } }),
V.h('span', [
nodeIdLink(node.gateway),
V.h('br'),
'IPv4'
])
];
if (node.gateway6 !== undefined) {
gatewayCols.push(V.h('span', [
nodeIdLink(node.gateway6),
V.h('br'),
'IPv6'
]));
}
return V.h('td', { props: { className: 'gateway' } }, gatewayCols);
}
function renderNeighbourRow(n) {
var icons = [V.h('span', { props: { className: 'icon ion-' + (n.link.type.indexOf('wifi') === 0 ? 'wifi' : 'share-alt'), title: _.t(n.link.type) } })];
if (helper.hasLocation(n.node)) {
icons.push(V.h('span', { props: { className: 'ion-location', title: _.t('location.location') } }));
}
return V.h('tr', [
V.h('td', icons),
V.h('td', nodeLink(n.node)),
V.h('td', n.node.clients),
V.h('td', [V.h('a', {
style: {
color: linkScale((n.link.source_tq + n.link.target_tq) / 2)
},
props: {
title: n.link.source.hostname + ' - ' + n.link.target.hostname,
href: router.generateLink({ link: n.link.id })
}, on: {
click: function (e) {
router.fullUrl({ link: n.link.id }, e);
}
}
}, helper.showTq(n.link.source_tq) + ' - ' + helper.showTq(n.link.target_tq))]),
V.h('td', helper.showDistance(n.link))
]);
}
var self = this;
var header = document.createElement('h2');
var table = document.createElement('table');
var images = document.createElement('div');
var neighbours = document.createElement('h3');
var headings = [{
name: '',
sort: function (a, b) {
return a.link.type.localeCompare(b.link.type);
}
}, {
name: 'node.nodes',
sort: function (a, b) {
return a.node.hostname.localeCompare(b.node.hostname);
},
reverse: false
}, {
name: 'node.clients',
class: 'ion-people',
sort: function (a, b) {
return a.node.clients - b.node.clients;
},
reverse: true
}, {
name: 'node.tq',
class: 'ion-connection-bars',
sort: function (a, b) {
return a.link.source_tq - b.link.source_tq;
},
reverse: true
}, {
name: 'node.distance',
class: 'ion-arrow-resize',
sort: function (a, b) {
return (a.link.distance === undefined ? -1 : a.link.distance) -
(b.link.distance === undefined ? -1 : b.link.distance);
},
reverse: true
}];
var tableNeighbour = new SortTable(headings, 1, renderNeighbourRow);
el.appendChild(header);
el.appendChild(table);
el.appendChild(neighbours);
el.appendChild(tableNeighbour.el);
el.appendChild(images);
self.render = function render() {
V.patch(header, V.h('h2', d.hostname));
var children = [];
config.nodeAttr.forEach(function (row) {
var field = d[row.value];
if (typeof row.value === 'function') {
field = row.value(d, nodeDict);
} else if (nodef['show' + row.value] !== undefined) {
field = nodef['show' + row.value](d);
}
if (field) {
if (typeof field !== 'object') {
field = V.h('td', field);
}
children.push(V.h('tr', [
row.name !== undefined ? V.h('th', _.t(row.name)) : null,
field
]));
function showSite(d, config) {
var site = helper.dictGet(d.nodeinfo, ['system', 'site_code']);
var rt = site;
if (config.siteNames) {
config.siteNames.forEach(function (t) {
if (site === t.site) {
rt = t.name;
}
});
}
return rt;
}
children.push(V.h('tr', [
V.h('th', _.t('node.gateway')),
showGateway(d)
]));
function showUptime(d) {
if (!('uptime' in d.statistics)) {
return undefined;
}
var elNew = V.h('table', children);
table = V.patch(table, elNew);
table.elm.classList.add('attributes');
return moment.duration(d.statistics.uptime, 'seconds').humanize();
}
V.patch(neighbours, V.h('h3', _.t('node.link', d.neighbours.length) + ' (' + d.neighbours.length + ')'));
if (d.neighbours.length > 0) {
tableNeighbour.setData(d.neighbours);
tableNeighbour.el.elm.classList.add('node-links');
}
function showFirstseen(d) {
if (!('firstseen' in d)) {
return undefined;
}
if (config.nodeInfos) {
var img = [];
config.nodeInfos.forEach(function (nodeInfo) {
img.push(V.h('h4', nodeInfo.name));
img.push(showStatImg(nodeInfo, d));
});
images = V.patch(images, V.h('div', img));
}
return d.firstseen.fromNow(true);
}
function showClients(d) {
if (!d.flags.online) {
return undefined;
}
return function (el) {
el.appendChild(document.createTextNode(d.statistics.clients > 0 ? d.statistics.clients : _.t('none')));
el.appendChild(document.createElement('br'));
var span = document.createElement('span');
span.classList.add('clients');
span.innerHTML = '<i class="ion-person"></i>'.repeat(d.statistics.clients);
el.appendChild(span);
};
}
self.setData = function setData(data) {
if (data.nodeDict[d.node_id]) {
d = data.nodeDict[d.node_id];
}
self.render();
function showIPs(d) {
var ips = helper.dictGet(d.nodeinfo, ['network', 'addresses']);
if (ips === null) {
return undefined;
}
ips.sort();
return function (el) {
ips.forEach(function (ip, i) {
var link = !ip.startsWith('fe80:');
if (i > 0) {
el.appendChild(document.createElement('br'));
}
if (link) {
var a = document.createElement('a');
a.href = 'http://[' + ip + ']/';
a.textContent = ip;
el.appendChild(a);
} else {
el.appendChild(document.createTextNode(ip));
}
});
};
return self;
}
function showBar(v, width, warning) {
var span = document.createElement('span');
span.classList.add('bar');
var bar = document.createElement('span');
bar.style.width = (width * 100) + '%';
if (warning) {
span.classList.add('warning');
}
span.appendChild(bar);
var label = document.createElement('label');
label.textContent = v;
span.appendChild(label);
return span;
}
function showLoad(d) {
if (!('loadavg' in d.statistics)) {
return undefined;
}
return function (el) {
var value = d.statistics.loadavg.toFixed(2);
var width = d.statistics.loadavg % 1;
var warning = false;
if (d.statistics.loadavg >= d.nodeinfo.hardware.nproc) {
warning = true;
}
el.appendChild(showBar(value, width, warning));
};
}
function showRAM(d) {
if (!('memory_usage' in d.statistics)) {
return undefined;
}
return function (el) {
var value = Math.round(d.statistics.memory_usage * 100) + ' %';
var width = d.statistics.memory_usage;
var warning = false;
if (d.statistics.memory_usage >= 0.8) {
warning = true;
}
el.appendChild(showBar(value, width, warning));
};
}
function showAutoupdate(d) {
var au = helper.dictGet(d.nodeinfo, ['software', 'autoupdater']);
if (!au) {
return undefined;
}
return au.enabled ? _.t('node.activated', { branch: au.branch }) : _.t('node.deactivated');
}
function showStatImg(o, d) {
var subst = {};
subst['{NODE_ID}'] = d.nodeinfo.node_id;
subst['{NODE_NAME}'] = d.nodeinfo.hostname.replace(/[^a-z0-9\-]/ig, '_');
subst['{TIME}'] = d.lastseen.format('DDMMYYYYHmmss');
subst['{LOCALE}'] = _.locale();
return helper.showStat(o, subst);
}
return function (config, el, router, d) {
var linkScale = d3Interpolate.interpolate('#F02311', '#04C714');
function renderNeighbourRow(n) {
var icons = [];
icons.push(V.h('span', { props: { className: n.incoming ? 'ion-arrow-left-c' : 'ion-arrow-right-c' } }));
if (helper.hasLocation(n.node)) {
icons.push(V.h('span', { props: { className: 'ion-location' } }));
}
var name = V.h('a', {
props: {
className: 'online',
href: router.generateLink({ node: n.node.nodeinfo.node_id })
}, on: {
click: function (e) {
router.fullUrl({ node: n.node.nodeinfo.node_id }, e);
}
}
}, n.node.nodeinfo.hostname);
var td1 = V.h('td', icons);
var td2 = V.h('td', name);
var td3 = V.h('td', (n.node.statistics.clients ? n.node.statistics.clients.toString() : '0'));
var td4 = V.h('td', { style: { color: linkScale(1 / n.link.tq) } }, helper.showTq(n.link));
var td5 = V.h('td', helper.showDistance(n.link));
return V.h('tr', [td1, td2, td3, td4, td5]);
}
var h2 = document.createElement('h2');
h2.textContent = d.nodeinfo.hostname;
el.appendChild(h2);
var attributes = document.createElement('table');
attributes.classList.add('attributes');
helper.attributeEntry(attributes, 'node.status', showStatus(d));
helper.attributeEntry(attributes, 'node.gateway', d.flags.gateway ? 'ja' : null);
helper.attributeEntry(attributes, 'node.coordinates', showGeoURI(d));
if (config.nodeInfobox && config.nodeInfobox.contact) {
helper.attributeEntry(attributes, 'node.contact', helper.dictGet(d.nodeinfo, ['owner', 'contact']));
}
helper.attributeEntry(attributes, 'node.hardware', helper.dictGet(d.nodeinfo, ['hardware', 'model']));
helper.attributeEntry(attributes, 'node.primaryMac', helper.dictGet(d.nodeinfo, ['network', 'mac']));
helper.attributeEntry(attributes, 'node.id', helper.dictGet(d.nodeinfo, ['node_id']));
helper.attributeEntry(attributes, 'node.firmware', showFirmware(d));
helper.attributeEntry(attributes, 'node.site', showSite(d, config));
helper.attributeEntry(attributes, 'node.uptime', showUptime(d));
helper.attributeEntry(attributes, 'node.firstSeen', showFirstseen(d));
if (config.nodeInfobox && config.nodeInfobox.hardwareUsage) {
helper.attributeEntry(attributes, 'node.systemLoad', showLoad(d));
helper.attributeEntry(attributes, 'node.ram', showRAM(d));
}
helper.attributeEntry(attributes, 'node.ipAddresses', showIPs(d));
helper.attributeEntry(attributes, 'node.selectedGateway', helper.dictGet(d.statistics, ['gateway']));
helper.attributeEntry(attributes, 'node.update', showAutoupdate(d));
helper.attributeEntry(attributes, 'node.clients', showClients(d));
el.appendChild(attributes);
if (d.neighbours.length > 0) {
var h3 = document.createElement('h3');
h3.textContent = _.t('node.link', d.neighbours.length) + '(' + d.neighbours.length + ')';
el.appendChild(h3);
var headings = [{
name: ''
}, {
name: 'node.nodes',
sort: function (a, b) {
return a.node.nodeinfo.hostname.localeCompare(b.node.nodeinfo.hostname);
},
reverse: false
}, {
name: 'node.clients',
class: 'ion-people',
sort: function (a, b) {
return ('clients' in a.node.statistics ? a.node.statistics.clients : -1) -
('clients' in b.node.statistics ? b.node.statistics.clients : -1);
},
reverse: true
}, {
name: 'node.tq',
class: 'ion-connection-bars',
sort: function (a, b) {
return a.link.tq - b.link.tq;
},
reverse: true
}, {
name: 'node.distance',
class: 'ion-arrow-resize',
sort: function (a, b) {
return (a.link.distance === undefined ? -1 : a.link.distance) -
(b.link.distance === undefined ? -1 : b.link.distance);
},
reverse: true
}];
var table = new SortTable(headings, 1, renderNeighbourRow);
table.setData(d.neighbours);
table.el.elm.classList.add('node-links');
el.appendChild(table.el.elm);
}
if (config.nodeInfos) {
config.nodeInfos.forEach(function (nodeInfo) {
var h4 = document.createElement('h4');
h4.textContent = nodeInfo.name;
el.appendChild(h4);
el.appendChild(showStatImg(nodeInfo, d));
});
}
};
});

View File

@ -1,53 +1,46 @@
define(['helper'], function (helper) {
'use strict';
return function (language) {
return function (config, language) {
var self = this;
var stats = document.createTextNode('');
var timestamp = document.createTextNode('');
self.setData = function setData(d) {
var totalNodes = Object.keys(d.nodeDict).length;
var totalOnlineNodes = d.nodes.online.length;
var totalClients = helper.sum(d.nodes.online.map(function (n) {
return n.clients;
var totalNodes = helper.sum(d.nodes.all.map(helper.one));
var totalOnlineNodes = helper.sum(d.nodes.all.filter(helper.online).map(helper.one));
var totalClients = helper.sum(d.nodes.all.filter(helper.online).map(function (n) {
return n.statistics.clients ? n.statistics.clients : 0;
}));
var totalGateways = helper.sum(d.nodes.online.filter(function (n) {
return n.is_gateway;
var totalGateways = helper.sum(d.nodes.all.filter(helper.online).filter(function (n) {
return n.flags.gateway;
}).map(helper.one));
stats.textContent = _.t('sidebar.nodes', { total: totalNodes, online: totalOnlineNodes }) + ' ' +
_.t('sidebar.clients', { smart_count: totalClients }) + ' ' +
_.t('sidebar.gateway', { smart_count: totalGateways });
timestamp.textContent = _.t('sidebar.lastUpdate') + ' ' + d.timestamp.fromNow();
timestamp.textContent = _.t('sidebar.lastUpdate') + ': ' + d.timestamp.format('DD.MM.Y HH:mm');
};
self.render = function render(el) {
var h1 = document.createElement('h1');
h1.textContent = config.siteName;
el.appendChild(h1);
var h2 = document.createElement('h2');
h2.textContent = config.siteName;
el.appendChild(h2);
language.languageSelect(el);
var p = document.createElement('p');
p.classList.add('legend');
p.innerHTML = '<span class="legend-new"><span class="symbol"></span> ' + _.t('sidebar.nodeNew') + '</span>' +
'<span class="legend-online"><span class="symbol"></span> ' + _.t('sidebar.nodeOnline') + '</span>' +
'<span class="legend-offline"><span class="symbol"></span> ' + _.t('sidebar.nodeOffline') + '</span>';
el.appendChild(p);
p.appendChild(document.createElement('br'));
p.appendChild(stats);
p.appendChild(document.createElement('br'));
p.appendChild(timestamp);
if (config.linkList) {
p.appendChild(document.createElement('br'));
config.linkList.forEach(function (link) {
var a = document.createElement('a');
a.innerText = link.title;
a.href = link.href;
p.appendChild(a);
});
}
el.appendChild(p);
};
return self;

View File

@ -1,17 +1,11 @@
define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
'use strict';
V = V.default;
function linkName(d) {
return (d.source ? d.source.hostname : d.source.id) + ' ' + d.target.hostname;
return (d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + ' ' + d.target.node.nodeinfo.hostname;
}
var headings = [{
name: '',
sort: function (a, b) {
return a.type.localeCompare(b.type);
}
}, {
name: 'node.nodes',
sort: function (a, b) {
return linkName(a).localeCompare(linkName(b));
@ -21,7 +15,7 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
name: 'node.tq',
class: 'ion-connection-bars',
sort: function (a, b) {
return (a.source_tq + a.target_tq) / 2 - (b.source_tq + b.target_tq) / 2;
return a.tq - b.tq;
},
reverse: true
}, {
@ -34,8 +28,9 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
reverse: true
}];
return function (linkScale) {
var table = new SortTable(headings, 3, renderRow);
return function (linkScale, router) {
var table = new SortTable(headings, 2, renderRow);
V = V.default;
function renderRow(d) {
var td1Content = [V.h('a', {
@ -48,12 +43,11 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
}
}, linkName(d))];
return V.h('tr', [
V.h('td', V.h('span', { props: { className: 'icon ion-' + (d.type.indexOf('wifi') === 0 ? 'wifi' : 'share-alt'), title: _.t(d.type) } })),
V.h('td', td1Content),
V.h('td', { style: { color: linkScale((d.source_tq + d.target_tq) / 2) } }, helper.showTq(d.source_tq) + ' - ' + helper.showTq(d.target_tq)),
V.h('td', helper.showDistance(d))
]);
var td1 = V.h('td', td1Content);
var td2 = V.h('td', { style: { color: linkScale(1 / d.tq) } }, helper.showTq(d));
var td3 = V.h('td', helper.showDistance(d));
return V.h('tr', [td1, td2, td3]);
}
this.render = function render(d) {
@ -65,7 +59,7 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
};
this.setData = function setData(d) {
table.setData(d.links);
table.setData(d.graph.links);
};
};
});

View File

@ -2,53 +2,105 @@ define(['moment', 'utils/router', 'leaflet', 'gui', 'helper', 'utils/language'],
function (moment, Router, L, GUI, helper, Language) {
'use strict';
return function () {
return function (config) {
function handleData(data) {
var timestamp;
var nodes = [];
var links = [];
var nodeDict = {};
var dataNodes = {};
dataNodes.nodes = [];
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;
}
for (var i = 0; i < data.length; ++i) {
nodes = nodes.concat(data[i].nodes);
timestamp = data[i].timestamp;
links = links.concat(data[i].links);
var vererr;
if (i % 2) {
if (data[i].version !== 1) {
vererr = 'Unsupported graph version: ' + data[i].version;
console.error(vererr); // silent fail
} else {
data[i].batadv.links.forEach(rearrangeLinks);
dataGraph.batadv.nodes = dataGraph.batadv.nodes.concat(data[i].batadv.nodes);
dataGraph.batadv.links = dataGraph.batadv.links.concat(data[i].batadv.links);
dataGraph.timestamp = data[i].timestamp;
}
} else if (data[i].version !== 2) {
vererr = 'Unsupported nodes version: ' + data[i].version;
console.error(vererr); // silent fail
} else {
dataNodes.nodes = dataNodes.nodes.concat(data[i].nodes);
dataNodes.timestamp = data[i].timestamp;
}
}
var nodes = dataNodes.nodes.filter(function (d) {
return 'firstseen' in d && 'lastseen' in d;
});
nodes.forEach(function (node) {
node.firstseen = moment.utc(node.firstseen).local();
node.lastseen = moment.utc(node.lastseen).local();
});
var age = moment().subtract(config.maxAge, 'days');
var now = moment();
var age = moment(now).subtract(config.maxAge, 'days');
var online = nodes.filter(function (d) {
return d.is_online;
});
var offline = nodes.filter(function (d) {
return !d.is_online;
var newnodes = helper.limit('firstseen', age, helper.sortByKey('firstseen', nodes).filter(helper.online));
var lostnodes = helper.limit('lastseen', age, helper.sortByKey('lastseen', nodes).filter(helper.offline));
var graphnodes = {};
dataNodes.nodes.forEach(function (d) {
graphnodes[d.nodeinfo.node_id] = d;
});
var newnodes = helper.limit('firstseen', age, helper.sortByKey('firstseen', online));
var lostnodes = helper.limit('lastseen', age, helper.sortByKey('lastseen', offline));
var graph = dataGraph.batadv;
graph.nodes.forEach(function (d) {
if (d.node_id in graphnodes) {
d.node = graphnodes[d.node_id];
if (d.unseen) {
d.node.flags.online = true;
d.node.flags.unseen = true;
}
}
});
graph.links.forEach(function (d) {
d.source = graph.nodes[d.source];
if (graph.nodes[d.target].node) {
d.target = graph.nodes[d.target];
} else {
d.target = undefined;
}
});
var links = graph.links.filter(function (d) {
return d.target !== undefined;
});
nodes.forEach(function (d) {
d.neighbours = [];
nodeDict[d.node_id] = d;
});
links.forEach(function (d) {
d.source = nodeDict[d.source];
d.target = nodeDict[d.target];
var ids;
d.id = [d.source.node_id, d.target.node_id].join('-');
d.source.neighbours.push({ node: d.target, link: d });
d.target.neighbours.push({ node: d.source, link: d });
ids = [d.source.node.nodeinfo.node_id, d.target.node.nodeinfo.node_id];
d.source.node.neighbours.push({ node: d.target.node, link: d, incoming: false });
d.target.node.neighbours.push({ node: d.source.node, link: d, incoming: true });
d.id = ids.join('-');
try {
d.latlngs = [];
d.latlngs.push(L.latLng(d.source.location.latitude, d.source.location.longitude));
d.latlngs.push(L.latLng(d.target.location.latitude, d.target.location.longitude));
d.latlngs.push(L.latLng(d.source.node.nodeinfo.location.latitude, d.source.node.nodeinfo.location.longitude));
d.latlngs.push(L.latLng(d.target.node.nodeinfo.location.latitude, d.target.node.nodeinfo.location.longitude));
d.distance = d.latlngs[0].distanceTo(d.latlngs[1]);
} catch (e) {
@ -56,53 +108,50 @@ define(['moment', 'utils/router', 'leaflet', 'gui', 'helper', 'utils/language'],
}
});
links.sort(function (a, b) {
return b.tq - a.tq;
});
return {
now: moment(),
timestamp: moment.utc(timestamp).local(),
now: now,
timestamp: moment.utc(dataNodes.timestamp).local(),
nodes: {
all: nodes,
online: online,
offline: offline,
new: newnodes,
lost: lostnodes
},
links: links,
nodeDict: nodeDict
graph: {
links: links,
nodes: graph.nodes
}
};
}
var language = new Language();
window.router = new Router(language);
var language = new Language(config);
var router = new Router(language);
config.dataPath.forEach(function (d, i) {
config.dataPath[i] += 'meshviewer.json';
});
var urls = [];
language.init(router);
if (typeof config.dataPath === 'string' || config.dataPath instanceof String) {
config.dataPath = [config.dataPath];
}
for (var i in config.dataPath) {
if (config.dataPath.hasOwnProperty(i)) {
urls.push(config.dataPath[i] + 'nodes.json');
urls.push(config.dataPath[i] + 'graph.json');
}
}
function update() {
return Promise.all(config.dataPath.map(helper.getJSON))
language.init(router);
return Promise.all(urls.map(helper.getJSON))
.then(handleData);
}
update()
.then(function (d) {
return new Promise(function (resolve, reject) {
var count = 0;
(function waitForLanguage() {
if (Object.keys(_.phrases).length > 0) {
resolve(d);
} else if (count > 500) {
reject(new Error('translation not loaded after 10 seconds'));
} else {
setTimeout(waitForLanguage.bind(this), 20);
}
count++;
})();
});
})
.then(function (d) {
var gui = new GUI(language);
var gui = new GUI(config, router, language);
gui.setData(d);
router.setData(d);
router.resolve();
@ -116,7 +165,7 @@ define(['moment', 'utils/router', 'leaflet', 'gui', 'helper', 'utils/language'],
})
.catch(function (e) {
document.querySelector('.loader').innerHTML += e.message
+ '<br /><br /><button onclick="location.reload(true)" class="btn text" aria-label="Try to reload">Try to reload</button><br /> or report to your community';
+ '<br /><br /><button onclick="location.reload(true)" class="btn text">Try to reload</button><br /> or report to your community';
console.warn(e);
});
};

View File

@ -1,4 +1,4 @@
define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activearea'],
define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
function (ClientLayer, LabelLayer, Button, L) {
'use strict';
@ -8,7 +8,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activ
minZoom: 0
};
return function (linkScale, sidebar, buttons) {
return function (config, linkScale, sidebar, router, buttons) {
var self = this;
var savedView;
@ -27,26 +27,10 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activ
document.querySelector('.leaflet-control-layers').classList.add('leaflet-control-layers-expanded');
}
function mapActiveArea() {
map.setActiveArea({
position: 'absolute',
left: sidebar.getWidth() + 'px',
right: 0,
top: 0,
bottom: 0
});
}
function setActiveArea() {
setTimeout(mapActiveArea, 300);
}
var el = document.createElement('div');
el.classList.add('map');
map = L.map(el, options);
mapActiveArea();
var now = new Date();
config.mapLayers.forEach(function (item, i) {
if ((typeof item.config.start === 'number' && item.config.start <= now.getHours()) || (typeof item.config.end === 'number' && item.config.end > now.getHours())) {
@ -63,7 +47,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activ
var layers = config.mapLayers.map(function (d) {
return {
'name': d.name,
'layer': L.tileLayer(d.url.replace('{retina}', L.Browser.retina ? '@2x' : ''), d.config)
'layer': 'url' in d ? L.tileLayer(d.url.replace('{retina}', L.Browser.retina ? '@2x' : ''), d.config) : console.warn('Missing map url')
};
});
@ -73,23 +57,13 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activ
baseLayers[d.name] = d.layer;
});
var button = new Button(map, buttons);
var button = new Button(config, map, router, buttons);
map.on('locationfound', button.locationFound);
map.on('locationerror', button.locationError);
map.on('dragend', saveView);
map.on('contextmenu', contextMenuOpenLayerMenu);
if (config.geo) {
[].forEach.call(config.geo, function (geo) {
geo.json().then(function (result) {
if (result) {
L.geoJSON(result, geo.option).addTo(map);
}
});
});
}
button.init();
layerControl = L.control.layers(baseLayers, [], { position: 'bottomright' });
@ -105,8 +79,6 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activ
labelLayer.addTo(map);
labelLayer.setZIndex(6);
sidebar.button.addEventListener('visibility', setActiveArea);
map.on('zoom', function () {
clientLayer.redraw();
labelLayer.redraw();
@ -133,14 +105,6 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activ
}
});
map.on('load', function () {
var inputs = document.querySelectorAll('.leaflet-control-layers-selector');
[].forEach.call(inputs, function (input) {
input.setAttribute('role', 'radiogroup');
input.setAttribute('aria-label', input.nextSibling.innerHTML.trim());
});
});
var nodeDict = {};
var linkDict = {};
var highlight;
@ -156,7 +120,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activ
}
function setView(bounds, zoom) {
map.fitBounds(bounds, { maxZoom: (zoom ? zoom : config.nodeZoom) });
map.fitBounds(bounds, { paddingTopLeft: [sidebar(), 0], maxZoom: (zoom ? zoom : config.nodeZoom) });
}
function goto(m) {
@ -178,12 +142,12 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activ
var m;
if (highlight !== undefined) {
if (highlight.type === 'node' && nodeDict[highlight.o.node_id]) {
m = nodeDict[highlight.o.node_id];
m.setStyle(config.map.highlightNode);
if (highlight.type === 'node' && nodeDict[highlight.o.nodeinfo.node_id]) {
m = nodeDict[highlight.o.nodeinfo.node_id];
m.setStyle({ color: 'orange', weight: 20, fillOpacity: 1, opacity: 0.7, className: 'stroke-first' });
} else if (highlight.type === 'link' && linkDict[highlight.o.id]) {
m = linkDict[highlight.o.id];
m.setStyle(config.map.highlightLink);
m.setStyle({ weight: 4, opacity: 1, dashArray: '5, 10' });
}
}
@ -203,7 +167,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activ
linkDict = {};
clientLayer.setData(data);
labelLayer.setData(data, map, nodeDict, linkDict, linkScale);
labelLayer.setData(data, map, nodeDict, linkDict, linkScale, router, config);
updateView(true);
};
@ -222,7 +186,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activ
self.gotoLink = function gotoLink(d) {
button.disableTracking();
highlight = { type: 'link', o: d[0] };
highlight = { type: 'link', o: d };
updateView();
};
@ -233,7 +197,6 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activ
self.destroy = function destroy() {
button.clearButtons();
sidebar.button.removeEventListener('visibility', setActiveArea);
map.remove();
if (el.parentNode) {

View File

@ -1,291 +0,0 @@
define(function () {
/**
* https://github.com/Mappy/Leaflet-active-area
* Apache 2.0 license https://www.apache.org/licenses/LICENSE-2.0
*/
var previousMethods = {
getCenter: L.Map.prototype.getCenter,
setView: L.Map.prototype.setView,
setZoomAround: L.Map.prototype.setZoomAround,
getBoundsZoom: L.Map.prototype.getBoundsZoom,
RendererUpdate: L.Renderer.prototype._update
};
L.Map.include({
getBounds: function () {
if (this._viewport) {
return this.getViewportLatLngBounds();
}
var bounds = this.getPixelBounds();
var sw = this.unproject(bounds.getBottomLeft());
var ne = this.unproject(bounds.getTopRight());
return new L.LatLngBounds(sw, ne);
},
getViewport: function () {
return this._viewport;
},
getViewportBounds: function () {
var vp = this._viewport;
var topleft = L.point(vp.offsetLeft, vp.offsetTop);
var vpsize = L.point(vp.clientWidth, vp.clientHeight);
if (vpsize.x === 0 || vpsize.y === 0) {
// Our own viewport has no good size - so we fallback to the container size:
vp = this.getContainer();
if (vp) {
topleft = L.point(0, 0);
vpsize = L.point(vp.clientWidth, vp.clientHeight);
}
}
return L.bounds(topleft, topleft.add(vpsize));
},
getViewportLatLngBounds: function () {
var bounds = this.getViewportBounds();
return L.latLngBounds(this.containerPointToLatLng(bounds.min), this.containerPointToLatLng(bounds.max));
},
getOffset: function () {
var mCenter = this.getSize().divideBy(2);
var vCenter = this.getViewportBounds().getCenter();
return mCenter.subtract(vCenter);
},
getCenter: function (withoutViewport) {
var center = previousMethods.getCenter.call(this);
if (this.getViewport() && !withoutViewport) {
var zoom = this.getZoom();
var point = this.project(center, zoom);
point = point.subtract(this.getOffset());
center = this.unproject(point, zoom);
}
return center;
},
setView: function (center, zoom, options) {
center = L.latLng(center);
zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
if (this.getViewport()) {
var point = this.project(center, this._limitZoom(zoom));
point = point.add(this.getOffset());
center = this.unproject(point, this._limitZoom(zoom));
}
return previousMethods.setView.call(this, center, zoom, options);
},
setZoomAround: function (latlng, zoom, options) {
var vp = this.getViewport();
if (vp) {
var scale = this.getZoomScale(zoom);
var viewHalf = this.getViewportBounds().getCenter();
var containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng);
var centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale);
var newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
return this.setView(newCenter, zoom, { zoom: options });
}
return previousMethods.setZoomAround.call(this, latlng, zoom, options);
},
getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
bounds = L.latLngBounds(bounds);
padding = L.point(padding || [0, 0]);
var zoom = this.getZoom() || 0;
var min = this.getMinZoom();
var max = this.getMaxZoom();
var nw = bounds.getNorthWest();
var se = bounds.getSouthEast();
var vp = this.getViewport();
var size = (vp ? L.point(vp.clientWidth, vp.clientHeight) : this.getSize()).subtract(padding);
var boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom));
var snap = L.Browser.any3d ? this.options.zoomSnap : 1;
var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
zoom = this.getScaleZoom(scale, zoom);
if (snap) {
zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
}
return Math.max(min, Math.min(max, zoom));
}
});
L.Map.include({
setActiveArea: function (css, keepCenter, animate) {
var center;
if (keepCenter && this._zoom) {
// save center if map is already initialized
// and keepCenter is passed
center = this.getCenter();
}
if (!this._viewport) {
// Make viewport if not already made
var container = this.getContainer();
this._viewport = L.DomUtil.create('div', '');
container.insertBefore(this._viewport, container.firstChild);
}
if (typeof css === 'string') {
this._viewport.className = css;
} else {
L.extend(this._viewport.style, css);
}
if (center) {
this.setView(center, this.getZoom(), { animate: !!animate });
}
return this;
}
});
L.Renderer.include({
_onZoom: function () {
this._updateTransform(this._map.getCenter(true), this._map.getZoom());
},
_update: function () {
previousMethods.RendererUpdate.call(this);
this._center = this._map.getCenter(true);
}
});
L.GridLayer.include({
_updateLevels: function () {
var zoom = this._tileZoom;
var maxZoom = this.options.maxZoom;
if (zoom === undefined) {
return undefined;
}
for (var z in this._levels) {
if (this._levels[z].el.children.length || z === zoom) {
this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
} else {
L.DomUtil.remove(this._levels[z].el);
this._removeTilesAtZoom(z);
delete this._levels[z];
}
}
var level = this._levels[zoom];
var map = this._map;
if (!level) {
level = this._levels[zoom] = {};
level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
level.el.style.zIndex = maxZoom;
level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
level.zoom = zoom;
this._setZoomTransform(level, map.getCenter(true), map.getZoom());
// force the browser to consider the newly added element for transition
L.Util.falseFn(level.el.offsetWidth);
}
this._level = level;
return level;
},
_resetView: function (e) {
var animating = e && (e.pinch || e.flyTo);
this._setView(this._map.getCenter(true), this._map.getZoom(), animating, animating);
},
_update: function (center) {
var map = this._map;
if (!map) {
return;
}
var zoom = map.getZoom();
if (center === undefined) {
center = map.getCenter(this);
}
if (this._tileZoom === undefined) {
return;
} // if out of minzoom/maxzoom
var pixelBounds = this._getTiledPixelBounds(center);
var tileRange = this._pxBoundsToTileRange(pixelBounds);
var tileCenter = tileRange.getCenter();
var queue = [];
for (var key in this._tiles) {
this._tiles[key].current = false;
}
// _update just loads more tiles. If the tile zoom level differs too much
// from the map's, let _setView reset levels and prune old tiles.
if (Math.abs(zoom - this._tileZoom) > 1) {
this._setView(center, zoom);
return;
}
// create a queue of coordinates to load tiles from
for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
var coords = new L.Point(i, j);
coords.z = this._tileZoom;
if (!this._isValidTile(coords)) {
continue;
}
var tile = this._tiles[this._tileCoordsToKey(coords)];
if (tile) {
tile.current = true;
} else {
queue.push(coords);
}
}
}
// sort tile queue to load tiles in order of their distance to center
queue.sort(function (a, b) {
return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
});
if (queue.length !== 0) {
// if its the first batch of tiles to load
if (!this._loading) {
this._loading = true;
// @event loading: Event
// Fired when the grid layer starts loading tiles
this.fire('loading');
}
// create DOM fragment to append tiles in one batch
var fragment = document.createDocumentFragment();
for (i = 0; i < queue.length; i++) {
this._addTile(queue[i], fragment);
}
this._level.el.appendChild(fragment);
}
}
});
});

View File

@ -1,5 +1,5 @@
define(['map/clientlayer', 'map/labellayer', 'leaflet', 'map/locationmarker'],
function (ClientLayer, LabelLayer, L, LocationMarker) {
define(['map/clientlayer', 'map/labellayer', 'leaflet', 'moment', 'map/locationmarker'],
function (ClientLayer, LabelLayer, L, moment, LocationMarker) {
'use strict';
var self = {};
@ -28,8 +28,8 @@ define(['map/clientlayer', 'map/labellayer', 'leaflet', 'map/locationmarker'],
var LocateButton = ButtonBase.extend({
onAdd: function () {
var button = L.DomUtil.create('button', 'ion-locate');
button.setAttribute('aria-label', _.t('button.tracking'));
var button = L.DomUtil.create('button', 'ion-locate shadow');
button.setAttribute('data-tooltip', _.t('button.tracking'));
L.DomEvent.disableClickPropagation(button);
L.DomEvent.addListener(button, 'click', this.onClick, this);
@ -45,8 +45,8 @@ define(['map/clientlayer', 'map/labellayer', 'leaflet', 'map/locationmarker'],
var CoordsPickerButton = ButtonBase.extend({
onAdd: function () {
var button = L.DomUtil.create('button', 'ion-pin');
button.setAttribute('aria-label', _.t('button.location'));
var button = L.DomUtil.create('button', 'ion-pin shadow');
button.setAttribute('data-tooltip', _.t('button.location'));
// Click propagation isn't disabled as this causes problems with the
// location picking mode; instead propagation is stopped in onClick().
@ -63,7 +63,7 @@ define(['map/clientlayer', 'map/labellayer', 'leaflet', 'map/locationmarker'],
}
});
return function (map, buttons) {
return function (config, map, router, buttons) {
var userLocation;
var locateUserButton = new LocateButton(function (d) {

View File

@ -1,23 +1,23 @@
define(['leaflet', 'rbush', 'helper'],
function (L, RBush, helper) {
function (L, rbush, helper) {
'use strict';
return L.GridLayer.extend({
mapRTree: function mapRTree(d) {
return {
minX: d.location.latitude, minY: d.location.longitude,
maxX: d.location.latitude, maxY: d.location.longitude,
minX: d.nodeinfo.location.latitude, minY: d.nodeinfo.location.longitude,
maxX: d.nodeinfo.location.latitude, maxY: d.nodeinfo.location.longitude,
node: d
};
},
setData: function (data) {
var rtreeOnlineAll = new RBush(9);
var rtreeOnlineAll = rbush(9);
this.data = rtreeOnlineAll.load(data.nodes.online.filter(helper.hasLocation).map(this.mapRTree));
this.data = rtreeOnlineAll.load(data.nodes.all.filter(helper.online).filter(helper.hasLocation).map(this.mapRTree));
// pre-calculate start angles
this.data.all().forEach(function (n) {
n.startAngle = (parseInt(n.node.node_id.substr(10, 2), 16) / 255) * 2 * Math.PI;
n.startAngle = (parseInt(n.node.nodeinfo.node_id.substr(10, 2), 16) / 255) * 2 * Math.PI;
});
this.redraw();
},
@ -45,17 +45,21 @@ define(['leaflet', 'rbush', 'helper'],
return tile;
}
var startDistance = 10;
var startDistance = 12;
ctx.beginPath();
nodes.forEach(function (d) {
var p = map.project([d.node.location.latitude, d.node.location.longitude]);
var p = map.project([d.node.nodeinfo.location.latitude, d.node.nodeinfo.location.longitude]);
p.x -= s.x;
p.y -= s.y;
helper.positionClients(ctx, p, d.startAngle, d.node, startDistance);
helper.positionClients(ctx, p, d.startAngle, d.node.statistics.clients, startDistance);
});
ctx.fillStyle = 'rgba(220, 0, 103, 0.7)';
ctx.fill();
return tile;
}
});

View File

@ -1,5 +1,5 @@
define(['leaflet', 'rbush', 'helper', 'moment'],
function (L, RBush, helper, moment) {
function (L, rbush, helper, moment) {
'use strict';
var groupOnline;
@ -35,14 +35,14 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
return function (d) {
var font = fontSize + 'px ' + bodyStyle.fontFamily;
return {
position: L.latLng(d.location.latitude, d.location.longitude),
label: d.hostname,
position: L.latLng(d.nodeinfo.location.latitude, d.nodeinfo.location.longitude),
label: d.nodeinfo.hostname,
offset: offset,
fillStyle: fillStyle,
height: fontSize * 1.2,
font: font,
stroke: stroke,
width: measureText(font, d.hostname).width
width: measureText(font, d.nodeinfo.hostname).width
};
};
}
@ -76,33 +76,33 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
return { minX: x, minY: y, maxX: x + width, maxY: y + height };
}
function mkMarker(dict, iconFunc) {
function mkMarker(dict, iconFunc, router) {
return function (d) {
var m = L.circleMarker([d.location.latitude, d.location.longitude], iconFunc(d));
var m = L.circleMarker([d.nodeinfo.location.latitude, d.nodeinfo.location.longitude], iconFunc(d));
m.resetStyle = function resetStyle() {
m.setStyle(iconFunc(d));
};
m.on('click', function () {
router.fullUrl({ node: d.node_id });
router.fullUrl({ node: d.nodeinfo.node_id });
});
m.bindTooltip(helper.escape(d.hostname));
m.bindTooltip(d.nodeinfo.hostname);
dict[d.node_id] = m;
dict[d.nodeinfo.node_id] = m;
return m;
};
}
function addLinksToMap(dict, linkScale, graph) {
function addLinksToMap(dict, linkScale, graph, router) {
graph = graph.filter(function (d) {
return 'distance' in d && d.type.indexOf('vpn') !== 0;
return 'distance' in d && !d.vpn;
});
return graph.map(function (d) {
var opts = {
color: linkScale((d.source_tq + d.target_tq) / 2),
color: linkScale(1 / d.tq),
weight: 4,
opacity: 0.5,
dashArray: 'none'
@ -114,9 +114,7 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
line.setStyle(opts);
};
line.bindTooltip(helper.escape(d.source.hostname + ' ' + d.target.hostname) +
'<br><strong>' + helper.showDistance(d) + ' / ' + helper.showTq(d.source_tq) + ' - ' + helper.showTq(d.target_tq) + '<br>' + d.type + '</strong>');
line.bindTooltip(d.source.node.nodeinfo.hostname + ' ' + d.target.node.nodeinfo.hostname + '<br><strong>' + helper.showDistance(d) + ' / ' + helper.showTq(d) + '</strong>');
line.on('click', function () {
router.fullUrl({ link: d.id });
});
@ -127,7 +125,7 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
});
}
function getIcon(color) {
function getIcon(config, color) {
return Object.assign({}, config.icon.base, config.icon[color]);
}
@ -138,12 +136,12 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
this.prepareLabels();
}
},
setData: function (data, map, nodeDict, linkDict, linkScale) {
var iconOnline = getIcon('online');
var iconOffline = getIcon('offline');
var iconLost = getIcon('lost');
var iconAlert = getIcon('alert');
var iconNew = getIcon('new');
setData: function (data, map, nodeDict, linkDict, linkScale, router, config) {
var iconOnline = getIcon(config, 'online');
var iconOffline = getIcon(config, 'offline');
var iconLost = getIcon(config, 'lost');
var iconAlert = getIcon(config, 'alert');
var iconNew = getIcon(config, 'new');
// Check if init or data is already set
if (groupLines) {
groupOffline.clearLayers();
@ -153,36 +151,38 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
groupLines.clearLayers();
}
var lines = addLinksToMap(linkDict, linkScale, data.links);
var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router);
groupLines = L.featureGroup(lines).addTo(map);
var nodesOnline = helper.subtract(data.nodes.online, data.nodes.new).filter(helper.hasLocation);
var nodesOffline = helper.subtract(data.nodes.offline, data.nodes.lost).filter(helper.hasLocation);
var nodesNew = data.nodes.new.filter(helper.hasLocation);
var nodesLost = data.nodes.lost.filter(helper.hasLocation);
var nodesOnline = helper.subtract(data.nodes.all.filter(helper.online), data.nodes.new);
var nodesOffline = helper.subtract(data.nodes.all.filter(helper.offline), data.nodes.lost);
var markersOnline = nodesOnline.map(mkMarker(nodeDict, function () {
return iconOnline;
}));
var markersOnline = nodesOnline.filter(helper.hasLocation)
.map(mkMarker(nodeDict, function () {
return iconOnline;
}, router));
var markersOffline = nodesOffline.map(mkMarker(nodeDict, function () {
return iconOffline;
}));
var markersOffline = nodesOffline.filter(helper.hasLocation)
.map(mkMarker(nodeDict, function () {
return iconOffline;
}, router));
var markersNew = nodesNew.map(mkMarker(nodeDict, function () {
return iconNew;
}));
var markersNew = data.nodes.new.filter(helper.hasLocation)
.map(mkMarker(nodeDict, function () {
return iconNew;
}, router));
var markersLost = nodesLost.map(mkMarker(nodeDict, function (d) {
var age = moment(data.now).diff(d.lastseen, 'days', true);
if (age <= config.maxAgeAlert) {
return iconAlert;
}
if (age <= config.maxAge) {
return iconLost;
}
return null;
}));
var markersLost = data.nodes.lost.filter(helper.hasLocation)
.map(mkMarker(nodeDict, function (d) {
if (d.lastseen.isAfter(moment(data.now).subtract(config.maxAgeAlert, 'days'))) {
return iconAlert;
}
if (d.lastseen.isAfter(moment(data.now).subtract(config.maxAge, 'days'))) {
return iconLost;
}
return null;
}, router));
groupOffline = L.featureGroup(markersOffline).addTo(map);
groupLost = L.featureGroup(markersLost).addTo(map);
@ -190,10 +190,10 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
groupNew = L.featureGroup(markersNew).addTo(map);
this.data = {
online: nodesOnline,
offline: nodesOffline,
new: nodesNew,
lost: nodesLost
online: nodesOnline.filter(helper.hasLocation),
offline: nodesOffline.filter(helper.hasLocation),
new: data.nodes.new.filter(helper.hasLocation),
lost: data.nodes.lost.filter(helper.hasLocation)
};
this.updateLayer();
},
@ -214,9 +214,9 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
// - color (string)
var labelsOnline = d.online.map(prepareLabel(null, 11, 8, true));
var labelsOffline = d.offline.map(prepareLabel(config.icon.offline.color, 9, 5, false));
var labelsNew = d.new.map(prepareLabel(config.map.labelNewColor, 11, 8, true));
var labelsLost = d.lost.map(prepareLabel(config.icon.lost.color, 11, 8, true));
var labelsOffline = d.offline.map(prepareLabel('rgba(212, 62, 42, 0.9)', 9, 5, false));
var labelsNew = d.new.map(prepareLabel('rgba(48, 99, 20, 0.9)', 11, 8, true));
var labelsLost = d.lost.map(prepareLabel('rgba(212, 62, 42, 0.9)', 11, 8, true));
var labels = []
.concat(labelsNew)
@ -239,7 +239,7 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
}
for (var z = minZoom; z <= maxZoom; z++) {
trees[z] = new RBush(9);
trees[z] = rbush(9);
trees[z].load(labels.map(nodeToRect(z)));
}
@ -291,7 +291,7 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
}).sort().reverse()[0];
}
this.labels = new RBush(9);
this.labels = rbush(9);
this.labels.load(labels.map(mapRTree));
this.redraw();

View File

@ -2,10 +2,39 @@ define(['leaflet'], function (L) {
'use strict';
return L.CircleMarker.extend({
outerCircle: {
stroke: false,
color: '#4285F4',
opacity: 1,
fillOpacity: 0.3,
clickable: false,
radius: 16
},
innerCircle: {
stroke: true,
color: '#ffffff',
fillColor: '#4285F4',
weight: 1.5,
clickable: false,
opacity: 1,
fillOpacity: 1,
radius: 7
},
accuracyCircle: {
stroke: true,
color: '#4285F4',
weight: 1,
clickable: false,
opacity: 0.7,
fillOpacity: 0.2
},
initialize: function (latlng) {
this.accuracyCircle = L.circle(latlng, 0, config.locate.accuracyCircle);
this.outerCircle = L.circleMarker(latlng, config.locate.outerCircle);
L.CircleMarker.prototype.initialize.call(this, latlng, config.locate.innerCircle);
this.accuracyCircle = L.circle(latlng, 0, this.accuracyCircle);
this.outerCircle = L.circleMarker(latlng, this.outerCircle);
L.CircleMarker.prototype.initialize.call(this, latlng, this.innerCircle);
this.on('remove', function () {
this._map.removeLayer(this.accuracyCircle);

View File

@ -2,18 +2,28 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
'use strict';
V = V.default;
function getUptime(now, d) {
if (d.flags.online && 'uptime' in d.statistics) {
return Math.round(d.statistics.uptime);
} else if (!d.flags.online && 'lastseen' in d) {
return Math.round(-(now.unix() - d.lastseen.unix()));
}
return 0;
}
function showUptime(uptime) {
// 1000ms are 1 second and 60 second are 1min: 60 * 1000 = 60000
var s = uptime / 60000;
if (Math.abs(s) < 60) {
return Math.round(s) + ' m';
var s = '';
uptime /= 3600;
if (uptime !== undefined) {
if (Math.abs(uptime) >= 24) {
s = Math.round(uptime / 24) + 'd';
} else {
s = Math.round(uptime) + 'h';
}
}
s /= 60;
if (Math.abs(s) < 24) {
return Math.round(s) + ' h';
}
s /= 24;
return Math.round(s) + ' d';
return s;
}
var headings = [{
@ -21,7 +31,7 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
}, {
name: 'node.nodes',
sort: function (a, b) {
return a.hostname.localeCompare(b.hostname);
return a.nodeinfo.hostname.localeCompare(b.nodeinfo.hostname);
},
reverse: false
}, {
@ -42,36 +52,40 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
name: 'node.clients',
class: 'ion-people',
sort: function (a, b) {
return a.clients - b.clients;
return ('clients' in a.statistics ? a.statistics.clients : -1) -
('clients' in b.statistics ? b.statistics.clients : -1);
},
reverse: true
}];
return function () {
return function (router) {
function renderRow(d) {
var td0Content = '';
if (helper.hasLocation(d)) {
td0Content = V.h('span', { props: { className: 'icon ion-location', title: _.t('location.location') } });
}
var td0Content = [];
var td1Content = [];
var aClass = ['hostname', d.flags.online ? 'online' : 'offline'];
var td1Content = V.h('a', {
td1Content.push(V.h('a', {
props: {
className: ['hostname', d.is_online ? 'online' : 'offline'].join(' '),
href: router.generateLink({ node: d.node_id })
className: aClass.join(' '),
href: router.generateLink({ node: d.nodeinfo.node_id })
}, on: {
click: function (e) {
router.fullUrl({ node: d.node_id }, e);
router.fullUrl({ node: d.nodeinfo.node_id }, e);
}
}
}, d.hostname);
}, d.nodeinfo.hostname));
return V.h('tr', [
V.h('td', td0Content),
V.h('td', td1Content),
V.h('td', showUptime(d.uptime)),
V.h('td', d.neighbours.length),
V.h('td', d.clients)
]);
if (helper.hasLocation(d)) {
td0Content.push(V.h('span', { props: { className: 'icon ion-location' } }));
}
var td0 = V.h('td', td0Content);
var td1 = V.h('td', td1Content);
var td2 = V.h('td', showUptime(d.uptime));
var td3 = V.h('td', d.neighbours.length);
var td4 = V.h('td', Number('clients' in d.statistics ? d.statistics.clients : 0).toFixed(0));
return V.h('tr', [td0, td1, td2, td3, td4]);
}
var table = new SortTable(headings, 1, renderRow);
@ -87,11 +101,8 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
this.setData = function setData(d) {
var data = d.nodes.all.map(function (e) {
var n = Object.create(e);
if (e.is_online) {
n.uptime = d.now - new Date(e.uptime).getTime();
} else {
n.uptime = e.lastseen - d.now;
}
n.uptime = getUptime(d.now, e);
n.neighbours = e.neighbours;
return n;
});

View File

@ -1,21 +1,22 @@
define(['d3-interpolate', 'snabbdom', 'utils/version', 'filters/genericnode', 'helper'],
function (d3Interpolate, V, versionCompare, Filter, helper) {
define(['d3-interpolate', 'snabbdom', 'filters/genericnode', 'helper'],
function (d3Interpolate, V, Filter, helper) {
'use strict';
V = V.default;
return function (filterManager) {
return function (config, filterManager) {
var self = this;
var scale = d3Interpolate.interpolate(config.forceGraph.tqFrom, config.forceGraph.tqTo);
var time;
var scale = d3Interpolate.interpolate('#770038', '#dc0067');
V = V.default;
var statusTable;
var fwTable;
var hwTable;
var geoTable;
var autoTable;
var gatewayTable;
var gateway6Table;
var domainTable;
var siteTable;
function showStatGlobal(o) {
return helper.showStat(o);
}
function count(nodes, key, f) {
var dict = {};
@ -60,13 +61,14 @@ define(['d3-interpolate', 'snabbdom', 'utils/version', 'filters/genericnode', 'h
var filter = new Filter(_.t(name), d[2], d[0], d[3]);
var a = V.h('a', { on: { click: addFilter(filter) } }, d[0]);
var a = V.h('a', { props: { href: '#' }, on: { click: addFilter(filter) } }, d[0]);
var th = V.h('th', a);
var td = V.h('td', V.h('span', {
style: {
width: 'calc(25px + ' + Math.round(v * 90) + '%)',
backgroundColor: scale(v)
width: Math.round(v * 100) + '%',
backgroundColor: scale(v),
color: 'white'
}
}, d[1].toFixed(0)));
@ -77,53 +79,56 @@ define(['d3-interpolate', 'snabbdom', 'utils/version', 'filters/genericnode', 'h
}
self.setData = function setData(data) {
var onlineNodes = data.nodes.online;
var onlineNodes = data.nodes.all.filter(helper.online);
var nodes = onlineNodes.concat(data.nodes.lost);
time = data.timestamp;
var nodeDict = {};
function hostnameOfNodeID(nodeid) {
var gateway = data.nodeDict[nodeid];
if (gateway) {
return gateway.hostname;
}
return null;
}
data.nodes.all.forEach(function (d) {
nodeDict[d.nodeinfo.node_id] = d;
});
var gatewayDict = count(nodes, ['gateway'], hostnameOfNodeID);
var gateway6Dict = count(nodes, ['gateway6'], hostnameOfNodeID);
var statusDict = count(nodes, ['is_online'], function (d) {
var statusDict = count(nodes, ['flags', 'online'], function (d) {
return d ? 'online' : 'offline';
});
var fwDict = count(nodes, ['firmware', 'release']);
var hwDict = count(nodes, ['model']);
var geoDict = count(nodes, ['location'], function (d) {
var fwDict = count(nodes, ['nodeinfo', 'software', 'firmware', 'release']);
var hwDict = count(nodes, ['nodeinfo', 'hardware', 'model']);
var geoDict = count(nodes, ['nodeinfo', 'location'], function (d) {
return d && d.longitude && d.latitude ? _.t('yes') : _.t('no');
});
var autoDict = count(nodes, ['autoupdater'], function (d) {
if (d.enabled) {
var autoDict = count(nodes, ['nodeinfo', 'software', 'autoupdater'], function (d) {
if (d === null) {
return null;
} else if (d.enabled) {
return d.branch;
}
return _.t('node.deactivated');
});
var domainDict = count(nodes, ['domain'], function (d) {
if (config.domainNames) {
config.domainNames.some(function (t) {
if (d === t.domain) {
d = t.name;
return true;
var siteDict = count(nodes, ['nodeinfo', 'system', 'site_code'], function (d) {
var rt = d;
if (config.siteNames) {
config.siteNames.forEach(function (t) {
if (d === t.site) {
rt = t.name;
}
});
}
return d;
return rt;
});
statusTable = fillTable('node.status', statusTable, statusDict.sort(function (a, b) {
return b[1] - a[1];
}));
fwTable = fillTable('node.firmware', fwTable, fwDict.sort(versionCompare));
fwTable = fillTable('node.firmware', fwTable, fwDict.sort(function (a, b) {
if (b[0] < a[0]) {
return -1;
}
if (b[0] > a[0]) {
return 1;
}
return 0;
}));
hwTable = fillTable('node.hardware', hwTable, hwDict.sort(function (a, b) {
return b[1] - a[1];
}));
@ -133,54 +138,39 @@ define(['d3-interpolate', 'snabbdom', 'utils/version', 'filters/genericnode', 'h
autoTable = fillTable('node.update', autoTable, autoDict.sort(function (a, b) {
return b[1] - a[1];
}));
gatewayTable = fillTable('node.selectedGatewayIPv4', gatewayTable, gatewayDict.sort(function (a, b) {
return b[1] - a[1];
}));
gateway6Table = fillTable('node.selectedGatewayIPv6', gateway6Table, gateway6Dict.sort(function (a, b) {
return b[1] - a[1];
}));
domainTable = fillTable('node.domain', domainTable, domainDict.sort(function (a, b) {
siteTable = fillTable('node.site', siteTable, siteDict.sort(function (a, b) {
return b[1] - a[1];
}));
};
self.render = function render(el) {
var h2;
self.renderSingle(el, 'node.status', statusTable);
self.renderSingle(el, 'node.firmware', fwTable);
self.renderSingle(el, 'node.hardware', hwTable);
self.renderSingle(el, 'node.visible', geoTable);
self.renderSingle(el, 'node.update', autoTable);
self.renderSingle(el, 'node.selectedGatewayIPv4', gatewayTable);
self.renderSingle(el, 'node.selectedGatewayIPv6', gateway6Table);
self.renderSingle(el, 'node.domain', domainTable);
self.renderSingle(el, 'node.site', siteTable);
if (config.globalInfos) {
var images = document.createElement('div');
el.appendChild(images);
var img = [];
var subst = {
'{TIME}': time,
'{LOCALE}': _.locale()
};
config.globalInfos.forEach(function (globalInfo) {
img.push(V.h('h2', globalInfo.name));
img.push(helper.showStat(V, globalInfo, subst));
h2 = document.createElement('h2');
h2.textContent = globalInfo.name;
el.appendChild(h2);
el.appendChild(showStatGlobal(globalInfo));
});
V.patch(images, V.h('div', img));
}
};
self.renderSingle = function renderSingle(el, heading, table) {
if (table.children.length > 0) {
var h2 = document.createElement('h2');
h2.classList.add('proportion-header');
h2.textContent = _.t(heading);
h2.onclick = function onclick() {
table.elm.classList.toggle('hide');
};
el.appendChild(h2);
el.appendChild(table.elm);
}
var h2 = document.createElement('h2');
h2.classList.add('proportion-header');
h2.textContent = _.t(heading);
h2.onclick = function onclick() {
table.elm.classList.toggle('hide');
};
el.appendChild(h2);
el.appendChild(table.elm);
};
return self;
};

View File

@ -15,13 +15,10 @@ define(function () {
el.appendChild(sidebar);
var button = document.createElement('button');
var visibility = new CustomEvent('visibility');
sidebar.appendChild(button);
button.classList.add('sidebarhandle');
button.setAttribute('aria-label', _.t('sidebar.toggle'));
button.classList.add('sidebarhandle', 'shadow');
button.onclick = function onclick() {
button.dispatchEvent(visibility);
sidebar.classList.toggle('hidden');
};
@ -30,7 +27,7 @@ define(function () {
sidebar.appendChild(container);
self.getWidth = function getWidth() {
if (gridBreakpoints.lg[0] > window.innerWidth || sidebar.classList.contains('hidden')) {
if (gridBreakpoints.lg[0] > window.innerWidth) {
return 0;
} else if (gridBreakpoints.xl[0] > window.innerWidth) {
return gridBreakpoints.lg[1];
@ -57,7 +54,6 @@ define(function () {
};
self.container = sidebar;
self.button = button;
return self;
};

View File

@ -2,7 +2,7 @@ define(['moment', 'snabbdom', 'helper'], function (moment, V, helper) {
'use strict';
V = V.default;
return function (nodes, field, title) {
return function (nodes, field, router, title) {
var self = this;
var el;
var tbody;
@ -34,27 +34,32 @@ define(['moment', 'snabbdom', 'helper'], function (moment, V, helper) {
}
var items = list.map(function (d) {
var td0Content = '';
if (helper.hasLocation(d)) {
td0Content = V.h('span', { props: { className: 'icon ion-location', title: _.t('location.location') } });
}
var time = moment(d[field]).from(data.now);
var td0Content = [];
var td1Content = [];
var td1Content = V.h('a', {
var aClass = ['hostname', d.flags.online ? 'online' : 'offline'];
td1Content.push(V.h('a', {
props: {
className: ['hostname', d.is_online ? 'online' : 'offline'].join(' '),
href: router.generateLink({ node: d.node_id })
className: aClass.join(' '),
href: router.generateLink({ node: d.nodeinfo.node_id })
}, on: {
click: function (e) {
router.fullUrl({ node: d.node_id }, e);
router.fullUrl({ node: d.nodeinfo.node_id }, e);
}
}
}, d.hostname);
}, d.nodeinfo.hostname));
return V.h('tr', [
V.h('td', td0Content),
V.h('td', td1Content),
V.h('td', moment(d[field]).from(data.now))
]);
if (helper.hasLocation(d)) {
td0Content.push(V.h('span', { props: { className: 'icon ion-location' } }));
}
var td0 = V.h('td', td0Content);
var td1 = V.h('td', td1Content);
var td2 = V.h('td', time);
return V.h('tr', [td0, td1, td2]);
});
var tbodyNew = V.h('tbody', items);

View File

@ -1,7 +1,7 @@
define(function () {
'use strict';
return function () {
return function (config) {
function setTitle(d) {
var title = [config.siteName];
@ -17,11 +17,11 @@ define(function () {
};
this.gotoNode = function gotoNode(d) {
setTitle(d.hostname);
setTitle(d.nodeinfo.hostname);
};
this.gotoLink = function gotoLink(d) {
setTitle(d[0].source.hostname + ' \u21D4 ' + d[0].target.hostname);
setTitle((d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + ' \u21D4 ' + d.target.node.nodeinfo.hostname);
};
this.gotoLocation = function gotoLocation() {

View File

@ -27,9 +27,9 @@ define({
},
sortByKey: function sortByKey(key, d) {
return d.sort(function (a, b) {
return b[key] - a[key];
});
return d.slice().sort(function (a, b) {
return a[key] - b[key];
}).reverse();
},
limit: function limit(key, m, d) {
@ -48,6 +48,10 @@ define({
return 1;
},
trueDefault: function trueDefault(d) {
return d === undefined ? true : d;
},
dictGet: function dictGet(dict, key) {
var k = key.shift();
@ -72,21 +76,31 @@ define({
return s;
},
/* Helpers working with nodes */
offline: function offline(d) {
return !d.flags.online;
},
online: function online(d) {
return d.flags.online;
},
hasLocation: function hasLocation(d) {
return 'location' in d &&
Math.abs(d.location.latitude) < 90 &&
Math.abs(d.location.longitude) < 180;
return 'location' in d.nodeinfo &&
Math.abs(d.nodeinfo.location.latitude) < 90 &&
Math.abs(d.nodeinfo.location.longitude) < 180;
},
subtract: function subtract(a, b) {
var ids = {};
b.forEach(function (d) {
ids[d.node_id] = true;
ids[d.nodeinfo.node_id] = true;
});
return a.filter(function (d) {
return !ids[d.node_id];
return !(d.nodeinfo.node_id in ids);
});
},
@ -101,35 +115,59 @@ define({
},
showTq: function showTq(d) {
return (d * 100).toFixed(0) + '%';
return (1 / d.tq * 100).toFixed(0) + '%';
},
attributeEntry: function attributeEntry(V, children, label, value) {
if (value !== undefined) {
if (typeof value !== 'object') {
value = V.h('td', value);
}
children.push(V.h('tr', [
V.h('th', _.t(label)),
value
]));
attributeEntry: function attributeEntry(el, label, value) {
if (value === null || value === undefined) {
return '';
}
var tr = document.createElement('tr');
var th = document.createElement('th');
th.textContent = _.t(label);
tr.appendChild(th);
var td = document.createElement('td');
if (typeof value === 'function') {
value(td);
} else {
td.appendChild(document.createTextNode(value));
}
tr.appendChild(td);
el.appendChild(tr);
return td;
},
showStat: function showStat(V, o, subst) {
var content = V.h('img', { attrs: { src: require('helper').listReplace(o.image, subst) } });
showStat: function showStat(o, subst) {
var content;
subst = typeof subst !== 'undefined' ? subst : {};
content = document.createElement('img');
content.src = require('helper').listReplace(o.image, subst);
var p = document.createElement('p');
if (o.href) {
return V.h('p', V.h('a', {
attrs:
{
href: require('helper').listReplace(o.href, subst),
target: '_blank',
title: require('helper').listReplace(o.title, subst)
}
}, content));
var link = document.createElement('a');
link.target = '_blank';
link.href = require('helper').listReplace(o.href, subst);
link.appendChild(content);
if (o.title) {
link.title = require('helper').listReplace(o.title, subst);
}
p.appendChild(link);
} else {
p.appendChild(content);
}
return V.h('p', content);
return p;
},
getTileBBox: function getTileBBox(s, map, tileSize, margin) {
@ -138,35 +176,20 @@ define({
return { minX: br.lat, minY: tl.lng, maxX: tl.lat, maxY: br.lng };
},
positionClients: function positionClients(ctx, p, startAngle, node, startDistance) {
if (node.clients === 0) {
positionClients: function positionClients(ctx, p, startAngle, clients, startDistance) {
if (clients === 0) {
return;
}
var radius = 3;
var a = 1.2;
var mode = 0;
ctx.beginPath();
ctx.fillStyle = config.client.wifi24;
for (var orbit = 0, i = 0; i < node.clients; orbit++) {
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 = node.clients - i;
var delta = clients - i;
for (var j = 0; j < Math.min(delta, n); i++, j++) {
if (mode !== 1 && i >= (node.clients_wifi24 + node.clients_wifi5)) {
mode = 1;
ctx.fill();
ctx.beginPath();
ctx.fillStyle = config.client.wifi5;
} else if (mode === 0 && i >= node.clients_wifi24) {
mode = 2;
ctx.fill();
ctx.beginPath();
ctx.fillStyle = config.client.other;
}
var angle = 2 * Math.PI / n * j;
var x = p.x + distance * Math.cos(angle + startAngle);
var y = p.y + distance * Math.sin(angle + startAngle);
@ -175,32 +198,5 @@ define({
ctx.arc(x, y, radius, 0, 2 * Math.PI);
}
}
ctx.fill();
},
fullscreen: function fullscreen(btn) {
if (!document.fullscreenElement && !document.webkitFullscreenElement && !document.mozFullScreenElement) {
var fel = document.firstElementChild;
var func = fel.requestFullscreen
|| fel.webkitRequestFullScreen
|| fel.mozRequestFullScreen;
func.call(fel);
btn.classList.remove('ion-full-enter');
btn.classList.add('ion-full-exit');
} else {
func = document.exitFullscreen
|| document.webkitExitFullscreen
|| document.mozCancelFullScreen;
if (func) {
func.call(document);
btn.classList.remove('ion-full-exit');
btn.classList.add('ion-full-enter');
}
}
},
escape: function escape(string) {
return string.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&#34;')
.replace(/'/g, '&#39;');
}
});

View File

@ -1,12 +1,11 @@
define(['polyglot', 'moment', 'helper'], function (Polyglot, moment, helper) {
'use strict';
return function () {
return function (config) {
var router;
function languageSelect(el) {
var select = document.createElement('select');
select.className = 'language-switch';
select.setAttribute('aria-label', 'Language');
select.addEventListener('change', setSelectLocale);
el.appendChild(select);
@ -21,8 +20,13 @@ define(['polyglot', 'moment', 'helper'], function (Polyglot, moment, helper) {
router.fullUrl({ lang: event.target.value }, false, true);
}
function setLocale(lang) {
localStorage.setItem('language', getLocale(lang));
location.reload();
}
function getLocale(input) {
var language = input || navigator.languages && navigator.languages[0] || navigator.language;
var language = input || localStorage.getItem('language') || navigator.languages && navigator.languages[0] || navigator.language || navigator.userLanguage;
var locale = config.supportedLocale[0];
config.supportedLocale.some(function (item) {
if (language.indexOf(item) !== -1) {
@ -55,7 +59,6 @@ define(['polyglot', 'moment', 'helper'], function (Polyglot, moment, helper) {
function init(r) {
router = r;
/** global: _ */
window._ = new Polyglot({ locale: getLocale(router.getLang()), allowMissing: true });
helper.getJSON('locale/' + _.locale() + '.json?' + config.cacheBreaker).then(setTranslation);
document.querySelector('html').setAttribute('lang', _.locale());
@ -64,6 +67,7 @@ define(['polyglot', 'moment', 'helper'], function (Polyglot, moment, helper) {
return {
init: init,
getLocale: getLocale,
setLocale: setLocale,
languageSelect: languageSelect
};
};

View File

@ -2,7 +2,7 @@ define(function () {
var self = {};
self.distance = function distance(a, b) {
return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2);
};
self.distancePoint = function distancePoint(a, b) {

View File

@ -1,144 +0,0 @@
define(['snabbdom', 'helper', 'moment'], function (V, helper, moment) {
'use strict';
V = V.default;
var self = {};
function showBar(v, width, warning) {
return V.h('span',
{ props: { className: 'bar' + (warning ? ' warning' : '') } },
[
V.h('span',
{
style: { width: (width * 100) + '%' }
}),
V.h('label', v)
]
);
}
self.showStatus = function showStatus(d) {
return V.h('td',
{ props: { className: d.is_online ? 'online' : 'offline' } },
_.t((d.is_online ? 'node.lastOnline' : 'node.lastOffline'), {
time: d.lastseen.fromNow(),
date: d.lastseen.format('DD.MM.YYYY, H:mm:ss')
}));
};
self.showGeoURI = function showGeoURI(d) {
if (!helper.hasLocation(d)) {
return undefined;
}
return V.h('td',
V.h('a',
{ props: { href: 'geo:' + d.location.latitude + ',' + d.location.longitude } },
Number(d.location.latitude.toFixed(6)) + ', ' + Number(d.location.longitude.toFixed(6))
)
);
};
self.showGateway = function showGateway(d) {
return d.is_gateway ? _.t('yes') : undefined;
};
self.showFirmware = function showFirmware(d) {
return [
helper.dictGet(d, ['firmware', 'release']),
helper.dictGet(d, ['firmware', 'base'])
].filter(function (n) {
return n !== null;
}).join(' / ') || undefined;
};
self.showUptime = function showUptime(d) {
return moment.utc(d.uptime).local().fromNow(true);
};
self.showFirstSeen = function showFirstSeen(d) {
return d.firstseen.fromNow(true);
};
self.showLoad = function showLoad(d) {
return showBar(d.loadavg.toFixed(2), d.loadavg / (d.nproc || 1), d.loadavg >= d.nproc);
};
self.showRAM = function showRAM(d) {
return showBar(Math.round(d.memory_usage * 100) + ' %', d.memory_usage, d.memory_usage >= 0.8);
};
self.showDomain = function showDomain(d) {
var rt = d.domain;
if (config.domainNames) {
config.domainNames.some(function (t) {
if (rt === t.domain) {
rt = t.name;
return true;
}
});
}
return rt;
};
self.showClients = function showClients(d) {
if (!d.is_online) {
return undefined;
}
var clients = [
V.h('span', [
d.clients > 0 ? d.clients : _.t('none'),
V.h('br'),
V.h('i', { props: { className: 'ion-people', title: _.t('node.clients') } })
]),
V.h('span',
{ props: { className: 'legend-24ghz' } },
[
d.clients_wifi24,
V.h('br'),
V.h('span', { props: { className: 'symbol', title: '2,4 GHz' } })
]),
V.h('span',
{ props: { className: 'legend-5ghz' } },
[
d.clients_wifi5,
V.h('br'),
V.h('span', { props: { className: 'symbol', title: '5 GHz' } })
]),
V.h('span',
{ props: { className: 'legend-others' } },
[
d.clients_other,
V.h('br'),
V.h('span', { props: { className: 'symbol', title: _.t('others') } })
])
];
return V.h('td', { props: { className: 'clients' } }, clients);
};
self.showIPs = function showIPs(d) {
var string = [];
var ips = d.addresses;
ips.sort();
ips.forEach(function (ip, i) {
if (i > 0) {
string.push(V.h('br'));
}
if (ip.indexOf('fe80:') !== 0) {
string.push(V.h('a', { props: { href: 'http://[' + ip + ']/', target: '_blank' } }, ip));
} else {
string.push(ip);
}
});
return V.h('td', string);
};
self.showAutoupdate = function showAutoupdate(d) {
return d.autoupdater.enabled ? _.t('node.activated', { branch: d.autoupdater.branch }) : _.t('node.deactivated');
};
return self;
});

View File

@ -3,7 +3,7 @@ define(['Navigo'], function (Navigo) {
return function (language) {
var init = false;
var objects = {};
var objects = { nodes: {}, links: {} };
var targets = [];
var views = {};
var current = {};
@ -16,20 +16,17 @@ define(['Navigo'], function (Navigo) {
}
function gotoNode(d) {
if (objects.nodeDict[d.nodeId]) {
if (d.nodeId in objects.nodes) {
targets.forEach(function (t) {
t.gotoNode(objects.nodeDict[d.nodeId], objects.nodeDict);
t.gotoNode(objects.nodes[d.nodeId]);
});
}
}
function gotoLink(d) {
var link = objects.links.filter(function (value) {
return value.id === d.linkId;
});
if (link) {
if (d.linkId in objects.links) {
targets.forEach(function (t) {
t.gotoLink(link);
t.gotoLink(objects.links[d.linkId]);
});
}
}
@ -54,7 +51,7 @@ define(['Navigo'], function (Navigo) {
};
if (lang && lang !== state.lang && lang === language.getLocale(lang)) {
location.reload();
language.setLocale(lang);
}
if (!init || viewValue && viewValue !== state.view) {
@ -82,10 +79,10 @@ define(['Navigo'], function (Navigo) {
}
}
var router = new Navigo(null, true, '#!');
var router = new Navigo(null, true);
router
.on(/^\/?#?!?\/([\w]{2})?\/?(map|graph)?\/?([a-f\d]{12})?([a-f\d\-]{25})?\/?(?:(\d+)\/(-?[\d.]+)\/(-?[\d.]+))?$/, customRoute)
.on(/^\/?#?\/([\w]{2})?\/?(map|graph)?\/?([a-f\d]{12})?([a-f\d\-]{25})?\/?(?:(\d+)\/([\d.]+)\/([\d.]+))?$/, customRoute)
.on({
'*': function () {
router.fullUrl();
@ -93,7 +90,7 @@ define(['Navigo'], function (Navigo) {
});
router.generateLink = function generateLink(data, full, deep) {
var result = '#!';
var result = '#';
if (full) {
data = Object.assign({}, state, data);
@ -119,7 +116,7 @@ define(['Navigo'], function (Navigo) {
};
router.getLang = function getLang() {
var lang = location.hash.match(/^\/?#!?\/([\w]{2})\//);
var lang = location.hash.match(/^\/?#\/([\w]{2})\//);
if (lang) {
state.lang = language.getLocale(lang[1]);
return lang[1];
@ -142,7 +139,16 @@ define(['Navigo'], function (Navigo) {
};
router.setData = function setData(data) {
objects = data;
objects.nodes = {};
objects.links = {};
data.nodes.all.forEach(function (d) {
objects.nodes[d.nodeinfo.node_id] = d;
});
data.graph.links.forEach(function (d) {
objects.links[d.id] = d;
});
};
return router;

View File

@ -1,99 +0,0 @@
define(function () {
'use strict';
/*
reimplate after node-deb-version-compare under MIT
(https://github.com/sdumetz/node-deb-version-compare)
*/
function Version(v) {
var version = /^[a-zA-Z]?([0-9]*(?=:))?:(.*)/.exec(v);
this.epoch = (version) ? version[1] : 0;
version = (version && version[2]) ? version[2] : v;
version = version.split('-');
this.debian = (version.length > 1) ? version.pop() : '';
this.upstream = version.join('-');
}
Version.prototype.compare = function (b) {
if ((this.epoch > 0 || b.epoch > 0) && Math.sign(this.epoch - b.epoch) !== 0) {
return Math.sign(this.epoch - b.epoch);
}
if (this.compareStrings(this.upstream, b.upstream) !== 0) {
return this.compareStrings(this.upstream, b.upstream);
}
return this.compareStrings(this.debian, b.debian);
};
Version.prototype.charCode = function (c) { // the lower the charcode the lower the version.
// if (c === '~') {return 0;} // tilde sort before anything
// else
if (/[a-zA-Z]/.test(c)) {
return c.charCodeAt(0) - 'A'.charCodeAt(0) + 1;
} else if (/[.:+-:]/.test(c)) {
return c.charCodeAt(0) + 'z'.charCodeAt(0) + 1;
} // charcodes are 46..58
return 0;
};
// find index of "val" in "ar".
Version.prototype.findIndex = function (ar, fn) {
for (var i = 0; i < ar.length; i++) {
if (fn(ar[i], i)) {
return i;
}
}
return -1;
};
Version.prototype.compareChunk = function (a, b) {
var ca = a.split('');
var cb = b.split('');
var diff = this.findIndex(ca, function (c, index) {
return !(cb[index] && c === cb[index]);
});
if (diff === -1) {
if (cb.length > ca.length) {
if (cb[ca.length] === '~') {
return 1;
}
return -1;
}
return 0; // no diff found and same length
} else if (!cb[diff]) {
return (ca[diff] === '~') ? -1 : 1;
}
return (this.charCode(ca[diff]) > this.charCode(cb[diff])) ? 1 : -1;
};
Version.prototype.compareStrings = function (a, b) {
if (a === b) {
return 0;
}
var parseA = /([^0-9]+|[0-9]+)/g;
var parseB = /([^0-9]+|[0-9]+)/g;
var ra = parseA.exec(a);
var rb = parseB.exec(b);
while (ra !== null && rb !== null) {
if ((isNaN(ra[1]) || isNaN(rb[1])) && ra[1] !== rb[1]) { // a or b is not a number and they're not equal. Note : "" IS a number so both null is impossible
return this.compareChunk(ra[1], rb[1]);
} // both are numbers
if (ra[1] !== rb[1]) {
return (parseInt(ra[1], 10) > parseInt(rb[1], 10)) ? 1 : -1;
}
ra = parseA.exec(a);
rb = parseB.exec(b);
}
if (!ra && rb) { // rb doesn't get exec-ed when ra == null
return (rb.length > 0 && rb[1].split('')[0] === '~') ? 1 : -1;
} else if (ra && !rb) {
return (ra[1].split('')[0] === '~') ? -1 : 1;
}
return 0;
};
return function compare(a, b) {
var va = new Version(a[0]);
var vb = new Version(b[0]);
return vb.compare(va);
};
});

View File

@ -1,96 +0,0 @@
{
"node": {
"all": "Všechny uzly",
"nodes": "Uzly",
"uptime": "Celková doba provozu",
"links": "Odkazy",
"clients": "Klienti",
"distance": "Vzdálenost",
"connectionType": "typ připojení",
"tq": "tq",
"lastOnline": "poslední on-line %{time} (%{date})",
"lastOffline": "lastOffline %{time} (%{date})",
"activated": "aktivováno (%{branch})",
"deactivated": "deaktivováno",
"status": "Stav",
"firmware": "Verze firmwaru",
"hardware": "Model hardwaru",
"visible": "Visible on the map",
"update": "Automatický update",
"domain": "Domain",
"gateway": "Brána",
"coordinates": "Souřadnice",
"contact": "Kontakt",
"primaryMac": "Hlavní MAC",
"id": "Identifikace uzlu",
"firstSeen": "firstSeen",
"systemLoad": "Průměrné zatížení",
"ram": "Využití paměti",
"ipAddresses": "IP adresa",
"nexthop": "Další skok",
"selectedGatewayIPv4": "vybranýGatewayIPv4",
"selectedGatewayIPv6": "vybranýGatewayIPv6",
"link": "Odkaz ||| Odkazy",
"node": "Uzel ||| Uzly",
"new": "Nové uzly",
"missing": "Zmizelé uzly"
},
"location": {
"location": "Poloha",
"latitude": "Zeměpisná šířka",
"longitude": "Zeměpisná délka",
"copy": "Kopírovat"
},
"sidebar": {
"nodeFilter": "nodeFilter",
"nodes": "%{total} uzly, %{online} uzly on-line",
"clients": "%{smart_count} klienti |||| %{smart_count} klienti",
"gateway": " %{smart_count} gateway |||| %{smart_count} gateways",
"lastUpdate": "Poslední update",
"nodeNew": "nodeNew",
"nodeOnline": "Uzel je online",
"nodeOffline": "Uzel je offline",
"aboutInfo": "aboutInfo",
"actual": "aktuální",
"stats": "Statistika",
"about": "O produktu",
"toggle": "přepínat"
},
"button": {
"switchView": "Přepnout zobrazení",
"location": "Vybrat souřadnice",
"tracking": "Lokalizace"
},
"momentjs": {
"calendar": {
"sameDay": "[Today at] LT",
"nextDay": "[Tomorrow at] LT",
"nextWeek": "dddd [at] LT",
"lastDay": "[Yesterday at] LT",
"lastWeek": "[Last] dddd [at] LT",
"sameElse": "L"
},
"relativeTime": {
"future": "in %s",
"past": "%s ago",
"s": "Několik sekund",
"m": "minuta",
"mm": "%d minut",
"h": "an hour",
"hh": "%d hodin",
"d": "den",
"dd": "%d dnů",
"M": "měsíc",
"MM": "%d měsíců",
"y": "rok",
"yy": "%d let"
}
},
"yes": "ano",
"no": "ne",
"unknown": "neznámý",
"others": "ostatní",
"none": "žádný",
"remove": "odstranit",
"close": "zavřít"
}

View File

@ -6,7 +6,6 @@
"links": "Verbindungen",
"clients": "Nutzer",
"distance": "Entfernung",
"connectionType": "Verbindungsart",
"tq": "Übertragungsqualität",
"lastOnline": "online, letzte Nachricht %{time} (%{date})",
"lastOffline": "offline, letzte Nachricht %{time} (%{date})",
@ -17,19 +16,17 @@
"hardware": "Geräte-Modell",
"visible": "Auf der Karte sichtbar",
"update": "Auto-Update",
"domain": "Domain",
"site": "Site",
"gateway": "Gateway",
"coordinates": "Koordinaten",
"contact": "Kontakt",
"primaryMac": "Primäre MAC",
"id": "Knoten ID",
"firstSeen": "Erstmals gesehen",
"systemLoad": "Systemlast",
"systemLoad": "Load average",
"ram": "Speicherauslastung",
"ipAddresses": "IP Adressen",
"nexthop": "Nächster Sprung",
"selectedGatewayIPv4": "Gewähltes ipv4 Gateway",
"selectedGatewayIPv6": "Gewähltes ipv6 Gateway",
"selectedGateway": "Gewähltes Gateway",
"link": "Verbindung |||| Verbindungen",
"node": "Knoten",
"new": "Neue Knoten",
@ -47,20 +44,18 @@
"clients": "mit %{smart_count} Nutzer |||| mit %{smart_count} Nutzern",
"gateway": "auf %{smart_count} Gateway |||| auf %{smart_count} Gateways",
"lastUpdate": "Letzte Aktualisierung",
"nodeNew": "neu",
"nodeOnline": "online",
"nodeOffline": "offline",
"nodeNew": "Knoten ist neu",
"nodeOnline": "Knoten ist online",
"nodeOffline": "Knoten ist offline",
"aboutInfo": "<h2>Über Meshviewer</h2><p>Mit Doppelklick kann man in die Karte hinein zoomen und Shift+Doppelklick heraus zoomen.</p>",
"actual": "Aktuell",
"stats": "Statistiken",
"about": "Über",
"toggle": "Seitenleiste anzeigen/ausblenden"
"about": "Über"
},
"button": {
"switchView": "Ansicht wechseln",
"location": "Koordinaten wählen",
"tracking": "Lokalisierung",
"fullscreen": "Vollbildmodus wechseln"
"tracking": "Lokalisierung"
},
"momentjs": {
"calendar": {
@ -90,8 +85,5 @@
"yes": "ja",
"no": "nein",
"unknown": "unbekannt",
"others": "andere",
"none": "keine",
"remove": "entfernen",
"close": "schließen"
"none": "keine"
}

View File

@ -6,7 +6,6 @@
"links": "Links",
"clients": "Clients",
"distance": "Distance",
"connectionType": "Connection type",
"tq": "Transmit quality",
"lastOnline": "online, last message %{time} (%{date})",
"lastOffline": "offline, last message %{time} (%{date})",
@ -17,7 +16,7 @@
"hardware": "Hardware model",
"visible": "Visible on the map",
"update": "Auto update",
"domain": "Domain",
"site": "Site",
"gateway": "Gateway",
"coordinates": "Coordinates",
"contact": "Contact",
@ -27,9 +26,7 @@
"systemLoad": "Load average",
"ram": "Memory usage",
"ipAddresses": "IP addresses",
"nexthop": "Nexthop",
"selectedGatewayIPv4": "Selected ipv4-gateway",
"selectedGatewayIPv6": "Selected ipv6-gateway",
"selectedGateway": "Selected gateway",
"link": "Link |||| Links",
"node": "Node |||| Nodes",
"new": "New nodes",
@ -47,20 +44,18 @@
"clients": "with %{smart_count} client |||| with %{smart_count} clients",
"gateway": "on %{smart_count} gateway |||| on %{smart_count} gateways",
"lastUpdate": "Last update",
"nodeNew": "new",
"nodeOnline": "online",
"nodeOffline": "offline",
"nodeNew": "Node is new",
"nodeOnline": "Node is online",
"nodeOffline": "Node is offline",
"aboutInfo": "<h2>About Meshviewer</h2> <p>You can zoom in with double-click and zoom out with shift+double-click</p>",
"actual": "Current",
"stats": "Statistics",
"about": "About",
"toggle": "Toggle Sidebar"
"about": "About"
},
"button": {
"switchView": "Switch view",
"location": "Pick coordinates",
"tracking": "Localisation",
"fullscreen": "Toggle fullscreen"
"tracking": "Localisation"
},
"momentjs": {
"calendar": {
@ -90,8 +85,5 @@
"yes": "yes",
"no": "no",
"unknown": "unknown",
"others": "other",
"none": "none",
"remove": "remove",
"close": "close"
"none": "none"
}

View File

@ -6,7 +6,6 @@
"links": "Connexion",
"clients": "Clients",
"distance": "Distance",
"connectionType": "Type de connexion",
"tq": "Qualité de transmission",
"lastOnline": "en ligne, dernier message %{time} (%{date})",
"lastOffline": "hors ligne, dernier message %{time} (%{date})",
@ -17,7 +16,7 @@
"hardware": "Modèle matériel",
"visible": "Visible sur la carte",
"update": "Mise à jour automatique",
"domain": "Domain",
"site": "Site",
"gateway": "Passerelle",
"coordinates": "Coordonnées",
"contact": "Contact",
@ -27,9 +26,7 @@
"systemLoad": "Charge moyenne",
"ram": "Utilisation de la mémoire",
"ipAddresses": "Adresse IP",
"nexthop": "Nexthop",
"selectedGatewayIPv4": "Selected ipv4-gateway",
"selectedGatewayIPv6": "Selected ipv6-gateway",
"selectedGateway": "Passerelle sélectionné",
"link": "Connexion |||| Connexions",
"node": "Nœud |||| Nœuds",
"new": "Nouveaux nœuds",
@ -53,8 +50,7 @@
"aboutInfo": "<h2>Sur Meshviewer</h2> <p>Vous pouvez zoomer avec double-clic et effectuer un zoom arrière avec shift + double-clic</p>",
"actual": "Actuel",
"stats": "Statistiques",
"about": "À propros",
"toggle": "Toggle Sidebar"
"about": "À propros"
},
"button": {
"switchView": "Basculer laffichage",
@ -89,8 +85,5 @@
"yes": "oui",
"no": "non",
"unknown": "inconnu",
"others": "autres",
"none": "aucun",
"remove": "supprimer",
"close": "fermer"
"none": "aucun"
}

View File

@ -6,7 +6,6 @@
"links": "Ссылки",
"clients": "Клиенты",
"distance": "Расстояние",
"connectionType": "Тип подключения",
"tq": "Качество связи",
"lastOnline": "в сети, последнее сообщение %{time} (%{date})",
"lastOffline": "не в сети, последнее сообщение %{time} (%{date})",
@ -17,7 +16,7 @@
"hardware": "Тип оборудования",
"visible": "Видно на карте",
"update": "Автообновление",
"domain": "Сайт",
"site": "Сайт",
"gateway": "Шлюз",
"coordinates": "Координаты",
"contact": "Контакты",
@ -27,9 +26,7 @@
"systemLoad": "Средняя загрузка",
"ram": "Используемая память",
"ipAddresses": "IP адреса",
"nexthop": "Следующий скачок",
"selectedGatewayIPv4": "Выбранный шлюз ipv4",
"selectedGatewayIPv6": "Выбранный шлюз ipv6",
"selectedGateway": "Выбранный шлюз",
"link": "Ссылка |||| Ссылки",
"node": "Узел |||| Узлы",
"new": "Новые узлы",
@ -53,8 +50,7 @@
"aboutInfo": "<h2>О Meshviewer</h2> <p>Вы можете увеличить масштаб двойным щелчком мыши и уменьшить с shift + двойной щелчок</p>",
"actual": "Текущее",
"stats": "Статистика",
"about": "О продукте",
"toggle": "Включить панель"
"about": "О продукте"
},
"button": {
"switchView": "Переключить вид",
@ -89,8 +85,5 @@
"yes": "да",
"no": "нет",
"unknown": "неизвестно",
"others": "другие",
"none": "нет",
"remove": "убрать",
"close": "закрыть"
"none": "нет"
}

View File

@ -1,96 +0,0 @@
{
"node": {
"all": "Bütün düğümler",
"nodes": "Düğümler",
"uptime": "Çalışma süresi",
"links": "Bağlantılar",
"clients": "Müşteriler",
"distance": "Mesafe",
"connectionType": "Bağlantı türü",
"tq": "İletim kalitesi",
"lastOnline": "çevrimiçi, son mesaj %{time} (%{date})",
"lastOffline": "çevrimdışı, son mesaj %{time} (%{date})",
"activated": "aktif (%{branch})",
"deactivated": "devredışı bırakıldı",
"status": "Durum",
"firmware": "Yazılım versiyonu",
"hardware": "Donanım modeli",
"visible": "Harita üzerinde görünür",
"update": "Otomatik güncelleme",
"domain": "Domain",
"gateway": "Geçit",
"coordinates": "Koordinatlar",
"contact": "İlişki",
"primaryMac": "Birincil MAC",
"id": "Düğüm kimliği",
"firstSeen": "İlk görülme",
"systemLoad": "Ortalama yük",
"ram": "Bellek kullanımı",
"ipAddresses": "IP adresleri",
"nexthop": "Bir sonraki atlama",
"selectedGatewayIPv4": "Seçili Ipv4-ağ geçidi",
"selectedGatewayIPv6": "Seçili Ipv6-ağ geçidi",
"link": "Bağlantı ||| Bağlantılar",
"node": "Düğüm ||| Düğümler",
"new": "Yeni düğümler",
"missing": "Kaybolan düğümler"
},
"location": {
"location": "Konum",
"latitude": "Enlem",
"longitude": "Boylam",
"copy": "Kopya"
},
"sidebar": {
"nodeFilter": "Düğüm Filtresi",
"nodes": "%{total} düğümler, %{online} çevrimiçi düğümler dahil",
"clients": "%{smart_count} müşteri ile |||| %{smart_count} müşteriler ile",
"gateway": "%{smart_count} geçit üzerinde |||| %{smart_count} geçitler üzerinde",
"lastUpdate": "Son güncelleme",
"nodeNew": "yeni",
"nodeOnline": "çevrimiçi",
"nodeOffline": "çevrimdışı",
"aboutInfo": "<h2>Meshviewer Hakkında</h2> <p>Çift tıklayarak yakınlaştırabilir ve Shift tuşuna basıp+çift tıklayarak uzaklaştırabilirsiniz</p>",
"actual": "Mevcut",
"stats": "İstatistikler",
"about": "Hakkında",
"toggle": "Kenar çubuğunu değiştir"
},
"button": {
"switchView": "Görünümü Değiştir",
"location": "Koordinatları seç",
"tracking": "Yerelleştirme"
},
"momentjs": {
"calendar": {
"sameDay": "[Bugün] LT",
"nextDay": "[Yarın] LT",
"nextWeek": "dddd [at] LT",
"lastDay": "[Dün] LT",
"lastWeek": "[Last] dddd [at] LT",
"sameElse": "L"
},
"relativeTime": {
"future": "%s içinde",
"past": "%s önce",
"s": "birkaç saniye",
"m": "bir dakika",
"mm": "%d dakikalar",
"h": "bir saat",
"hh": "%d saatler",
"d": "bir gün",
"dd": "%d günler",
"M": "bir ay",
"MM": "%d aylar",
"y": "bir yıl",
"yy": "%d yıllar"
}
},
"yes": "evet",
"no": "hayır",
"unknown": "bilinmeyen",
"others": "diğer",
"none": "hiçbiri",
"remove": "kaldır",
"close": "kapat"
}

View File

@ -1,6 +1,5 @@
{
"name": "meshviewer",
"version": "11.1.0",
"name": "ffrgb-meshviewer",
"license": "AGPL-3.0",
"repository": {
"type": "git",
@ -10,30 +9,28 @@
"url": "https://github.com/ffrgb/meshviewer/issues"
},
"devDependencies": {
"babel-eslint": "^10.0.1",
"browser-sync": "^2.26.5",
"del": "^5.1.0",
"eslint": "^6.5.1",
"eslint-config-airbnb-es5": "^1.2.0",
"babel-eslint": "^7.2.3",
"browser-sync": "^2.18.8",
"eslint": "^3.19.0",
"eslint-config-airbnb-es5": "^1.1.0",
"eslint-config-defaults": "^9.0.0",
"eslint-plugin-react": "^7.12.4",
"gulp": "^4.0.1",
"gulp-autoprefixer": "^7.0.1",
"gulp-cache-bust": "^1.4.0",
"gulp-cli": "^2.2.0",
"eslint-plugin-react": "^7.0.0",
"gulp": "github:gulpjs/gulp#4.0",
"gulp-autoprefixer": "^3.1.1",
"gulp-cache-bust": "^1.1.0",
"gulp-environments": "^0.1.2",
"gulp-eslint": "^6.0.0",
"gulp-htmlmin": "^5.0.1",
"gulp-inject": "^5.0.2",
"gulp-inline-source": "^4.0.0",
"gulp-jsonminify": "^1.1.0",
"gulp-load-plugins": "^2.0.1",
"gulp-real-favicon": "^0.3.2",
"gulp-requirejs-optimize": "^1.3.0",
"gulp-sass": "^4.0.2",
"gulp-sass-lint": "^1.4.0",
"gulp-sourcemaps": "^2.6.5",
"gulp-uglify": "^3.0.2"
"gulp-eslint": "^3.0.1",
"gulp-htmlmin": "^3.0.0",
"gulp-inject": "^4.2.0",
"gulp-jsonminify": "^1.0.0",
"gulp-kyh-inline-source": "^3.0.2",
"gulp-load-plugins": "^1.5.0",
"gulp-real-favicon": "^0.2.2",
"gulp-requirejs-optimize": "^1.2.0",
"gulp-sass": "^3.1.0",
"gulp-sass-lint": "^1.3.2",
"gulp-sourcemaps": "^2.6.0",
"gulp-uglify": "^2.1.2"
},
"eslintConfig": {
"env": {
@ -45,23 +42,17 @@
},
"dependencies": {
"almond": "^0.3.3",
"d3-drag": "^1.2.4",
"d3-force": "^1.2.1",
"d3-selection": "^1.4.0",
"d3-zoom": "^1.8.3",
"leaflet": "^1.5.1",
"moment": "^2.24.0",
"navigo": "^7.1.2",
"node-polyglot": "2.2.2",
"promise-polyfill": "^8.1.3",
"rbush": "^3.0.1",
"requirejs": "^2.3.6",
"snabbdom": "^0.7.3"
},
"scripts": {
"gulp": "./node_modules/gulp-cli/bin/gulp.js"
},
"browserslist": [
"> 1% in DE"
]
"d3-drag": "^1.0.4",
"d3-force": "^1.0.6",
"d3-selection": "^1.0.6",
"d3-zoom": "^1.1.3",
"leaflet": "^1.0.3",
"moment": "^2.17.1",
"navigo": "^4.7.1",
"node-polyglot": "^2.2.2",
"promise-polyfill": "^6.0.2",
"rbush": "^2.0.1",
"requirejs": "^2.3.2",
"snabbdom": "^0.6.4"
}
}

View File

@ -7,8 +7,56 @@ if (!String.prototype.includes) {
};
}
if (!String.prototype.startsWith) {
String.prototype.startsWith = function (searchString, position) {
position = position || 0;
return this.substr(position, searchString.length) === searchString;
};
}
if (!String.prototype.repeat) {
String.prototype.repeat = function (count) {
'use strict';
if (this === null) {
throw new TypeError('can\'t convert ' + this + ' to object');
}
var str = '' + this;
count = +count;
if (count < 0) {
throw new RangeError('repeat count must be non-negative');
}
if (count === Infinity) {
throw new RangeError('repeat count must be less than infinity');
}
count = Math.floor(count);
if (str.length === 0 || count === 0) {
return '';
}
// Ensuring count is a 31-bit integer allows us to heavily optimize the
// main part. But anyway, most current (August 2014) browsers can't handle
// strings 1 << 28 chars or longer, so:
if (str.length * count >= 1 << 28) {
throw new RangeError('repeat count must not overflow maximum string size');
}
var rpt = '';
for (; ;) {
if ((count & 1) === 1) {
rpt += str;
}
count >>>= 1;
if (count === 0) {
break;
}
str += str;
}
// Could we try:
// return Array(count + 1).join(this);
return rpt;
};
}
if (typeof Object.assign !== 'function') {
Object.assign = function (target, varArgs) { // .length of function is 2
Object.assign = function(target, varArgs) { // .length of function is 2
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
@ -30,25 +78,3 @@ if (typeof Object.assign !== 'function') {
return to;
};
}
// eslint-disable-next-line consistent-return
(function () {
if (typeof window.CustomEvent === 'function') {
return false;
}
function CustomEvent(event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js');
}

View File

@ -8,3 +8,94 @@
// SCSS supports css with a lot of additional features like variables or mixins.
// Autoprefixer runs in postcss, no need to add browser-prefixes like -webkit, -moz or -ms
@font-face {
font-family: 'Exo 2';
font-weight: 300;
src: local('Exo 2 Light'),
local('Exo2-Light'),
url('/typo3conf/ext/freifunk_regensburg/Resources/Public/Fonts/Exo2.0-Light-webfont.woff2') format('woff2'),
url('/typo3conf/ext/freifunk_regensburg/Resources/Public/Fonts/Exo2.0-Light-webfont.woff') format('woff'),
url('/typo3conf/ext/freifunk_regensburg/Resources/Public/Fonts/Exo2.0-Light-webfont.ttf') format('truetype');
}
@font-face {
font-family: 'Exo 2';
font-weight: 800;
src: local('Exo 2 Black'),
local('Exo2-Black'),
url('/typo3conf/ext/freifunk_regensburg/Resources/Public/Fonts/Exo2.0-Black-webfont.woff2') format('woff2'),
url('/typo3conf/ext/freifunk_regensburg/Resources/Public/Fonts/Exo2.0-Black-webfont.woff') format('woff'),
url('/typo3conf/ext/freifunk_regensburg/Resources/Public/Fonts/Exo2.0-Black-webfont.ttf') format('truetype');
}
a {
color: $color-freifunk-primary;
}
%leaflet-button {
background-color: $color-freifunk-primary;
color: $color-freifunk-secondary;
opacity: .9;
&.active,
&:hover {
color: $color-white;
opacity: 1;
}
}
button {
@extend %leaflet-button;
&.close {
&.active,
&:hover {
color: $color-primary;
}
}
}
.leaflet-control-zoom {
opacity: 1;
a {
@extend %leaflet-button;
}
}
.leaflet-control-layers {
&.leaflet-control {
opacity: 1;
}
}
.leaflet-container {
.leaflet-control-layers-toggle {
@extend %leaflet-button;
}
}
.infobox {
.clients {
color: $color-freifunk-primary;
}
}
.ion-location {
color: $color-freifunk-primary;
}
.leaflet-label {
&.leaflet-label-right {
background-color: $color-white;
border: 2px solid $color-freifunk-primary;
border-radius: 0;
font-weight: normal;
opacity: .8;
&::before {
display: none;
}
}
}

View File

@ -2,3 +2,21 @@
//$color-black: #fff;
//$color-white: invert($color-white);
//$color-primary: invert($color-primary);
$color-freifunk-primary: #e32d6d;
$color-freifunk-secondary: #f4c72f;
$color-freifunk-gray-light: #e9e9e9;
$color-freifunk-gray: #504f4e;
$color-freifunk-gray-dark: #3f3f3e;
$color-freifunk-gray-darker: #373636;
$color-black: $color-freifunk-gray-darker;
$color-primary: $color-freifunk-primary;
$color-gray-dark: $color-freifunk-gray-dark;
$font-family: 'Exo 2', sans-serif;
$font-family-secondary: 'Exo 2', sans-serif;
$shadows: 0;
$use-included-font: 0;

View File

@ -3,6 +3,7 @@
@import 'custom/variables';
// Mixins
@import 'mixins/shadow';
@import 'mixins/icon';
@import 'mixins/font';

14
scss/mixins/_shadow.scss Normal file
View File

@ -0,0 +1,14 @@
// Original is in LESS and can be found here: https://gist.github.com/gefangenimnetz/3ef3e18364edf105c5af
@mixin shadow($level: 1) {
@if $level == 1 {
box-shadow: 0 1px 3px transparentize($color-black, .88), 0 1px 2px transparentize($color-black, .76);
} @else if $level == 2 {
box-shadow: 0 3px 6px transparentize($color-black, .84), 0 3px 6px transparentize($color-black, .77);
} @else if $level == 3 {
box-shadow: 0 10px 20px transparentize($color-black, .81), 0 6px 6px transparentize($color-black, .77);
} @else if $level == 4 {
box-shadow: 0 14px 28px transparentize($color-black, .75), 0 10px 10px transparentize($color-black, .78);
} @else if $level == 5 {
box-shadow: 0 19px 38px transparentize($color-black, .7), 0 15px 12px transparentize($color-black, .78);
}
}

View File

@ -13,12 +13,6 @@ header {
border-bottom: 1px solid darken($color-white, 10%);
}
textarea,
input {
background: transparent;
color: $color-black, 100;
}
h1,
h2,
h3,
@ -28,7 +22,11 @@ h6 {
font-weight: bold;
}
h1,
h1 {
font-size: 2em;
padding: .67em 0;
}
h2 {
font-size: 1.5em;
padding: .83em 0;
@ -39,7 +37,6 @@ h3 {
padding: 1em 0;
}
h1,
h2,
h3 {
padding-left: $button-distance;
@ -60,10 +57,6 @@ img {
a {
color: $color-online;
text-decoration: none;
&:focus {
color: darken($color-online, 15%);
}
}
p {
@ -77,15 +70,3 @@ strong {
.hide {
display: none !important; // sass-lint:disable-line no-important
}
.sr-only {
border: 0;
clip: rect(0, 0, 0, 0);
clip-path: inset(50%);
height: 1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
}

View File

@ -29,8 +29,7 @@ button {
}
}
&.active,
&:focus {
&.active {
box-shadow: 0 0 0 2px $color-primary;
}
@ -38,6 +37,20 @@ button {
color: $color-primary;
}
@if $shadows == 1 {
&.shadow {
@include shadow(1);
&:hover {
@include shadow(2);
}
&:active {
box-shadow: inset 0 5px 20px transparentize($color-black, .81), inset 0 3px 6px transparentize($color-black, .77);
}
}
}
// Tooltip
&[data-tooltip] {
&::after {
@ -65,6 +78,9 @@ button {
&.close {
background-color: transparent;
border-radius: 0;
@if $shadows == 1 {
box-shadow: none;
}
color: transparentize($color-black, .5);
float: right;
font-size: $button-font-size;
@ -74,33 +90,3 @@ button {
width: auto;
}
}
// Tooltip
// sass-lint:disable-block nesting-depth
.content,
.sidebar > {
button {
&[aria-label] {
&::after {
background: $color-black;
border-radius: 3px;
color: $color-white;
content: attr(aria-label);
font-family: $font-family;
font-size: $font-size;
padding: 0 12px;
position: absolute;
transform: translate(45px, 52px);
visibility: hidden;
white-space: nowrap;
}
&:hover {
&::after {
transition: visibility 0s linear .3s;
visibility: visible;
}
}
}
}
}

View File

@ -57,10 +57,6 @@
outline: none;
padding: 0 2px;
width: 100%;
&:focus {
background: transparentize($color-primary, .95);
}
}
button {

View File

@ -1,30 +1,7 @@
.infobox {
.clients,
.gateway {
display: flex;
flex-flow: wrap;
span {
flex-grow: 1;
text-align: center;
}
.ion-people,
.ion-arrow-right-c {
font-size: 1.5em;
}
}
.node-links {
table-layout: fixed;
th,
td {
&:nth-child(3),
&:nth-child(5) {
width: 12%;
}
}
.clients {
color: $color-online;
font-family: $font-family-icons;
}
input,

View File

@ -1,5 +1,5 @@
header {
h1 {
h2 {
display: inline-block;
}
}
@ -17,23 +17,13 @@ header {
}
.legend {
a {
margin-right: 10px;
.symbol {
border-radius: 50%;
display: inline-block;
height: 1em;
vertical-align: -5%;
width: 1em;
}
span {
&:not(:first-child) {
margin-left: 1em;
}
}
}
.symbol {
border-radius: 50%;
display: inline-block;
height: 1em;
vertical-align: -5%;
width: 1em;
}
// Dot looks compared to thin font a bit darker - lighten it 10%
@ -55,20 +45,7 @@ header {
}
}
.legend-24ghz {
.symbol {
background-color: $color-24ghz;
}
}
.legend-5ghz {
.symbol {
background-color: $color-5ghz;
}
}
.legend-others {
.symbol {
background-color: $color-others;
}
.legend-online,
.legend-offline {
margin-left: 1em;
}

View File

@ -16,10 +16,9 @@
}
@media screen and (max-width: map-get($grid-breakpoints, lg) - 1) {
right: .1rem;
right: -1rem;
top: 0;
transform: scale(.8);
transform-origin: right;
}
}

View File

@ -16,7 +16,6 @@
background: $color-new;
display: inline-block;
height: 1.4em;
max-width: 100%;
}
label {

View File

@ -16,14 +16,9 @@
span {
box-sizing: border-box;
color: $color-white;
display: inline-block;
font-weight: bold;
min-width: 1.5em;
padding: .25em .5em;
}
a {
cursor: pointer;
}
}

View File

@ -12,13 +12,6 @@
.sidebarhandle {
left: $button-distance;
transform: scale(-1, 1);
// sass-lint:disable-block nesting-depth
&[aria-label] {
&::after {
transform: scale(-1, 1) translate(105px, 52px) !important; // sass-lint:disable-line no-important
}
}
}
@media screen and (max-width: map-get($grid-breakpoints, lg) - 1) {
width: auto;
@ -30,8 +23,7 @@
}
.node-list,
.node-links,
.link-list {
.node-links {
th,
td {
&:first-child {
@ -48,29 +40,36 @@
}
}
.link-list {
th,
td {
&:nth-child(2) {
width: 60%;
}
}
}
.node-links {
padding-bottom: 15px;
th,
td {
&:first-child {
width: 35px;
width: 50px;
}
}
}
.link-list {
th,
td {
&:nth-child(1) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 66%;
}
}
}
.container {
@if $shadows == 1 {
@include shadow(2);
} @else {
border-right: 1px solid darken($color-white, 10%);
}
background: transparentize($color-white, .03);
border-right: 1px solid darken($color-white, 10%);
min-height: 100vh;
overflow-y: visible;
@ -92,6 +91,9 @@
.container,
.infobox {
border-radius: 0;
@if $shadows == 1 {
box-shadow: none;
}
margin: 0;
}
}
@ -118,19 +120,13 @@
left: $sidebar-width + 2 * $button-distance;
position: fixed;
top: $button-distance;
transition: left .5s, color .5s, transform .5s;
transition: left .5s, box-shadow .5s, color .5s, transform .5s;
z-index: 1010;
&::before {
&::after {
content: '\f124';
padding-right: .125em;
}
&[aria-label] {
&::after {
transform: translate(-45px, 52px) !important; // sass-lint:disable-line no-important
}
}
}
.online {
@ -140,3 +136,7 @@
.offline {
color: $color-offline;
}
.unseen {
color: $color-unseen;
}

View File

@ -22,16 +22,6 @@ table {
}
}
tr {
&.header {
font-size: 1.2em;
th {
padding-top: 1em;
}
}
}
td,
th {
line-height: 1.41em;

View File

@ -1,7 +1,11 @@
.tabs {
@if $shadows == 1 {
@include shadow(1);
} @else {
border: 0 solid darken($color-white, 10%);
border-bottom-width: 1px;
}
background: transparentize($color-black, .98);
border: 0 solid darken($color-white, 10%);
border-bottom-width: 1px;
display: flex;
display: -webkit-flex; // sass-lint:disable-line no-vendor-prefixes no-duplicate-properties
list-style: none;

Some files were not shown because too many files have changed in this diff Show More