Compare commits
246 Commits
Author | SHA1 | Date | |
---|---|---|---|
8a756f0ecb | |||
b1a1df1a56 | |||
35cbfad9b0 | |||
68de16e73b | |||
9fe1d0fab8 | |||
538e47ed0e | |||
fb12482833 | |||
f5317e4559 | |||
07fbfe6a63 | |||
0fc62b342e | |||
b6a4d02507 | |||
4d046c2c8e | |||
|
71da7ebd8c | ||
|
50188b9b1a | ||
|
d29919556d | ||
|
0cd0af750d | ||
|
7429c5dc58 | ||
a37f26c256 | |||
a7cfaa8b0a | |||
fa9d990428 | |||
78ab3c0796 | |||
e65544a31b | |||
6563c9073e | |||
d1e9c75c0a | |||
ab67d494c9 | |||
cf3d47bdd7 | |||
441e1e61be | |||
b4b9d42bf4 | |||
a768496858 | |||
d987c63461 | |||
536a6c920f | |||
f14ba5d954 | |||
1347254bb2 | |||
614093d2af | |||
|
3c0cd2a36d | ||
|
4fd562b84b | ||
|
e448b02bb4 | ||
|
4d47587278 | ||
|
1fa034e1e8 | ||
|
1bcc6bfd10 | ||
|
35a79ba4f6 | ||
|
d28e834cd5 | ||
|
f40179535f | ||
|
1dfe8951ac | ||
|
3a3a3e014c | ||
|
e083ed13a7 | ||
|
8a89c0f5db | ||
|
ec0e0e5445 | ||
|
157da0aeba | ||
|
8959e1e7cb | ||
|
ddb1482a67 | ||
|
6c8c12be1e | ||
|
22af6d1bce | ||
|
90a19a4e74 | ||
|
b6f1dd066e | ||
|
d021d312d7 | ||
|
98e7a55f6f | ||
|
2736461604 | ||
|
c0ea13a8fa | ||
|
2200c4bc19 | ||
|
c99e4d8c42 | ||
|
aeb849aca8 | ||
|
3dc4d10082 | ||
|
97cda8cb18 | ||
|
5ad56139ac | ||
|
c45f367fcd | ||
|
29ea71a4d0 | ||
|
d2033af7d7 | ||
|
69e37f779f | ||
|
a34812f2a9 | ||
|
c02cf3be95 | ||
|
84085727a6 | ||
|
8fdc8dcabb | ||
|
77c94a1f2e | ||
|
2604b2b731 | ||
|
31d0209cc2 | ||
|
bc82e07354 | ||
|
fd9eab724b | ||
|
5d1d72d95d | ||
|
8bf3498744 | ||
|
2192500d05 | ||
|
712c3f21ce | ||
|
3d113c6247 | ||
|
9c596531e2 | ||
|
e1aa152055 | ||
|
d29bb31311 | ||
|
2c6303d820 | ||
|
2a7e1cdaa6 | ||
|
3311e70296 | ||
|
e1d3a3d7b2 | ||
|
25212adb81 | ||
|
4fd4e27a8b | ||
|
bfb1111744 | ||
|
81a26b5560 | ||
|
02e02f9219 | ||
|
20a8c4583a | ||
|
51be472ce4 | ||
|
e75d865fbf | ||
|
4f078f4b56 | ||
|
15f8f4bd74 | ||
|
3c72e12c3a | ||
|
28df47c15f | ||
|
ded730f36c | ||
|
ad4ee19344 | ||
|
48aa3a486b | ||
|
ad89c910ab | ||
|
7aab2f1462 | ||
|
61b9e9d351 | ||
|
3085c14c3b | ||
|
100cf626a5 | ||
|
6d57ad35c3 | ||
|
0c03efbfff | ||
|
ef9b9c3c3f | ||
|
1b89f86d74 | ||
|
b294a5d405 | ||
|
957cc892a9 | ||
|
6c0e667259 | ||
|
13f84d5dbc | ||
|
7bd2883732 | ||
|
5343b5dfaf | ||
|
82c9309a1d | ||
|
3714795e2b | ||
|
300a73213b | ||
|
4dd8dc9c45 | ||
|
87eb98f542 | ||
|
8e8cdd63af | ||
|
75c650b107 | ||
|
f115600e5b | ||
|
c32e951cf1 | ||
|
4bc2256f2f | ||
|
76fb09326e | ||
|
9212b0e427 | ||
|
fc596ee45f | ||
|
380b13d04b | ||
|
66127de355 | ||
|
bd670c85fd | ||
|
3c33dd63ac | ||
|
dc81543bc2 | ||
|
5c6f478fe6 | ||
|
198b97ad18 | ||
|
0aa3473a62 | ||
|
e0cef88beb | ||
|
71ab6e65e4 | ||
|
1673fe1248 | ||
|
c3cda56fe9 | ||
|
bac2977b3d | ||
|
b48b4f41f0 | ||
|
201c74d29b | ||
|
5702b5f21b | ||
|
b1a5e472e4 | ||
|
f8bf473666 | ||
|
8da8154edd | ||
|
c9dd968c92 | ||
|
ed80c7657e | ||
|
f729349a1f | ||
|
a30d12312a | ||
|
7fa0c5e522 | ||
|
6af7ba6796 | ||
|
bdac3c01e2 | ||
|
a71eff2d39 | ||
|
ad02f7789d | ||
|
0e20617435 | ||
|
2aadc39022 | ||
|
87e4c1d2f9 | ||
|
e99c38970a | ||
|
0c9860192b | ||
|
7bfbb1b909 | ||
|
72674fd6da | ||
|
338c90a2d1 | ||
|
844bf99641 | ||
|
dabfbfba83 | ||
|
1c14ec79ab | ||
|
18a2a17d97 | ||
|
d0b6031d5f | ||
|
2c7500f1bb | ||
|
cc18e53430 | ||
|
6add4f0916 | ||
|
b89e99b79e | ||
|
fd6c7c7f1e | ||
|
6091a8b82c | ||
|
c407f2e334 | ||
|
9a4836257f | ||
|
4315f18efe | ||
|
ed93a202d6 | ||
|
aa89f06342 | ||
|
1887a3270c | ||
|
9f95fa9c95 | ||
|
1ec81fd45c | ||
|
31e8667658 | ||
|
4caf38e990 | ||
|
a0378348b5 | ||
|
519f37cd14 | ||
|
1909eb291e | ||
|
8d6d508bba | ||
|
da029735cf | ||
|
f060884b04 | ||
|
57ee21f8ec | ||
|
af589ee227 | ||
|
d6b84eba22 | ||
|
fb857717fd | ||
|
b4bd941197 | ||
|
13eacf5fa8 | ||
|
e0630808e3 | ||
|
35fd75e4f6 | ||
|
914f6a344b | ||
|
10ab1ead2c | ||
|
b3ef2460b3 | ||
|
a9630ffa78 | ||
|
2a3dbc9842 | ||
|
4787aa7f62 | ||
|
77ac4ca3f5 | ||
|
db16ea8375 | ||
|
7c8456b18a | ||
|
24abeb74bd | ||
|
ec4732610b | ||
|
cb0aa1317c | ||
|
f0789392b5 | ||
|
93fb72788a | ||
|
def55bad15 | ||
|
e2a6200d75 | ||
|
a5d4140bda | ||
|
9aff29a634 | ||
|
f9892e5401 | ||
|
329a0c44fc | ||
|
779fa0a630 | ||
|
c24cf3cfb5 | ||
|
375627ab00 | ||
|
1995855693 | ||
|
1bd72654b8 | ||
|
ea057cf90f | ||
|
0cbf619c13 | ||
|
94b4a10bd4 | ||
|
b37ac2ddce | ||
|
717ba27992 | ||
|
374e73998a | ||
|
9c3f57dd3f | ||
|
6b67e4f714 | ||
|
2fba8c1ac9 | ||
|
8df5c2ce80 | ||
|
3e63b6432e | ||
|
0987b4b39a | ||
|
d0c5fb2dab | ||
|
ecf73dd7ab | ||
|
62c9c1c830 | ||
|
6e9d30445b | ||
|
69c6a75409 |
26
.bithoundrc
@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"mute": [
|
|
||||||
],
|
|
||||||
"unused-ignores": [
|
|
||||||
"almond",
|
|
||||||
"d3-*",
|
|
||||||
"leaflet",
|
|
||||||
"moment",
|
|
||||||
"navigo",
|
|
||||||
"node-polyglot",
|
|
||||||
"promise-polyfill",
|
|
||||||
"rbush",
|
|
||||||
"requirejs",
|
|
||||||
"snabbdom"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"critics": {
|
|
||||||
"wc": {
|
|
||||||
"limit": 5000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ignore": [
|
|
||||||
"polyfill.js"
|
|
||||||
]
|
|
||||||
}
|
|
14
.drone.yml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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
|
||||||
|
|
@ -2,15 +2,19 @@
|
|||||||
|
|
||||||
# top-most EditorConfig file
|
# top-most EditorConfig file
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
|
||||||
# Get rid of whitespace to avoid diffs with a bunch of EOL changes
|
# Get rid of whitespace to avoid diffs with a bunch of EOL changes
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*]
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.{js,html,scss,json,yml,md}]
|
[*.{js,html,scss,json,yml,md}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
|
|
||||||
|
[assets/favicon/manifest.json]
|
||||||
|
indent_size = 4
|
||||||
|
@ -9,5 +9,6 @@ rules:
|
|||||||
"func-names": 0
|
"func-names": 0
|
||||||
"guard-for-in": 0
|
"guard-for-in": 0
|
||||||
"no-undefined": 0
|
"no-undefined": 0
|
||||||
|
"consistent-return": 0
|
||||||
"no-nested-ternary": 0
|
"no-nested-ternary": 0
|
||||||
"no-extend-native": ["error", { "exceptions": ["String"] }]
|
"no-extend-native": ["error", { "exceptions": ["String"] }]
|
||||||
|
7
.github/CONTRIBUTING.md
vendored
@ -1,5 +1,6 @@
|
|||||||
## Contributing is welcome
|
## Contributing is welcome
|
||||||
|
|
||||||
Pull requests are welcome without an opening an issue. If you unsure about the feature or implementation open an issue and
|
Pull requests are welcome without the need of opening an issue. If you're unsure
|
||||||
discuss your suggested changes. Meshviewer is a frontend application and the code needs to be loaded and
|
about your feature or your implementation open an issue and discuss your
|
||||||
perform on slow mobile devices with many nodes and clients.
|
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.
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
<!--- Provide a general summary of the issue in the Title above -->
|
<!--- Provide a general summary of the issue in the Title above -->
|
||||||
<!--- This template should help to improve the report, unneeded parts can be remvoed -->
|
<!--- This template should help to improve the report, unneeded parts can be remvoed -->
|
||||||
|
|
||||||
@ -13,7 +19,7 @@
|
|||||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||||
<!--- or ideas how to implement the addition or change -->
|
<!--- or ideas how to implement the addition or change -->
|
||||||
|
|
||||||
## Steps to Reproduce (for bugs)
|
## Steps to Reproduce
|
||||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||||
1.
|
1.
|
||||||
@ -31,3 +37,6 @@
|
|||||||
* Browser Name and version:
|
* Browser Name and version:
|
||||||
* Operating System and version (desktop or mobile):
|
* Operating System and version (desktop or mobile):
|
||||||
* Link to your project:
|
* Link to your project:
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
<!--- If applicable, add screenshots to help explain your problem. -->
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
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. -->
|
17
.travis.yml
@ -1,31 +1,24 @@
|
|||||||
sudo: false
|
sudo: false
|
||||||
|
dist: trusty
|
||||||
|
|
||||||
language: node_js
|
language: node_js
|
||||||
|
|
||||||
node_js:
|
node_js:
|
||||||
- "7"
|
- "12"
|
||||||
|
|
||||||
os:
|
os:
|
||||||
- linux
|
- linux
|
||||||
- macosx
|
- osx
|
||||||
- windows
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- node_js: 7
|
- node_js: "8"
|
||||||
os: linux
|
os: linux
|
||||||
env: USE_NPM=true
|
- node_js: "10"
|
||||||
- node_js: 6
|
|
||||||
os: linux
|
os: linux
|
||||||
- node_js: 4
|
|
||||||
os: linux
|
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
yarn: true
|
yarn: true
|
||||||
|
|
||||||
before_install:
|
|
||||||
- if [ "$USE_NPM" == "true" ]; then rm yarn.lock; fi
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- if git status | grep -q "modified. \.travis\.yml"; then echo "Dirty yarn.lock"; exit 1; fi
|
- if git status | grep -q "modified. \.travis\.yml"; then echo "Dirty yarn.lock"; exit 1; fi
|
||||||
|
|
||||||
|
66
CHANGELOG.md
@ -1,66 +0,0 @@
|
|||||||
# 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
|
|
21
Dockerfile
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# 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/
|
52
README.md
@ -1,64 +1,20 @@
|
|||||||
# Meshviewer
|
# Meshviewer
|
||||||
[![Build Status](https://img.shields.io/travis/ffrgb/meshviewer/develop.svg?style=flat-square)](https://travis-ci.org/ffrgb/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)
|
||||||
[![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)
|
[![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)
|
||||||
[![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/github/license/ffrgb/meshviewer.svg?style=flat-square)](https://www.gnu.org/licenses/agpl-3.0)
|
||||||
[![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.
|
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
|
### Demo
|
||||||
|
|
||||||
Embedded: https://regensburg.freifunk.net/netz/karte/
|
Embedded: https://regensburg.freifunk.net/netz/karte/
|
||||||
Standalone: https://regensburg.freifunk.net/meshviewer/
|
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
|
## Sponsoring / Supporting
|
||||||
|
|
||||||
- [BrowserStack](https://www.browserstack.com/) for providing an awesome testing service for hundreds of browsers
|
- [BrowserStack](https://www.browserstack.com/) for providing an awesome testing service for hundreds of browsers
|
||||||
- [Travis CI](https://travis-ci.org/) for building meshviewer on every push and pull request
|
- [Travis CI](https://travis-ci.com/) 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
|
- [Scrutinizer CI](https://scrutinizer-ci.com/g/ffrgb/meshviewer/) for testing code quality on every push and pull request
|
||||||
- [Crowdin](https://crowdin.com/) for providing an easy non-developer translation environment
|
- [POEditor](https://poeditor.com/join/project/VZBjPNNic9) for providing an easy non-developer translation environment
|
||||||
|
|
||||||
These tools need a lot of infrastructures and provide a free account for open source software.
|
These tools need a lot of infrastructures and provide a free account for open source software.
|
||||||
|
26
app.js
@ -9,21 +9,21 @@ require.config({
|
|||||||
'moment': '../node_modules/moment/moment',
|
'moment': '../node_modules/moment/moment',
|
||||||
// d3 modules indirect dependencies
|
// d3 modules indirect dependencies
|
||||||
// by d3-zoom: d3-drag
|
// by d3-zoom: d3-drag
|
||||||
'd3-ease': '../node_modules/d3-ease/build/d3-ease',
|
'd3-ease': '../node_modules/d3-ease/dist/d3-ease',
|
||||||
'd3-transition': '../node_modules/d3-transition/build/d3-transition',
|
'd3-transition': '../node_modules/d3-transition/dist/d3-transition',
|
||||||
'd3-color': '../node_modules/d3-color/build/d3-color',
|
'd3-color': '../node_modules/d3-color/dist/d3-color',
|
||||||
'd3-interpolate': '../node_modules/d3-interpolate/build/d3-interpolate',
|
'd3-interpolate': '../node_modules/d3-interpolate/dist/d3-interpolate',
|
||||||
// by d3-force
|
// by d3-force
|
||||||
'd3-collection': '../node_modules/d3-collection/build/d3-collection',
|
'd3-collection': '../node_modules/d3-collection/dist/d3-collection',
|
||||||
'd3-dispatch': '../node_modules/d3-dispatch/build/d3-dispatch',
|
'd3-dispatch': '../node_modules/d3-dispatch/dist/d3-dispatch',
|
||||||
'd3-quadtree': '../node_modules/d3-quadtree/build/d3-quadtree',
|
'd3-quadtree': '../node_modules/d3-quadtree/dist/d3-quadtree',
|
||||||
'd3-timer': '../node_modules/d3-timer/build/d3-timer',
|
'd3-timer': '../node_modules/d3-timer/dist/d3-timer',
|
||||||
// by d3-drag: d3-selection
|
// by d3-drag: d3-selection
|
||||||
// d3 modules dependencies
|
// d3 modules dependencies
|
||||||
'd3-selection': '../node_modules/d3-selection/build/d3-selection',
|
'd3-selection': '../node_modules/d3-selection/dist/d3-selection',
|
||||||
'd3-force': '../node_modules/d3-force/build/d3-force',
|
'd3-force': '../node_modules/d3-force/dist/d3-force',
|
||||||
'd3-zoom': '../node_modules/d3-zoom/build/d3-zoom',
|
'd3-zoom': '../node_modules/d3-zoom/dist/d3-zoom',
|
||||||
'd3-drag': '../node_modules/d3-drag/build/d3-drag',
|
'd3-drag': '../node_modules/d3-drag/dist/d3-drag',
|
||||||
'snabbdom': '../node_modules/snabbdom/dist/snabbdom-patch',
|
'snabbdom': '../node_modules/snabbdom/dist/snabbdom-patch',
|
||||||
'rbush': '../node_modules/rbush/rbush',
|
'rbush': '../node_modules/rbush/rbush',
|
||||||
'helper': 'utils/helper'
|
'helper': 'utils/helper'
|
||||||
@ -37,5 +37,5 @@ require.config({
|
|||||||
});
|
});
|
||||||
|
|
||||||
require(['main'], function (main) {
|
require(['main'], function (main) {
|
||||||
main(jsonData);
|
main();
|
||||||
});
|
});
|
||||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 624 B After Width: | Height: | Size: 485 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 886 B |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Meshviewer",
|
"name": "Meshviewer",
|
||||||
|
"short_name": "Meshviewer",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "./android-chrome-192x192.png",
|
"src": "./android-chrome-192x192.png",
|
||||||
@ -14,5 +15,7 @@
|
|||||||
],
|
],
|
||||||
"theme_color": "#dc0067",
|
"theme_color": "#dc0067",
|
||||||
"background_color": "#dc0067",
|
"background_color": "#dc0067",
|
||||||
"display": "standalone"
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"orientation": "portrait"
|
||||||
}
|
}
|
@ -1 +1 @@
|
|||||||
{"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=\"Meshviewer\">\n<meta name=\"application-name\" content=\"Meshviewer\">\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"}
|
{"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"}
|
||||||
|
BIN
assets/icons/fonts/meshviewer.ttf
Normal file
BIN
assets/icons/fonts/meshviewer.woff
Normal file
BIN
assets/icons/fonts/meshviewer.woff2
Normal file
@ -7,9 +7,9 @@ $cache-breaker: unique-id();
|
|||||||
font-family: 'ionicons';
|
font-family: 'ionicons';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
src: url('fonts/icon.woff2?rel=#{$cache-breaker}') format('woff2'),
|
src: url('fonts/meshviewer.woff2?rel=#{$cache-breaker}') format('woff2'),
|
||||||
url('fonts/icon.woff?rel=#{$cache-breaker}') format('woff'),
|
url('fonts/meshviewer.woff?rel=#{$cache-breaker}') format('woff'),
|
||||||
url('fonts/icon.ttf?rel=#{$cache-breaker}') format('truetype');
|
url('fonts/meshviewer.ttf?rel=#{$cache-breaker}') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
[class^='ion-'],
|
[class^='ion-'],
|
||||||
@ -49,3 +49,5 @@ $cache-breaker: unique-id();
|
|||||||
@include icon('arrow-resize', '\f264');
|
@include icon('arrow-resize', '\f264');
|
||||||
@include icon('arrow-left-c', '\f108');
|
@include icon('arrow-left-c', '\f108');
|
||||||
@include icon('arrow-right-c', '\f10b');
|
@include icon('arrow-right-c', '\f10b');
|
||||||
|
@include icon('full-enter', '\e901');
|
||||||
|
@include icon('full-exit', '\e900');
|
||||||
|
194
config.default.js
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
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 -->'
|
||||||
|
};
|
||||||
|
};
|
@ -1,52 +0,0 @@
|
|||||||
// 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 -->"
|
|
||||||
}
|
|
89
config.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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">© Freifunk-Rhein-Sieg</a> <a href="http://www.openstreetmap.org/about/" target="_blank">© 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">© OpenMapTiles</a> <a href="http://www.openstreetmap.org/about/" target="_blank">© 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/'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
115
config.json
@ -1,115 +0,0 @@
|
|||||||
// 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?panelId=1&from=now-7d&var-nodeid={NODE_ID}&var-host={NODE_NAME}&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?panelId=2&from=now-7d&var-nodeid={NODE_ID}&var-host={NODE_NAME}&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/",
|
|
||||||
"siteName": "Freifunk Regensburg",
|
|
||||||
"mapLayers": [
|
|
||||||
{
|
|
||||||
"name": "Freifunk Regensburg",
|
|
||||||
// Please ask Freifunk Regensburg before using its tile server c- example with retina tiles
|
|
||||||
"url": "https://{s}.tiles.ffrgb.net/{z}/{x}/{y}{retina}.png",
|
|
||||||
"config": {
|
|
||||||
"maxZoom": 22,
|
|
||||||
"subdomains": "1234",
|
|
||||||
"attribution": "<a href=\"https://www.mapbox.com/about/maps/\" target=\"_blank\">© Mapbox</a> <a href=\"https://openstreetmap.org/about/\" target=\"_blank\">© 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": "1234",
|
|
||||||
"attribution": "<a href=\"https://www.mapbox.com/about/maps/\" target=\"_blank\">© Mapbox</a> <a href=\"https://openstreetmap.org/about/\" target=\"_blank\">© 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": "OpenStreetMap.HOT",
|
|
||||||
"url": "https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png",
|
|
||||||
"config": {
|
|
||||||
"maxZoom": 19,
|
|
||||||
"attribution": "© Openstreetmap France | © <a href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a>"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "HERE",
|
|
||||||
// Please use your own API key - Free plan is on right side after the pay plans
|
|
||||||
"url": "https://{s}.base.maps.api.here.com/maptile/2.1/maptile/newest/normal.day/{z}/{x}/{y}/256/png8?app_id=YOUR_KEY&app_code=YOUR_CODE&lg=deu",
|
|
||||||
"config": {
|
|
||||||
"attribution": "Map © 1987-2014 <a href=\"http://developer.here.com\">HERE</a>",
|
|
||||||
"subdomains": "1234",
|
|
||||||
"maxZoom": 20
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Esri.WorldImagery",
|
|
||||||
"url": "//server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
|
||||||
"config": {
|
|
||||||
"maxZoom": 20,
|
|
||||||
"attribution": "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "HERE.hybridDay",
|
|
||||||
// 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=YOUR_KEY&app_code=YOUR_CODE&lg=deu",
|
|
||||||
"config": {
|
|
||||||
"attribution": "Map © 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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
files:
|
|
||||||
- source: /locale/en.json
|
|
||||||
translation: /locale/%two_letters_code%.json
|
|
@ -8,11 +8,11 @@ module.exports = function () {
|
|||||||
sass: 'scss/**/*.scss',
|
sass: 'scss/**/*.scss',
|
||||||
javascript: ['./app.js', 'lib/**/*.js'],
|
javascript: ['./app.js', 'lib/**/*.js'],
|
||||||
json: 'locale/*.json',
|
json: 'locale/*.json',
|
||||||
html: ['html/*.html', './config*.json']
|
html: ['html/*.html', './config*.js']
|
||||||
},
|
},
|
||||||
clean: [build + '/*.map', build + '/vendor', build + '/main.css'],
|
clean: [build + '/*.map', build + '/vendor', build + '/main.css'],
|
||||||
autoprefixer: ['> 1% in DE'],
|
|
||||||
browsersync: {
|
browsersync: {
|
||||||
|
open: false,
|
||||||
server: {
|
server: {
|
||||||
baseDir: build
|
baseDir: build
|
||||||
},
|
},
|
||||||
|
@ -2,10 +2,12 @@ module.exports = function (gulp, plugins, config) {
|
|||||||
return function copy() {
|
return function copy() {
|
||||||
gulp.src(['html/*.html', 'assets/favicon/*'])
|
gulp.src(['html/*.html', 'assets/favicon/*'])
|
||||||
.pipe(gulp.dest(config.build));
|
.pipe(gulp.dest(config.build));
|
||||||
gulp.src(['assets/logo.svg'])
|
gulp.src(['assets/logo.svg', 'service-worker.js'])
|
||||||
.pipe(gulp.dest(config.build));
|
.pipe(gulp.dest(config.build));
|
||||||
gulp.src(['node_modules/promise-polyfill/promise.js', 'polyfill.js'])
|
gulp.src(['polyfill.js'])
|
||||||
.pipe(gulp.dest(config.build + '/vendor'));
|
.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/*'])
|
return gulp.src(['assets/fonts/*', 'assets/icons/fonts/*'])
|
||||||
.pipe(gulp.dest(config.build + '/fonts'));
|
.pipe(gulp.dest(config.build + '/fonts'));
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ module.exports = function (gulp, plugins, config) {
|
|||||||
design: {
|
design: {
|
||||||
ios: {
|
ios: {
|
||||||
pictureAspect: 'backgroundAndMargin',
|
pictureAspect: 'backgroundAndMargin',
|
||||||
backgroundColor: '#000000',
|
backgroundColor: '#ffffff',
|
||||||
margin: '14%',
|
margin: '14%',
|
||||||
assets: {
|
assets: {
|
||||||
ios6AndPriorIcons: false,
|
ios6AndPriorIcons: false,
|
||||||
@ -19,7 +19,7 @@ module.exports = function (gulp, plugins, config) {
|
|||||||
},
|
},
|
||||||
desktopBrowser: {},
|
desktopBrowser: {},
|
||||||
windows: {
|
windows: {
|
||||||
pictureAspect: 'noChange',
|
pictureAspect: 'whiteSilhouette',
|
||||||
backgroundColor: '#dc0067',
|
backgroundColor: '#dc0067',
|
||||||
onConflict: 'override',
|
onConflict: 'override',
|
||||||
assets: {
|
assets: {
|
||||||
@ -39,7 +39,7 @@ module.exports = function (gulp, plugins, config) {
|
|||||||
manifest: {
|
manifest: {
|
||||||
name: 'Meshviewer',
|
name: 'Meshviewer',
|
||||||
display: 'standalone',
|
display: 'standalone',
|
||||||
orientation: 'notSet',
|
orientation: 'portrait',
|
||||||
onConflict: 'override',
|
onConflict: 'override',
|
||||||
declared: true
|
declared: true
|
||||||
},
|
},
|
||||||
|
@ -1,25 +1,52 @@
|
|||||||
const fs = require('fs');
|
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) {
|
module.exports = function (gulp, plugins, config, env) {
|
||||||
return function html() {
|
return function html() {
|
||||||
return gulp.src(env.production() ? config.build + '/*.html' : 'html/*.html')
|
return gulp.src(env.production() ? config.build + '/*.html' : 'html/*.html')
|
||||||
.pipe(plugins.inject(gulp.src(['config.json']), {
|
.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,
|
||||||
starttag: '<!-- inject:config -->',
|
starttag: '<!-- inject:config -->',
|
||||||
transform: function (filePath, customConfig) {
|
transform: function () {
|
||||||
var defaultConfig = fs.readFileSync('config.default.json', 'utf8');
|
delete require.cache[require.resolve('../../config.default')];
|
||||||
var buildConfig = Object.assign(
|
delete require.cache[require.resolve('../../config')];
|
||||||
JSON.parse(JSON.minify(defaultConfig)),
|
var buildConfig = Object.assign({}, require('../../config.default')(), require('../../config')());
|
||||||
JSON.parse(JSON.minify(customConfig.contents.toString('utf8')))
|
return '<title>' + buildConfig.siteName + ' - loading...</title>' +
|
||||||
);
|
'<script>window.config =' +
|
||||||
return '<script>var jsonData =' +
|
stringify(buildConfig)
|
||||||
JSON.stringify(buildConfig)
|
|
||||||
.replace('<!-- inject:cache-breaker -->',
|
.replace('<!-- inject:cache-breaker -->',
|
||||||
Math.random().toString(12).substring(7)) +
|
Math.random().toString(12).substring(7)) +
|
||||||
';</script>'
|
';</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;
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.pipe(env.production(plugins.kyhInlineSource({ compress: false })))
|
|
||||||
.pipe(plugins.realFavicon.injectFaviconMarkups(JSON.parse(fs.readFileSync(config.faviconData)).favicon.html_code))
|
|
||||||
.pipe(plugins.cacheBust({
|
.pipe(plugins.cacheBust({
|
||||||
type: 'timestamp'
|
type: 'timestamp'
|
||||||
}))
|
}))
|
||||||
|
@ -3,7 +3,10 @@ module.exports = function (gulp, plugins, config, env) {
|
|||||||
return gulp.src('app.js')
|
return gulp.src('app.js')
|
||||||
.pipe(env.development(plugins.sourcemaps.init()))
|
.pipe(env.development(plugins.sourcemaps.init()))
|
||||||
.pipe(plugins.requirejsOptimize(env.production() ? config.requireJs.prod : config.requireJs.dev))
|
.pipe(plugins.requirejsOptimize(env.production() ? config.requireJs.prod : config.requireJs.dev))
|
||||||
.pipe(env.production(plugins.uglify({ preserveComments: 'license' })))
|
.on('error', function () {
|
||||||
|
this.emit('end');
|
||||||
|
})
|
||||||
|
.pipe(env.production(plugins.uglify({ output: { comments: 'all' } })))
|
||||||
.pipe(env.development(plugins.sourcemaps.write('.', { addComment: true })))
|
.pipe(env.development(plugins.sourcemaps.write('.', { addComment: true })))
|
||||||
.pipe(gulp.dest(config.build));
|
.pipe(gulp.dest(config.build));
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,9 @@ module.exports = function (gulp, plugins, config, env) {
|
|||||||
outputStyle: 'compressed',
|
outputStyle: 'compressed',
|
||||||
sourceMap: false
|
sourceMap: false
|
||||||
}))
|
}))
|
||||||
|
.on('error', function () {
|
||||||
|
this.emit('end');
|
||||||
|
})
|
||||||
.pipe(plugins.autoprefixer({
|
.pipe(plugins.autoprefixer({
|
||||||
browsers: config.autoprefixer
|
browsers: config.autoprefixer
|
||||||
}))
|
}))
|
||||||
|
@ -1,22 +1,43 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html itemscope itemtype="http://schema.org/WebPage">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
<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" href="main.css" inline>
|
||||||
<link rel="stylesheet" class="css-mode night" media="not" href="night.css" inline>
|
<link rel="stylesheet" class="css-mode night" media="not" href="night.css" inline>
|
||||||
<!-- inject:config -->
|
<!-- inject:config -->
|
||||||
<!-- contents of html partials will be injected here -->
|
<!-- contents of html partials will be injected here -->
|
||||||
<!-- endinject -->
|
<!-- endinject -->
|
||||||
<script src="vendor/polyfill.js" inline></script>
|
<script src="vendor/polyfill.js" inline></script>
|
||||||
<script src="vendor/promise.js" inline></script>
|
<script src="vendor/promise/polyfill.js" inline></script>
|
||||||
<script src="app.js"></script>
|
<script src="app.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="loader">
|
<div class="loader">
|
||||||
<p>
|
<p>
|
||||||
Lade<br />
|
Lade<br />
|
||||||
<img inline src="logo.svg" class="spinner" />
|
<img inline src="logo.svg" class="spinner" alt="Loading ..."/>
|
||||||
<br />
|
<br />
|
||||||
Karten & Knoten...
|
Karten & Knoten...
|
||||||
</p>
|
</p>
|
||||||
|
23
html/offline.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!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>
|
13
lib/about.js
@ -4,7 +4,18 @@ define(function () {
|
|||||||
return function () {
|
return function () {
|
||||||
this.render = function render(d) {
|
this.render = function render(d) {
|
||||||
d.innerHTML = _.t('sidebar.aboutInfo') +
|
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>' +
|
'<h3>AGPL 3</h3>' +
|
||||||
|
|
||||||
'<p>Copyright (C) Milan Pässler</p>' +
|
'<p>Copyright (C) Milan Pässler</p>' +
|
||||||
|
@ -97,8 +97,7 @@ define(['filters/nodefilter'], function (NodeFilter) {
|
|||||||
setData: setData,
|
setData: setData,
|
||||||
addFilter: addFilter,
|
addFilter: addFilter,
|
||||||
removeFilter: removeFilter,
|
removeFilter: removeFilter,
|
||||||
watchFilters: watchFilters,
|
watchFilters: watchFilters
|
||||||
refresh: refresh
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -22,6 +22,7 @@ define(function () {
|
|||||||
|
|
||||||
var button = document.createElement('button');
|
var button = document.createElement('button');
|
||||||
button.classList.add('ion-close');
|
button.classList.add('ion-close');
|
||||||
|
button.setAttribute('aria-label', _.t('remove'));
|
||||||
button.onclick = function onclick() {
|
button.onclick = function onclick() {
|
||||||
distributor.removeFilter(d);
|
distributor.removeFilter(d);
|
||||||
};
|
};
|
||||||
|
@ -16,7 +16,7 @@ define(function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function run(d) {
|
function run(d) {
|
||||||
return (d.nodeinfo !== undefined ? d.nodeinfo.hostname.toLowerCase().includes(input.value.toLowerCase()) : '');
|
return d.hostname.toLowerCase().includes(input.value.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRefresh(f) {
|
function setRefresh(f) {
|
||||||
@ -26,6 +26,7 @@ define(function () {
|
|||||||
function render(el) {
|
function render(el) {
|
||||||
input.type = 'search';
|
input.type = 'search';
|
||||||
input.placeholder = _.t('sidebar.nodeFilter');
|
input.placeholder = _.t('sidebar.nodeFilter');
|
||||||
|
input.setAttribute('aria-label', _.t('sidebar.nodeFilter'));
|
||||||
input.addEventListener('input', refresh);
|
input.addEventListener('input', refresh);
|
||||||
el.classList.add('filter-node');
|
el.classList.add('filter-node');
|
||||||
el.classList.add('ion-filter');
|
el.classList.add('ion-filter');
|
||||||
|
@ -12,26 +12,8 @@ define(function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var filteredIds = new Set();
|
n.links = data.links.filter(function (d) {
|
||||||
|
return filter(d.source) && filter(d.target);
|
||||||
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;
|
return n;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegraph/draw'],
|
define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'd3-timer', 'd3-ease', 'd3-interpolate', 'utils/math', 'forcegraph/draw'],
|
||||||
function (d3Selection, d3Force, d3Zoom, d3Drag, math, draw) {
|
function (d3Selection, d3Force, d3Zoom, d3Drag, d3Timer, d3Ease, d3Interpolate, math, draw) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
return function (config, linkScale, sidebar, router) {
|
return function (linkScale, sidebar) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var el;
|
var el;
|
||||||
var canvas;
|
var canvas;
|
||||||
@ -14,15 +14,18 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegr
|
|||||||
var intNodes = [];
|
var intNodes = [];
|
||||||
var dictNodes = {};
|
var dictNodes = {};
|
||||||
var intLinks = [];
|
var intLinks = [];
|
||||||
|
var movetoTimer;
|
||||||
|
var initial = 1.8;
|
||||||
|
|
||||||
var NODE_RADIUS_DRAG = 10;
|
var NODE_RADIUS_DRAG = 10;
|
||||||
var NODE_RADIUS_SELECT = 15;
|
var NODE_RADIUS_SELECT = 15;
|
||||||
var LINK_RADIUS_SELECT = 12;
|
var LINK_RADIUS_SELECT = 12;
|
||||||
|
var ZOOM_ANIMATE_DURATION = 350;
|
||||||
|
|
||||||
var ZOOM_MIN = 1 / 8;
|
var ZOOM_MIN = 1 / 8;
|
||||||
var ZOOM_MAX = 3;
|
var ZOOM_MAX = 3;
|
||||||
|
|
||||||
var FORCE_ALPHA = 0.3;
|
var FORCE_ALPHA = 0.01;
|
||||||
|
|
||||||
draw.setTransform(transform);
|
draw.setTransform(transform);
|
||||||
|
|
||||||
@ -32,9 +35,43 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegr
|
|||||||
draw.setMaxArea(canvas.width, canvas.height);
|
draw.setMaxArea(canvas.width, canvas.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveTo(x, y) {
|
function transformPosition(p) {
|
||||||
transform.x = (canvas.width + sidebar()) / 2 - x * transform.k;
|
transform.x = p.x;
|
||||||
transform.y = canvas.height / 2 - y * transform.k;
|
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 onClick() {
|
function onClick() {
|
||||||
@ -46,7 +83,7 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegr
|
|||||||
var n = force.find(e[0], e[1], NODE_RADIUS_SELECT);
|
var n = force.find(e[0], e[1], NODE_RADIUS_SELECT);
|
||||||
|
|
||||||
if (n !== undefined) {
|
if (n !== undefined) {
|
||||||
router.fullUrl({ node: n.o.node.nodeinfo.node_id });
|
router.fullUrl({ node: n.o.node_id });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,16 +122,16 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegr
|
|||||||
|
|
||||||
forceLink = d3Force.forceLink()
|
forceLink = d3Force.forceLink()
|
||||||
.distance(function (d) {
|
.distance(function (d) {
|
||||||
if (d.o.vpn) {
|
if (d.o.type.indexOf('vpn') === 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 75;
|
return 75;
|
||||||
})
|
})
|
||||||
.strength(function (d) {
|
.strength(function (d) {
|
||||||
if (d.o.vpn) {
|
if (d.o.type.indexOf('vpn') === 0) {
|
||||||
return 0.02;
|
return 0.02;
|
||||||
}
|
}
|
||||||
return Math.max(0.5, 1 / d.o.tq);
|
return Math.max(0.5, d.o.source_tq);
|
||||||
});
|
});
|
||||||
|
|
||||||
var zoom = d3Zoom.zoom()
|
var zoom = d3Zoom.zoom()
|
||||||
@ -111,7 +148,8 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegr
|
|||||||
.force('x', d3Force.forceX().strength(0.02))
|
.force('x', d3Force.forceX().strength(0.02))
|
||||||
.force('y', d3Force.forceY().strength(0.02))
|
.force('y', d3Force.forceY().strength(0.02))
|
||||||
.force('collide', d3Force.forceCollide())
|
.force('collide', d3Force.forceCollide())
|
||||||
.on('tick', redraw);
|
.on('tick', redraw)
|
||||||
|
.alphaDecay(0.025);
|
||||||
|
|
||||||
var drag = d3Drag.drag()
|
var drag = d3Drag.drag()
|
||||||
.subject(function () {
|
.subject(function () {
|
||||||
@ -160,13 +198,11 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegr
|
|||||||
});
|
});
|
||||||
|
|
||||||
self.setData = function setData(data) {
|
self.setData = function setData(data) {
|
||||||
intNodes = data.graph.nodes.map(function (d) {
|
intNodes = data.nodes.all.map(function (d) {
|
||||||
var e;
|
var e = dictNodes[d.node_id];
|
||||||
if (d.id in dictNodes) {
|
if (!e) {
|
||||||
e = dictNodes[d.id];
|
|
||||||
} else {
|
|
||||||
e = {};
|
e = {};
|
||||||
dictNodes[d.id] = e;
|
dictNodes[d.node_id] = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.o = d;
|
e.o = d;
|
||||||
@ -174,65 +210,67 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegr
|
|||||||
return e;
|
return e;
|
||||||
});
|
});
|
||||||
|
|
||||||
intLinks = data.graph.links.map(function (d) {
|
intLinks = data.links.filter(function (d) {
|
||||||
var e = {};
|
return data.nodeDict[d.source.node_id].is_online && data.nodeDict[d.target.node_id].is_online;
|
||||||
e.o = d;
|
}).map(function (d) {
|
||||||
e.source = dictNodes[d.source.id];
|
return {
|
||||||
e.target = dictNodes[d.target.id];
|
o: d,
|
||||||
e.color = linkScale(1 / d.tq);
|
source: dictNodes[d.source.node_id],
|
||||||
|
target: dictNodes[d.target.node_id],
|
||||||
return e;
|
color: linkScale(d.source_tq),
|
||||||
|
color_to: linkScale(d.target_tq)
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
force.nodes(intNodes);
|
force.nodes(intNodes);
|
||||||
forceLink.links(intLinks);
|
forceLink.links(intLinks);
|
||||||
|
|
||||||
force.alpha(1).restart();
|
force.alpha(initial).velocityDecay(0.15).restart();
|
||||||
|
if (initial === 1.8) {
|
||||||
|
initial = 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
resizeCanvas();
|
resizeCanvas();
|
||||||
};
|
};
|
||||||
|
|
||||||
self.resetView = function resetView() {
|
self.resetView = function resetView() {
|
||||||
|
moveTo(function calcToReset() {
|
||||||
draw.setHighlight(null);
|
draw.setHighlight(null);
|
||||||
transform.k = (ZOOM_MIN + 1) / 2;
|
return [0, 0, (ZOOM_MIN + config.forceGraph.zoomModifier) / 2];
|
||||||
moveTo(0, 0);
|
}, true);
|
||||||
redraw();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.gotoNode = function gotoNode(d) {
|
self.gotoNode = function gotoNode(d) {
|
||||||
for (var i = 0; i < intNodes.length; i++) {
|
moveTo(function calcToNode() {
|
||||||
var n = intNodes[i];
|
draw.setHighlight({ type: 'node', id: d.node_id });
|
||||||
if (n.o.node.nodeinfo.node_id !== d.nodeinfo.node_id) {
|
var n = dictNodes[d.node_id];
|
||||||
continue;
|
if (n) {
|
||||||
|
return [n.x, n.y, (ZOOM_MAX + 1) / 2];
|
||||||
}
|
}
|
||||||
draw.setHighlight({ type: 'node', o: n.o.node });
|
return self.resetView();
|
||||||
transform.k = (ZOOM_MAX + 1) / 2;
|
});
|
||||||
moveTo(n.x, n.y);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
redraw();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.gotoLink = function gotoLink(d) {
|
self.gotoLink = function gotoLink(d) {
|
||||||
draw.setHighlight({ type: 'link', o: d });
|
moveTo(function calcToLink() {
|
||||||
for (var i = 0; i < intLinks.length; i++) {
|
draw.setHighlight({ type: 'link', id: d[0].id });
|
||||||
var l = intLinks[i];
|
var l = intLinks.find(function (link) {
|
||||||
if (l.o !== d) {
|
return link.o.id === d[0].id;
|
||||||
continue;
|
});
|
||||||
|
if (l) {
|
||||||
|
return [(l.source.x + l.target.x) / 2, (l.source.y + l.target.y) / 2, (ZOOM_MAX / 2) + ZOOM_MIN];
|
||||||
}
|
}
|
||||||
moveTo((l.source.x + l.target.x) / 2, (l.source.y + l.target.y) / 2);
|
return self.resetView();
|
||||||
break;
|
});
|
||||||
}
|
|
||||||
redraw();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
self.gotoLocation = function gotoLocation() {
|
self.gotoLocation = function gotoLocation() {
|
||||||
// ignore
|
// ignore
|
||||||
};
|
};
|
||||||
|
|
||||||
self.destroy = function destroy() {
|
self.destroy = function destroy() {
|
||||||
force.stop();
|
force.stop();
|
||||||
canvas.remove();
|
canvas.parentNode.removeChild(canvas);
|
||||||
force = null;
|
force = null;
|
||||||
|
|
||||||
if (el.parentNode) {
|
if (el.parentNode) {
|
||||||
|
@ -4,50 +4,39 @@ define(['helper'], function (helper) {
|
|||||||
var ctx;
|
var ctx;
|
||||||
var width;
|
var width;
|
||||||
var height;
|
var height;
|
||||||
|
|
||||||
var transform;
|
var transform;
|
||||||
|
|
||||||
var highlight;
|
var highlight;
|
||||||
|
|
||||||
var nodeColor = '#fff';
|
|
||||||
var clientColor = '#e6324b';
|
|
||||||
var highlightColor = 'rgba(255, 255, 255, 0.2)';
|
|
||||||
|
|
||||||
var labelColor = '#fff';
|
|
||||||
|
|
||||||
var NODE_RADIUS = 15;
|
var NODE_RADIUS = 15;
|
||||||
var LINE_RADIUS = 12;
|
var LINE_RADIUS = 12;
|
||||||
|
|
||||||
function drawDetailNode(d) {
|
function drawDetailNode(d) {
|
||||||
if (transform.k > 1) {
|
if (transform.k > 1 && d.o.is_online) {
|
||||||
ctx.beginPath();
|
helper.positionClients(ctx, d, Math.PI, d.o, 15);
|
||||||
helper.positionClients(ctx, d, Math.PI, d.o.node.statistics.clients, 15);
|
|
||||||
ctx.fillStyle = clientColor;
|
|
||||||
ctx.fill();
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
var name = d.o.node_id;
|
var name = d.o.node_id;
|
||||||
if (d.o.node && d.o.node.nodeinfo) {
|
if (d.o) {
|
||||||
name = d.o.node.nodeinfo.hostname;
|
name = d.o.hostname;
|
||||||
}
|
}
|
||||||
ctx.textAlign = 'center';
|
ctx.textAlign = 'center';
|
||||||
ctx.fillStyle = labelColor;
|
ctx.fillStyle = config.forceGraph.labelColor;
|
||||||
ctx.fillText(name, d.x, d.y + 20);
|
ctx.fillText(name, d.x, d.y + 20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawHighlightNode(d) {
|
function drawHighlightNode(d) {
|
||||||
if (highlight && highlight.type === 'node' && d.o.node === highlight.o) {
|
if (highlight && highlight.type === 'node' && d.o.node_id === highlight.id) {
|
||||||
ctx.arc(d.x, d.y, NODE_RADIUS * 1.5, 0, 2 * Math.PI);
|
ctx.arc(d.x, d.y, NODE_RADIUS * 1.5, 0, 2 * Math.PI);
|
||||||
ctx.fillStyle = highlightColor;
|
ctx.fillStyle = config.forceGraph.highlightColor;
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawHighlightLink(d, to) {
|
function drawHighlightLink(d, to) {
|
||||||
if (highlight && highlight.type === 'link' && d.o === highlight.o) {
|
if (highlight && highlight.type === 'link' && d.o.id === highlight.id) {
|
||||||
ctx.lineTo(to[0], to[1]);
|
ctx.lineTo(to[0], to[1]);
|
||||||
ctx.strokeStyle = highlightColor;
|
ctx.strokeStyle = config.forceGraph.highlightColor;
|
||||||
ctx.lineWidth = LINE_RADIUS * 2;
|
ctx.lineWidth = LINE_RADIUS * 2;
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = 'round';
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
@ -64,9 +53,17 @@ define(['helper'], function (helper) {
|
|||||||
|
|
||||||
drawHighlightNode(d);
|
drawHighlightNode(d);
|
||||||
|
|
||||||
ctx.moveTo(d.x + 3, d.y);
|
if (d.o.is_online) {
|
||||||
ctx.arc(d.x, d.y, 8, 0, 2 * Math.PI);
|
ctx.arc(d.x, d.y, 8, 0, 2 * Math.PI);
|
||||||
ctx.fillStyle = nodeColor;
|
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.fill();
|
ctx.fill();
|
||||||
|
|
||||||
drawDetailNode(d);
|
drawDetailNode(d);
|
||||||
@ -85,9 +82,13 @@ define(['helper'], function (helper) {
|
|||||||
|
|
||||||
to = drawHighlightLink(d, to);
|
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.lineTo(to[0], to[1]);
|
||||||
ctx.strokeStyle = d.color;
|
ctx.strokeStyle = grd;
|
||||||
if (d.o.vpn) {
|
if (d.o.type.indexOf('vpn') === 0) {
|
||||||
ctx.globalAlpha = 0.2;
|
ctx.globalAlpha = 0.2;
|
||||||
ctx.lineWidth = 1.5;
|
ctx.lineWidth = 1.5;
|
||||||
} else {
|
} else {
|
||||||
|
46
lib/gui.js
@ -1,18 +1,18 @@
|
|||||||
define(['d3-interpolate', 'map', 'sidebar', 'tabs', 'container', 'legend',
|
define(['d3-interpolate', 'map', 'sidebar', 'tabs', 'container', 'legend',
|
||||||
'linklist', 'nodelist', 'simplenodelist', 'infobox/main',
|
'linklist', 'nodelist', 'simplenodelist', 'infobox/main',
|
||||||
'proportions', 'forcegraph', 'title', 'about', 'datadistributor',
|
'proportions', 'forcegraph', 'title', 'about', 'datadistributor',
|
||||||
'filters/filtergui', 'filters/hostname'],
|
'filters/filtergui', 'filters/hostname', 'helper'],
|
||||||
function (d3Interpolate, Map, Sidebar, Tabs, Container, Legend, Linklist,
|
function (d3Interpolate, Map, Sidebar, Tabs, Container, Legend, Linklist,
|
||||||
Nodelist, SimpleNodelist, Infobox, Proportions, ForceGraph,
|
Nodelist, SimpleNodelist, Infobox, Proportions, ForceGraph,
|
||||||
Title, About, DataDistributor, FilterGUI, HostnameFilter) {
|
Title, About, DataDistributor, FilterGUI, HostnameFilter, helper) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
return function (config, router, language) {
|
return function (language) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var content;
|
var content;
|
||||||
var contentDiv;
|
var contentDiv;
|
||||||
|
|
||||||
var linkScale = d3Interpolate.interpolate('#F02311', '#04C714');
|
var linkScale = d3Interpolate.interpolate(config.map.tqFrom, config.map.tqTo);
|
||||||
var sidebar;
|
var sidebar;
|
||||||
|
|
||||||
var buttons = document.createElement('div');
|
var buttons = document.createElement('div');
|
||||||
@ -38,7 +38,7 @@ define(['d3-interpolate', 'map', 'sidebar', 'tabs', 'container', 'legend',
|
|||||||
function addContent(K) {
|
function addContent(K) {
|
||||||
removeContent();
|
removeContent();
|
||||||
|
|
||||||
content = new K(config, linkScale, sidebar.getWidth, router, buttons);
|
content = new K(linkScale, sidebar, buttons);
|
||||||
content.render(contentDiv);
|
content.render(contentDiv);
|
||||||
|
|
||||||
fanout.add(content);
|
fanout.add(content);
|
||||||
@ -63,8 +63,8 @@ define(['d3-interpolate', 'map', 'sidebar', 'tabs', 'container', 'legend',
|
|||||||
contentDiv.appendChild(buttons);
|
contentDiv.appendChild(buttons);
|
||||||
|
|
||||||
var buttonToggle = document.createElement('button');
|
var buttonToggle = document.createElement('button');
|
||||||
buttonToggle.classList.add('ion-eye', 'shadow');
|
buttonToggle.classList.add('ion-eye');
|
||||||
buttonToggle.setAttribute('data-tooltip', _.t('button.switchView'));
|
buttonToggle.setAttribute('aria-label', _.t('button.switchView'));
|
||||||
buttonToggle.onclick = function onclick() {
|
buttonToggle.onclick = function onclick() {
|
||||||
var data;
|
var data;
|
||||||
if (content.constructor === Map) {
|
if (content.constructor === Map) {
|
||||||
@ -77,23 +77,35 @@ define(['d3-interpolate', 'map', 'sidebar', 'tabs', 'container', 'legend',
|
|||||||
|
|
||||||
buttons.appendChild(buttonToggle);
|
buttons.appendChild(buttonToggle);
|
||||||
|
|
||||||
var title = new Title(config);
|
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 header = new Container('header');
|
||||||
var infobox = new Infobox(config, sidebar, router);
|
var infobox = new Infobox(sidebar, linkScale);
|
||||||
var tabs = new Tabs();
|
var tabs = new Tabs();
|
||||||
var overview = new Container();
|
var overview = new Container();
|
||||||
var legend = new Legend(config, language);
|
var legend = new Legend(language);
|
||||||
var newnodeslist = new SimpleNodelist('new', 'firstseen', router, _.t('node.new'));
|
var newnodeslist = new SimpleNodelist('new', 'firstseen', _.t('node.new'));
|
||||||
var lostnodeslist = new SimpleNodelist('lost', 'lastseen', router, _.t('node.missing'));
|
var lostnodeslist = new SimpleNodelist('lost', 'lastseen', _.t('node.missing'));
|
||||||
var nodelist = new Nodelist(router);
|
var nodelist = new Nodelist();
|
||||||
var linklist = new Linklist(linkScale, router);
|
var linklist = new Linklist(linkScale);
|
||||||
var statistics = new Proportions(config, fanout);
|
var statistics = new Proportions(fanout);
|
||||||
var about = new About();
|
var about = new About();
|
||||||
|
|
||||||
fanoutUnfiltered.add(legend);
|
fanoutUnfiltered.add(legend);
|
||||||
fanoutUnfiltered.add(newnodeslist);
|
fanoutUnfiltered.add(newnodeslist);
|
||||||
fanoutUnfiltered.add(lostnodeslist);
|
fanoutUnfiltered.add(lostnodeslist);
|
||||||
|
fanoutUnfiltered.add(infobox);
|
||||||
fanout.add(nodelist);
|
fanout.add(nodelist);
|
||||||
fanout.add(linklist);
|
fanout.add(linklist);
|
||||||
fanout.add(statistics);
|
fanout.add(statistics);
|
||||||
@ -128,4 +140,4 @@ define(['d3-interpolate', 'map', 'sidebar', 'tabs', 'container', 'legend',
|
|||||||
|
|
||||||
return self;
|
return self;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,53 +1,89 @@
|
|||||||
define(['helper'], function (helper) {
|
define(['helper', 'snabbdom'], function (helper, V) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
V = V.default;
|
||||||
|
|
||||||
function showStatImg(o, d, time) {
|
function showStatImg(img, o, d, time) {
|
||||||
var subst = {};
|
var subst = {
|
||||||
subst['{SOURCE_ID}'] = d.source.node_id;
|
'{SOURCE_ID}': d.source.node_id,
|
||||||
subst['{SOURCE_NAME}'] = d.source.node.nodeinfo.hostname.replace(/[^a-z0-9\-]/ig, '_');
|
'{SOURCE_NAME}': d.source.hostname.replace(/[^a-z0-9\-]/ig, '_'),
|
||||||
subst['{TARGET_ID}'] = d.target.node_id;
|
'{SOURCE_ADDR}': d.source_addr,
|
||||||
subst['{TARGET_NAME}'] = d.target.node.nodeinfo.hostname.replace(/[^a-z0-9\-]/ig, '_');
|
'{SOURCE_MAC}': d.source_mac ? d.source_mac : d.source_addr,
|
||||||
subst['{TIME}'] = time;
|
'{TARGET_ID}': d.target.node_id,
|
||||||
subst['{LOCALE}'] = _.locale();
|
'{TARGET_NAME}': d.target.hostname.replace(/[^a-z0-9\-]/ig, '_'),
|
||||||
return helper.showStat(o, subst);
|
'{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));
|
||||||
}
|
}
|
||||||
|
|
||||||
return function (config, el, router, d) {
|
return function (el, d, linkScale) {
|
||||||
var h2 = document.createElement('h2');
|
var self = this;
|
||||||
var a1 = document.createElement('a');
|
var header = document.createElement('div');
|
||||||
a1.href = router.generateLink({ node: d.source.node_id });
|
var table = document.createElement('table');
|
||||||
a1.textContent = d.source.node.nodeinfo.hostname;
|
var images = document.createElement('div');
|
||||||
h2.appendChild(a1);
|
el.appendChild(header);
|
||||||
|
el.appendChild(table);
|
||||||
|
el.appendChild(images);
|
||||||
|
|
||||||
var arrow = document.createElement('span');
|
self.render = function render() {
|
||||||
arrow.classList.add('ion-arrow-right-c');
|
var children = [];
|
||||||
h2.appendChild(arrow);
|
var img = [];
|
||||||
|
var time = d[0].target.lastseen.format('DDMMYYYYHmmss');
|
||||||
|
|
||||||
var a2 = document.createElement('a');
|
header = V.patch(header, V.h('div', V.h('h2', [
|
||||||
a2.href = router.generateLink({ node: d.target.node_id });
|
V.h('a', {
|
||||||
a2.textContent = d.target.node.nodeinfo.hostname;
|
props: { href: router.generateLink({ node: d[0].source.node_id }) }
|
||||||
h2.appendChild(a2);
|
}, d[0].source.hostname),
|
||||||
el.appendChild(h2);
|
V.h('span', ' - '),
|
||||||
|
V.h('a', {
|
||||||
|
props: { href: router.generateLink({ node: d[0].target.node_id }) }
|
||||||
|
}, d[0].target.hostname)
|
||||||
|
])));
|
||||||
|
|
||||||
var attributes = document.createElement('table');
|
helper.attributeEntry(V, children, 'node.hardware', (d[0].source.model ? d[0].source.model + ' – ' : '') +
|
||||||
attributes.classList.add('attributes');
|
(d[0].target.model ? d[0].target.model : ''));
|
||||||
|
helper.attributeEntry(V, children, 'node.distance', helper.showDistance(d[0]));
|
||||||
|
|
||||||
helper.attributeEntry(attributes, 'node.tq', helper.showTq(d));
|
d.forEach(function (link) {
|
||||||
helper.attributeEntry(attributes, 'node.distance', helper.showDistance(d));
|
children.push(V.h('tr', { props: { className: 'header' } }, [
|
||||||
var hw1 = helper.dictGet(d.source.node.nodeinfo, ['hardware', 'model']);
|
V.h('th', _.t('node.connectionType')),
|
||||||
var hw2 = helper.dictGet(d.target.node.nodeinfo, ['hardware', 'model']);
|
V.h('th', link.type)
|
||||||
helper.attributeEntry(attributes, 'node.hardware', hw1 + ' – ' + hw2);
|
]));
|
||||||
|
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))
|
||||||
|
);
|
||||||
|
|
||||||
el.appendChild(attributes);
|
if (config.linkTypeInfos) {
|
||||||
|
config.linkTypeInfos.forEach(function (o) {
|
||||||
if (config.linkInfos) {
|
showStatImg(img, o, link, time);
|
||||||
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;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
define(['helper'], function (helper) {
|
define(['helper'], function (helper) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
return function (config, el, router, d) {
|
return function (el, d) {
|
||||||
var sidebarTitle = document.createElement('h2');
|
var sidebarTitle = document.createElement('h2');
|
||||||
sidebarTitle.textContent = _.t('location.location');
|
sidebarTitle.textContent = _.t('location.location');
|
||||||
el.appendChild(sidebarTitle);
|
el.appendChild(sidebarTitle);
|
||||||
@ -14,16 +14,19 @@ define(['helper'], function (helper) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var editLat = document.createElement('input');
|
var editLat = document.createElement('input');
|
||||||
|
editLat.setAttribute('aria-label', _.t('location.latitude'));
|
||||||
editLat.type = 'text';
|
editLat.type = 'text';
|
||||||
editLat.value = d.lat.toFixed(9);
|
editLat.value = d.lat.toFixed(9);
|
||||||
el.appendChild(createBox('lat', _.t('location.latitude'), editLat));
|
el.appendChild(createBox('lat', _.t('location.latitude'), editLat));
|
||||||
|
|
||||||
var editLng = document.createElement('input');
|
var editLng = document.createElement('input');
|
||||||
|
editLng.setAttribute('aria-label', _.t('location.longitude'));
|
||||||
editLng.type = 'text';
|
editLng.type = 'text';
|
||||||
editLng.value = d.lng.toFixed(9);
|
editLng.value = d.lng.toFixed(9);
|
||||||
el.appendChild(createBox('lng', _.t('location.longitude'), editLng));
|
el.appendChild(createBox('lng', _.t('location.longitude'), editLng));
|
||||||
|
|
||||||
var editUci = document.createElement('textarea');
|
var editUci = document.createElement('textarea');
|
||||||
|
editUci.setAttribute('aria-label', 'Uci');
|
||||||
editUci.value =
|
editUci.value =
|
||||||
"uci set gluon-node-info.@location[0]='location'; " +
|
"uci set gluon-node-info.@location[0]='location'; " +
|
||||||
"uci set gluon-node-info.@location[0].share_location='1';" +
|
"uci set gluon-node-info.@location[0].share_location='1';" +
|
||||||
@ -41,6 +44,7 @@ define(['helper'], function (helper) {
|
|||||||
var btn = document.createElement('button');
|
var btn = document.createElement('button');
|
||||||
btn.classList.add('ion-clipboard');
|
btn.classList.add('ion-clipboard');
|
||||||
btn.title = _.t('location.copy');
|
btn.title = _.t('location.copy');
|
||||||
|
btn.setAttribute('aria-label', _.t('location.copy'));
|
||||||
btn.onclick = function onclick() {
|
btn.onclick = function onclick() {
|
||||||
copy2clip(inputElem.id);
|
copy2clip(inputElem.id);
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
define(['infobox/link', 'infobox/node', 'infobox/location'], function (link, node, location) {
|
define(['infobox/link', 'infobox/node', 'infobox/location'], function (Link, Node, location) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
return function (config, sidebar, router) {
|
return function (sidebar, linkScale) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var el;
|
var el;
|
||||||
|
var node;
|
||||||
|
var link;
|
||||||
|
|
||||||
function destroy() {
|
function destroy() {
|
||||||
if (el && el.parentNode) {
|
if (el && el.parentNode) {
|
||||||
el.parentNode.removeChild(el);
|
el.parentNode.removeChild(el);
|
||||||
el = undefined;
|
node = link = el = undefined;
|
||||||
sidebar.reveal();
|
sidebar.reveal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,6 +30,7 @@ define(['infobox/link', 'infobox/node', 'infobox/location'], function (link, nod
|
|||||||
var closeButton = document.createElement('button');
|
var closeButton = document.createElement('button');
|
||||||
closeButton.classList.add('close');
|
closeButton.classList.add('close');
|
||||||
closeButton.classList.add('ion-close');
|
closeButton.classList.add('ion-close');
|
||||||
|
closeButton.setAttribute('aria-label', _.t('close'));
|
||||||
closeButton.onclick = function () {
|
closeButton.onclick = function () {
|
||||||
router.fullUrl();
|
router.fullUrl();
|
||||||
};
|
};
|
||||||
@ -36,19 +39,30 @@ define(['infobox/link', 'infobox/node', 'infobox/location'], function (link, nod
|
|||||||
|
|
||||||
self.resetView = destroy;
|
self.resetView = destroy;
|
||||||
|
|
||||||
self.gotoNode = function gotoNode(d) {
|
self.gotoNode = function gotoNode(d, nodeDict) {
|
||||||
create();
|
create();
|
||||||
node(config, el, router, d);
|
node = new Node(el, d, linkScale, nodeDict);
|
||||||
|
node.render();
|
||||||
};
|
};
|
||||||
|
|
||||||
self.gotoLink = function gotoLink(d) {
|
self.gotoLink = function gotoLink(d) {
|
||||||
create();
|
create();
|
||||||
link(config, el, router, d);
|
link = new Link(el, d, linkScale);
|
||||||
|
link.render();
|
||||||
};
|
};
|
||||||
|
|
||||||
self.gotoLocation = function gotoLocation(d) {
|
self.gotoLocation = function gotoLocation(d) {
|
||||||
create();
|
create();
|
||||||
location(config, el, router, d);
|
location(el, d);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.setData = function setData(d) {
|
||||||
|
if (typeof node === 'object') {
|
||||||
|
node.setData(d);
|
||||||
|
}
|
||||||
|
if (typeof link === 'object') {
|
||||||
|
link.setData(d);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
@ -1,272 +1,120 @@
|
|||||||
define(['sorttable', 'snabbdom', 'd3-interpolate', 'moment', 'helper'],
|
define(['sorttable', 'snabbdom', 'd3-interpolate', 'helper', 'utils/node'],
|
||||||
function (SortTable, V, d3Interpolate, moment, helper) {
|
function (SortTable, V, d3Interpolate, helper, nodef) {
|
||||||
'use strict';
|
'use strict';
|
||||||
V = V.default;
|
V = V.default;
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return release + ' / ' + base;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showUptime(d) {
|
|
||||||
if (!('uptime' in d.statistics)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return moment.duration(d.statistics.uptime, 'seconds').humanize();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showFirstseen(d) {
|
|
||||||
if (!('firstseen' in d)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
function showStatImg(o, d) {
|
||||||
var subst = {};
|
var subst = {
|
||||||
subst['{NODE_ID}'] = d.nodeinfo.node_id;
|
'{NODE_ID}': d.node_id,
|
||||||
subst['{NODE_NAME}'] = d.nodeinfo.hostname.replace(/[^a-z0-9\-]/ig, '_');
|
'{NODE_NAME}': d.hostname.replace(/[^a-z0-9\-]/ig, '_'),
|
||||||
subst['{TIME}'] = d.lastseen.format('DDMMYYYYHmmss');
|
'{TIME}': d.lastseen.format('DDMMYYYYHmmss'),
|
||||||
subst['{LOCALE}'] = _.locale();
|
'{LOCALE}': _.locale()
|
||||||
return helper.showStat(o, subst);
|
};
|
||||||
|
return helper.showStat(V, o, subst);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function (config, el, router, d) {
|
return function (el, d, linkScale, nodeDict) {
|
||||||
var linkScale = d3Interpolate.interpolate('#F02311', '#04C714');
|
function nodeLink(node) {
|
||||||
|
return V.h('a', {
|
||||||
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: {
|
props: {
|
||||||
className: 'online',
|
className: node.is_online ? 'online' : 'offline',
|
||||||
href: router.generateLink({ node: n.node.nodeinfo.node_id })
|
href: router.generateLink({ node: node.node_id })
|
||||||
}, on: {
|
}, on: {
|
||||||
click: function (e) {
|
click: function (e) {
|
||||||
router.fullUrl({ node: n.node.nodeinfo.node_id }, e);
|
router.fullUrl({ node: node.node_id }, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, n.node.nodeinfo.hostname);
|
}, node.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');
|
function nodeIdLink(nodeId) {
|
||||||
h2.textContent = d.nodeinfo.hostname;
|
if (nodeDict[nodeId]) {
|
||||||
el.appendChild(h2);
|
return nodeLink(nodeDict[nodeId]);
|
||||||
|
}
|
||||||
var attributes = document.createElement('table');
|
return nodeId;
|
||||||
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']));
|
function showGateway(node) {
|
||||||
helper.attributeEntry(attributes, 'node.primaryMac', helper.dictGet(d.nodeinfo, ['network', 'mac']));
|
var gatewayCols = [
|
||||||
helper.attributeEntry(attributes, 'node.id', helper.dictGet(d.nodeinfo, ['node_id']));
|
V.h('span', [
|
||||||
helper.attributeEntry(attributes, 'node.firmware', showFirmware(d));
|
nodeIdLink(node.gateway_nexthop),
|
||||||
helper.attributeEntry(attributes, 'node.site', showSite(d, config));
|
V.h('br'),
|
||||||
helper.attributeEntry(attributes, 'node.uptime', showUptime(d));
|
_.t('node.nexthop')
|
||||||
helper.attributeEntry(attributes, 'node.firstSeen', showFirstseen(d));
|
]),
|
||||||
if (config.nodeInfobox && config.nodeInfobox.hardwareUsage) {
|
V.h('span', { props: { className: 'ion-arrow-right-c' } }),
|
||||||
helper.attributeEntry(attributes, 'node.systemLoad', showLoad(d));
|
V.h('span', [
|
||||||
helper.attributeEntry(attributes, 'node.ram', showRAM(d));
|
nodeIdLink(node.gateway),
|
||||||
|
V.h('br'),
|
||||||
|
'IPv4'
|
||||||
|
])
|
||||||
|
];
|
||||||
|
|
||||||
|
if (node.gateway6 !== undefined) {
|
||||||
|
gatewayCols.push(V.h('span', [
|
||||||
|
nodeIdLink(node.gateway6),
|
||||||
|
V.h('br'),
|
||||||
|
'IPv6'
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
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);
|
return V.h('td', { props: { className: 'gateway' } }, gatewayCols);
|
||||||
|
}
|
||||||
|
|
||||||
if (d.neighbours.length > 0) {
|
function renderNeighbourRow(n) {
|
||||||
var h3 = document.createElement('h3');
|
var icons = [V.h('span', { props: { className: 'icon ion-' + (n.link.type.indexOf('wifi') === 0 ? 'wifi' : 'share-alt'), title: _.t(n.link.type) } })];
|
||||||
h3.textContent = _.t('node.link', d.neighbours.length) + '(' + d.neighbours.length + ')';
|
if (helper.hasLocation(n.node)) {
|
||||||
el.appendChild(h3);
|
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 = [{
|
var headings = [{
|
||||||
name: ''
|
name: '',
|
||||||
|
sort: function (a, b) {
|
||||||
|
return a.link.type.localeCompare(b.link.type);
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
name: 'node.nodes',
|
name: 'node.nodes',
|
||||||
sort: function (a, b) {
|
sort: function (a, b) {
|
||||||
return a.node.nodeinfo.hostname.localeCompare(b.node.nodeinfo.hostname);
|
return a.node.hostname.localeCompare(b.node.hostname);
|
||||||
},
|
},
|
||||||
reverse: false
|
reverse: false
|
||||||
}, {
|
}, {
|
||||||
name: 'node.clients',
|
name: 'node.clients',
|
||||||
class: 'ion-people',
|
class: 'ion-people',
|
||||||
sort: function (a, b) {
|
sort: function (a, b) {
|
||||||
return ('clients' in a.node.statistics ? a.node.statistics.clients : -1) -
|
return a.node.clients - b.node.clients;
|
||||||
('clients' in b.node.statistics ? b.node.statistics.clients : -1);
|
|
||||||
},
|
},
|
||||||
reverse: true
|
reverse: true
|
||||||
}, {
|
}, {
|
||||||
name: 'node.tq',
|
name: 'node.tq',
|
||||||
class: 'ion-connection-bars',
|
class: 'ion-connection-bars',
|
||||||
sort: function (a, b) {
|
sort: function (a, b) {
|
||||||
return a.link.tq - b.link.tq;
|
return a.link.source_tq - b.link.source_tq;
|
||||||
},
|
},
|
||||||
reverse: true
|
reverse: true
|
||||||
}, {
|
}, {
|
||||||
@ -278,20 +126,69 @@ define(['sorttable', 'snabbdom', 'd3-interpolate', 'moment', 'helper'],
|
|||||||
},
|
},
|
||||||
reverse: true
|
reverse: true
|
||||||
}];
|
}];
|
||||||
|
var tableNeighbour = new SortTable(headings, 1, renderNeighbourRow);
|
||||||
|
|
||||||
var table = new SortTable(headings, 1, renderNeighbourRow);
|
el.appendChild(header);
|
||||||
table.setData(d.neighbours);
|
el.appendChild(table);
|
||||||
table.el.elm.classList.add('node-links');
|
el.appendChild(neighbours);
|
||||||
el.appendChild(table.el.elm);
|
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
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
children.push(V.h('tr', [
|
||||||
|
V.h('th', _.t('node.gateway')),
|
||||||
|
showGateway(d)
|
||||||
|
]));
|
||||||
|
|
||||||
|
var elNew = V.h('table', children);
|
||||||
|
table = V.patch(table, elNew);
|
||||||
|
table.elm.classList.add('attributes');
|
||||||
|
|
||||||
|
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');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.nodeInfos) {
|
if (config.nodeInfos) {
|
||||||
|
var img = [];
|
||||||
config.nodeInfos.forEach(function (nodeInfo) {
|
config.nodeInfos.forEach(function (nodeInfo) {
|
||||||
var h4 = document.createElement('h4');
|
img.push(V.h('h4', nodeInfo.name));
|
||||||
h4.textContent = nodeInfo.name;
|
img.push(showStatImg(nodeInfo, d));
|
||||||
el.appendChild(h4);
|
|
||||||
el.appendChild(showStatImg(nodeInfo, d));
|
|
||||||
});
|
});
|
||||||
|
images = V.patch(images, V.h('div', img));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.setData = function setData(data) {
|
||||||
|
if (data.nodeDict[d.node_id]) {
|
||||||
|
d = data.nodeDict[d.node_id];
|
||||||
|
}
|
||||||
|
self.render();
|
||||||
|
};
|
||||||
|
return self;
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,46 +1,53 @@
|
|||||||
define(['helper'], function (helper) {
|
define(['helper'], function (helper) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
return function (config, language) {
|
return function (language) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var stats = document.createTextNode('');
|
var stats = document.createTextNode('');
|
||||||
var timestamp = document.createTextNode('');
|
var timestamp = document.createTextNode('');
|
||||||
|
|
||||||
self.setData = function setData(d) {
|
self.setData = function setData(d) {
|
||||||
var totalNodes = helper.sum(d.nodes.all.map(helper.one));
|
var totalNodes = Object.keys(d.nodeDict).length;
|
||||||
var totalOnlineNodes = helper.sum(d.nodes.all.filter(helper.online).map(helper.one));
|
var totalOnlineNodes = d.nodes.online.length;
|
||||||
var totalClients = helper.sum(d.nodes.all.filter(helper.online).map(function (n) {
|
var totalClients = helper.sum(d.nodes.online.map(function (n) {
|
||||||
return n.statistics.clients ? n.statistics.clients : 0;
|
return n.clients;
|
||||||
}));
|
}));
|
||||||
var totalGateways = helper.sum(d.nodes.all.filter(helper.online).filter(function (n) {
|
var totalGateways = helper.sum(d.nodes.online.filter(function (n) {
|
||||||
return n.flags.gateway;
|
return n.is_gateway;
|
||||||
}).map(helper.one));
|
}).map(helper.one));
|
||||||
|
|
||||||
stats.textContent = _.t('sidebar.nodes', { total: totalNodes, online: totalOnlineNodes }) + ' ' +
|
stats.textContent = _.t('sidebar.nodes', { total: totalNodes, online: totalOnlineNodes }) + ' ' +
|
||||||
_.t('sidebar.clients', { smart_count: totalClients }) + ' ' +
|
_.t('sidebar.clients', { smart_count: totalClients }) + ' ' +
|
||||||
_.t('sidebar.gateway', { smart_count: totalGateways });
|
_.t('sidebar.gateway', { smart_count: totalGateways });
|
||||||
|
|
||||||
timestamp.textContent = _.t('sidebar.lastUpdate') + ': ' + d.timestamp.format('DD.MM.Y HH:mm');
|
timestamp.textContent = _.t('sidebar.lastUpdate') + ' ' + d.timestamp.fromNow();
|
||||||
};
|
};
|
||||||
|
|
||||||
self.render = function render(el) {
|
self.render = function render(el) {
|
||||||
var h2 = document.createElement('h2');
|
var h1 = document.createElement('h1');
|
||||||
h2.textContent = config.siteName;
|
h1.textContent = config.siteName;
|
||||||
el.appendChild(h2);
|
el.appendChild(h1);
|
||||||
|
|
||||||
language.languageSelect(el);
|
language.languageSelect(el);
|
||||||
|
|
||||||
var p = document.createElement('p');
|
var p = document.createElement('p');
|
||||||
p.classList.add('legend');
|
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(stats);
|
||||||
p.appendChild(document.createElement('br'));
|
p.appendChild(document.createElement('br'));
|
||||||
p.appendChild(timestamp);
|
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;
|
return self;
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
|
define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
V = V.default;
|
||||||
|
|
||||||
function linkName(d) {
|
function linkName(d) {
|
||||||
return (d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + ' – ' + d.target.node.nodeinfo.hostname;
|
return (d.source ? d.source.hostname : d.source.id) + ' – ' + d.target.hostname;
|
||||||
}
|
}
|
||||||
|
|
||||||
var headings = [{
|
var headings = [{
|
||||||
|
name: '',
|
||||||
|
sort: function (a, b) {
|
||||||
|
return a.type.localeCompare(b.type);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
name: 'node.nodes',
|
name: 'node.nodes',
|
||||||
sort: function (a, b) {
|
sort: function (a, b) {
|
||||||
return linkName(a).localeCompare(linkName(b));
|
return linkName(a).localeCompare(linkName(b));
|
||||||
@ -15,7 +21,7 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
|
|||||||
name: 'node.tq',
|
name: 'node.tq',
|
||||||
class: 'ion-connection-bars',
|
class: 'ion-connection-bars',
|
||||||
sort: function (a, b) {
|
sort: function (a, b) {
|
||||||
return a.tq - b.tq;
|
return (a.source_tq + a.target_tq) / 2 - (b.source_tq + b.target_tq) / 2;
|
||||||
},
|
},
|
||||||
reverse: true
|
reverse: true
|
||||||
}, {
|
}, {
|
||||||
@ -28,9 +34,8 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
|
|||||||
reverse: true
|
reverse: true
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return function (linkScale, router) {
|
return function (linkScale) {
|
||||||
var table = new SortTable(headings, 2, renderRow);
|
var table = new SortTable(headings, 3, renderRow);
|
||||||
V = V.default;
|
|
||||||
|
|
||||||
function renderRow(d) {
|
function renderRow(d) {
|
||||||
var td1Content = [V.h('a', {
|
var td1Content = [V.h('a', {
|
||||||
@ -43,11 +48,12 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
|
|||||||
}
|
}
|
||||||
}, linkName(d))];
|
}, linkName(d))];
|
||||||
|
|
||||||
var td1 = V.h('td', td1Content);
|
return V.h('tr', [
|
||||||
var td2 = V.h('td', { style: { color: linkScale(1 / d.tq) } }, helper.showTq(d));
|
V.h('td', V.h('span', { props: { className: 'icon ion-' + (d.type.indexOf('wifi') === 0 ? 'wifi' : 'share-alt'), title: _.t(d.type) } })),
|
||||||
var td3 = V.h('td', helper.showDistance(d));
|
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)),
|
||||||
return V.h('tr', [td1, td2, td3]);
|
V.h('td', helper.showDistance(d))
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.render = function render(d) {
|
this.render = function render(d) {
|
||||||
@ -59,7 +65,7 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.setData = function setData(d) {
|
this.setData = function setData(d) {
|
||||||
table.setData(d.graph.links);
|
table.setData(d.links);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
158
lib/main.js
@ -2,108 +2,53 @@ define(['moment', 'utils/router', 'leaflet', 'gui', 'helper', 'utils/language'],
|
|||||||
function (moment, Router, L, GUI, helper, Language) {
|
function (moment, Router, L, GUI, helper, Language) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
return function (config) {
|
return function () {
|
||||||
function handleData(data) {
|
function handleData(data) {
|
||||||
var dataNodes = {};
|
var timestamp;
|
||||||
dataNodes.nodes = [];
|
var nodes = [];
|
||||||
var dataGraph = {};
|
var links = [];
|
||||||
dataGraph.batadv = {};
|
var nodeDict = {};
|
||||||
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) {
|
for (var i = 0; i < data.length; ++i) {
|
||||||
var vererr;
|
nodes = nodes.concat(data[i].nodes);
|
||||||
if (i % 2) {
|
timestamp = data[i].timestamp;
|
||||||
if (data[i].version !== 1) {
|
links = links.concat(data[i].links);
|
||||||
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) {
|
nodes.forEach(function (node) {
|
||||||
node.firstseen = moment.utc(node.firstseen).local();
|
node.firstseen = moment.utc(node.firstseen).local();
|
||||||
node.lastseen = moment.utc(node.lastseen).local();
|
node.lastseen = moment.utc(node.lastseen).local();
|
||||||
});
|
});
|
||||||
|
|
||||||
var now = moment();
|
var age = moment().subtract(config.maxAge, 'days');
|
||||||
var age = moment(now).subtract(config.maxAge, 'days');
|
|
||||||
|
|
||||||
var newnodes = helper.limit('firstseen', age, helper.sortByKey('firstseen', nodes).filter(helper.online));
|
var online = nodes.filter(function (d) {
|
||||||
var lostnodes = helper.limit('lastseen', age, helper.sortByKey('lastseen', nodes).filter(helper.offline));
|
return d.is_online;
|
||||||
|
});
|
||||||
var graphnodes = {};
|
var offline = nodes.filter(function (d) {
|
||||||
|
return !d.is_online;
|
||||||
dataNodes.nodes.forEach(function (d) {
|
|
||||||
graphnodes[d.nodeinfo.node_id] = d;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var graph = dataGraph.batadv;
|
var newnodes = helper.limit('firstseen', age, helper.sortByKey('firstseen', online));
|
||||||
|
var lostnodes = helper.limit('lastseen', age, helper.sortByKey('lastseen', offline));
|
||||||
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) {
|
nodes.forEach(function (d) {
|
||||||
d.neighbours = [];
|
d.neighbours = [];
|
||||||
|
nodeDict[d.node_id] = d;
|
||||||
});
|
});
|
||||||
|
|
||||||
links.forEach(function (d) {
|
links.forEach(function (d) {
|
||||||
var ids;
|
d.source = nodeDict[d.source];
|
||||||
|
d.target = nodeDict[d.target];
|
||||||
|
|
||||||
ids = [d.source.node.nodeinfo.node_id, d.target.node.nodeinfo.node_id];
|
d.id = [d.source.node_id, d.target.node_id].join('-');
|
||||||
d.source.node.neighbours.push({ node: d.target.node, link: d, incoming: false });
|
d.source.neighbours.push({ node: d.target, link: d });
|
||||||
d.target.node.neighbours.push({ node: d.source.node, link: d, incoming: true });
|
d.target.neighbours.push({ node: d.source, link: d });
|
||||||
if (d.vpn) {
|
|
||||||
d.source.node.meshlinks = d.source.node.meshlinks ? d.source.node.meshlinks + 1 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
d.id = ids.join('-');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
d.latlngs = [];
|
d.latlngs = [];
|
||||||
d.latlngs.push(L.latLng(d.source.node.nodeinfo.location.latitude, d.source.node.nodeinfo.location.longitude));
|
d.latlngs.push(L.latLng(d.source.location.latitude, d.source.location.longitude));
|
||||||
d.latlngs.push(L.latLng(d.target.node.nodeinfo.location.latitude, d.target.node.nodeinfo.location.longitude));
|
d.latlngs.push(L.latLng(d.target.location.latitude, d.target.location.longitude));
|
||||||
|
|
||||||
d.distance = d.latlngs[0].distanceTo(d.latlngs[1]);
|
d.distance = d.latlngs[0].distanceTo(d.latlngs[1]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -111,50 +56,53 @@ define(['moment', 'utils/router', 'leaflet', 'gui', 'helper', 'utils/language'],
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
links.sort(function (a, b) {
|
|
||||||
return b.tq - a.tq;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
now: now,
|
now: moment(),
|
||||||
timestamp: moment.utc(dataNodes.timestamp).local(),
|
timestamp: moment.utc(timestamp).local(),
|
||||||
nodes: {
|
nodes: {
|
||||||
all: nodes,
|
all: nodes,
|
||||||
|
online: online,
|
||||||
|
offline: offline,
|
||||||
new: newnodes,
|
new: newnodes,
|
||||||
lost: lostnodes
|
lost: lostnodes
|
||||||
},
|
},
|
||||||
graph: {
|
|
||||||
links: links,
|
links: links,
|
||||||
nodes: graph.nodes
|
nodeDict: nodeDict
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var language = new Language(config);
|
var language = new Language();
|
||||||
var router = new Router(language);
|
window.router = new Router(language);
|
||||||
|
|
||||||
var urls = [];
|
config.dataPath.forEach(function (d, i) {
|
||||||
|
config.dataPath[i] += 'meshviewer.json';
|
||||||
|
});
|
||||||
|
|
||||||
if (typeof config.dataPath === 'string' || config.dataPath instanceof String) {
|
language.init(router);
|
||||||
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() {
|
function update() {
|
||||||
language.init(router);
|
return Promise.all(config.dataPath.map(helper.getJSON))
|
||||||
return Promise.all(urls.map(helper.getJSON))
|
|
||||||
.then(handleData);
|
.then(handleData);
|
||||||
}
|
}
|
||||||
|
|
||||||
update()
|
update()
|
||||||
.then(function (d) {
|
.then(function (d) {
|
||||||
var gui = new GUI(config, router, language);
|
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);
|
||||||
gui.setData(d);
|
gui.setData(d);
|
||||||
router.setData(d);
|
router.setData(d);
|
||||||
router.resolve();
|
router.resolve();
|
||||||
@ -168,7 +116,7 @@ define(['moment', 'utils/router', 'leaflet', 'gui', 'helper', 'utils/language'],
|
|||||||
})
|
})
|
||||||
.catch(function (e) {
|
.catch(function (e) {
|
||||||
document.querySelector('.loader').innerHTML += e.message
|
document.querySelector('.loader').innerHTML += e.message
|
||||||
+ '<br /><br /><button onclick="location.reload(true)" class="btn text">Try to reload</button><br /> or report to your community';
|
+ '<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';
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
59
lib/map.js
@ -1,4 +1,4 @@
|
|||||||
define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
|
define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet', 'map/activearea'],
|
||||||
function (ClientLayer, LabelLayer, Button, L) {
|
function (ClientLayer, LabelLayer, Button, L) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
|
|||||||
minZoom: 0
|
minZoom: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
return function (config, linkScale, sidebar, router, buttons) {
|
return function (linkScale, sidebar, buttons) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var savedView;
|
var savedView;
|
||||||
|
|
||||||
@ -27,10 +27,26 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
|
|||||||
document.querySelector('.leaflet-control-layers').classList.add('leaflet-control-layers-expanded');
|
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');
|
var el = document.createElement('div');
|
||||||
el.classList.add('map');
|
el.classList.add('map');
|
||||||
|
|
||||||
map = L.map(el, options);
|
map = L.map(el, options);
|
||||||
|
mapActiveArea();
|
||||||
|
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
config.mapLayers.forEach(function (item, i) {
|
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())) {
|
if ((typeof item.config.start === 'number' && item.config.start <= now.getHours()) || (typeof item.config.end === 'number' && item.config.end > now.getHours())) {
|
||||||
@ -47,7 +63,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
|
|||||||
var layers = config.mapLayers.map(function (d) {
|
var layers = config.mapLayers.map(function (d) {
|
||||||
return {
|
return {
|
||||||
'name': d.name,
|
'name': d.name,
|
||||||
'layer': 'url' in d ? L.tileLayer(d.url.replace('{retina}', L.Browser.retina ? '@2x' : ''), d.config) : console.warn('Missing map url')
|
'layer': L.tileLayer(d.url.replace('{retina}', L.Browser.retina ? '@2x' : ''), d.config)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -57,13 +73,23 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
|
|||||||
baseLayers[d.name] = d.layer;
|
baseLayers[d.name] = d.layer;
|
||||||
});
|
});
|
||||||
|
|
||||||
var button = new Button(config, map, router, buttons);
|
var button = new Button(map, buttons);
|
||||||
|
|
||||||
map.on('locationfound', button.locationFound);
|
map.on('locationfound', button.locationFound);
|
||||||
map.on('locationerror', button.locationError);
|
map.on('locationerror', button.locationError);
|
||||||
map.on('dragend', saveView);
|
map.on('dragend', saveView);
|
||||||
map.on('contextmenu', contextMenuOpenLayerMenu);
|
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();
|
button.init();
|
||||||
|
|
||||||
layerControl = L.control.layers(baseLayers, [], { position: 'bottomright' });
|
layerControl = L.control.layers(baseLayers, [], { position: 'bottomright' });
|
||||||
@ -79,6 +105,8 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
|
|||||||
labelLayer.addTo(map);
|
labelLayer.addTo(map);
|
||||||
labelLayer.setZIndex(6);
|
labelLayer.setZIndex(6);
|
||||||
|
|
||||||
|
sidebar.button.addEventListener('visibility', setActiveArea);
|
||||||
|
|
||||||
map.on('zoom', function () {
|
map.on('zoom', function () {
|
||||||
clientLayer.redraw();
|
clientLayer.redraw();
|
||||||
labelLayer.redraw();
|
labelLayer.redraw();
|
||||||
@ -105,6 +133,14 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 nodeDict = {};
|
||||||
var linkDict = {};
|
var linkDict = {};
|
||||||
var highlight;
|
var highlight;
|
||||||
@ -120,7 +156,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setView(bounds, zoom) {
|
function setView(bounds, zoom) {
|
||||||
map.fitBounds(bounds, { paddingTopLeft: [sidebar(), 0], maxZoom: (zoom ? zoom : config.nodeZoom) });
|
map.fitBounds(bounds, { maxZoom: (zoom ? zoom : config.nodeZoom) });
|
||||||
}
|
}
|
||||||
|
|
||||||
function goto(m) {
|
function goto(m) {
|
||||||
@ -142,12 +178,12 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
|
|||||||
var m;
|
var m;
|
||||||
|
|
||||||
if (highlight !== undefined) {
|
if (highlight !== undefined) {
|
||||||
if (highlight.type === 'node' && nodeDict[highlight.o.nodeinfo.node_id]) {
|
if (highlight.type === 'node' && nodeDict[highlight.o.node_id]) {
|
||||||
m = nodeDict[highlight.o.nodeinfo.node_id];
|
m = nodeDict[highlight.o.node_id];
|
||||||
m.setStyle({ color: 'orange', weight: 20, fillOpacity: 1, opacity: 0.7, className: 'stroke-first' });
|
m.setStyle(config.map.highlightNode);
|
||||||
} else if (highlight.type === 'link' && linkDict[highlight.o.id]) {
|
} else if (highlight.type === 'link' && linkDict[highlight.o.id]) {
|
||||||
m = linkDict[highlight.o.id];
|
m = linkDict[highlight.o.id];
|
||||||
m.setStyle({ weight: 4, opacity: 1, dashArray: '5, 10' });
|
m.setStyle(config.map.highlightLink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +203,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
|
|||||||
linkDict = {};
|
linkDict = {};
|
||||||
|
|
||||||
clientLayer.setData(data);
|
clientLayer.setData(data);
|
||||||
labelLayer.setData(data, map, nodeDict, linkDict, linkScale, router, config);
|
labelLayer.setData(data, map, nodeDict, linkDict, linkScale);
|
||||||
|
|
||||||
updateView(true);
|
updateView(true);
|
||||||
};
|
};
|
||||||
@ -186,7 +222,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
|
|||||||
|
|
||||||
self.gotoLink = function gotoLink(d) {
|
self.gotoLink = function gotoLink(d) {
|
||||||
button.disableTracking();
|
button.disableTracking();
|
||||||
highlight = { type: 'link', o: d };
|
highlight = { type: 'link', o: d[0] };
|
||||||
updateView();
|
updateView();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -197,6 +233,7 @@ define(['map/clientlayer', 'map/labellayer', 'map/button', 'leaflet'],
|
|||||||
|
|
||||||
self.destroy = function destroy() {
|
self.destroy = function destroy() {
|
||||||
button.clearButtons();
|
button.clearButtons();
|
||||||
|
sidebar.button.removeEventListener('visibility', setActiveArea);
|
||||||
map.remove();
|
map.remove();
|
||||||
|
|
||||||
if (el.parentNode) {
|
if (el.parentNode) {
|
||||||
|
291
lib/map/activearea.js
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,5 @@
|
|||||||
define(['map/clientlayer', 'map/labellayer', 'leaflet', 'moment', 'map/locationmarker'],
|
define(['map/clientlayer', 'map/labellayer', 'leaflet', 'map/locationmarker'],
|
||||||
function (ClientLayer, LabelLayer, L, moment, LocationMarker) {
|
function (ClientLayer, LabelLayer, L, LocationMarker) {
|
||||||
'use strict';
|
'use strict';
|
||||||
var self = {};
|
var self = {};
|
||||||
|
|
||||||
@ -28,8 +28,8 @@ define(['map/clientlayer', 'map/labellayer', 'leaflet', 'moment', 'map/locationm
|
|||||||
|
|
||||||
var LocateButton = ButtonBase.extend({
|
var LocateButton = ButtonBase.extend({
|
||||||
onAdd: function () {
|
onAdd: function () {
|
||||||
var button = L.DomUtil.create('button', 'ion-locate shadow');
|
var button = L.DomUtil.create('button', 'ion-locate');
|
||||||
button.setAttribute('data-tooltip', _.t('button.tracking'));
|
button.setAttribute('aria-label', _.t('button.tracking'));
|
||||||
L.DomEvent.disableClickPropagation(button);
|
L.DomEvent.disableClickPropagation(button);
|
||||||
L.DomEvent.addListener(button, 'click', this.onClick, this);
|
L.DomEvent.addListener(button, 'click', this.onClick, this);
|
||||||
|
|
||||||
@ -45,8 +45,8 @@ define(['map/clientlayer', 'map/labellayer', 'leaflet', 'moment', 'map/locationm
|
|||||||
|
|
||||||
var CoordsPickerButton = ButtonBase.extend({
|
var CoordsPickerButton = ButtonBase.extend({
|
||||||
onAdd: function () {
|
onAdd: function () {
|
||||||
var button = L.DomUtil.create('button', 'ion-pin shadow');
|
var button = L.DomUtil.create('button', 'ion-pin');
|
||||||
button.setAttribute('data-tooltip', _.t('button.location'));
|
button.setAttribute('aria-label', _.t('button.location'));
|
||||||
|
|
||||||
// Click propagation isn't disabled as this causes problems with the
|
// Click propagation isn't disabled as this causes problems with the
|
||||||
// location picking mode; instead propagation is stopped in onClick().
|
// location picking mode; instead propagation is stopped in onClick().
|
||||||
@ -63,7 +63,7 @@ define(['map/clientlayer', 'map/labellayer', 'leaflet', 'moment', 'map/locationm
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return function (config, map, router, buttons) {
|
return function (map, buttons) {
|
||||||
var userLocation;
|
var userLocation;
|
||||||
|
|
||||||
var locateUserButton = new LocateButton(function (d) {
|
var locateUserButton = new LocateButton(function (d) {
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
define(['leaflet', 'rbush', 'helper'],
|
define(['leaflet', 'rbush', 'helper'],
|
||||||
function (L, rbush, helper) {
|
function (L, RBush, helper) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
return L.GridLayer.extend({
|
return L.GridLayer.extend({
|
||||||
mapRTree: function mapRTree(d) {
|
mapRTree: function mapRTree(d) {
|
||||||
return {
|
return {
|
||||||
minX: d.nodeinfo.location.latitude, minY: d.nodeinfo.location.longitude,
|
minX: d.location.latitude, minY: d.location.longitude,
|
||||||
maxX: d.nodeinfo.location.latitude, maxY: d.nodeinfo.location.longitude,
|
maxX: d.location.latitude, maxY: d.location.longitude,
|
||||||
node: d
|
node: d
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
setData: function (data) {
|
setData: function (data) {
|
||||||
var rtreeOnlineAll = rbush(9);
|
var rtreeOnlineAll = new RBush(9);
|
||||||
|
|
||||||
this.data = rtreeOnlineAll.load(data.nodes.all.filter(helper.online).filter(helper.hasLocation).map(this.mapRTree));
|
this.data = rtreeOnlineAll.load(data.nodes.online.filter(helper.hasLocation).map(this.mapRTree));
|
||||||
|
|
||||||
// pre-calculate start angles
|
// pre-calculate start angles
|
||||||
this.data.all().forEach(function (n) {
|
this.data.all().forEach(function (n) {
|
||||||
n.startAngle = (parseInt(n.node.nodeinfo.node_id.substr(10, 2), 16) / 255) * 2 * Math.PI;
|
n.startAngle = (parseInt(n.node.node_id.substr(10, 2), 16) / 255) * 2 * Math.PI;
|
||||||
});
|
});
|
||||||
this.redraw();
|
this.redraw();
|
||||||
},
|
},
|
||||||
@ -45,21 +45,17 @@ define(['leaflet', 'rbush', 'helper'],
|
|||||||
return tile;
|
return tile;
|
||||||
}
|
}
|
||||||
|
|
||||||
var startDistance = 12;
|
var startDistance = 10;
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
nodes.forEach(function (d) {
|
nodes.forEach(function (d) {
|
||||||
var p = map.project([d.node.nodeinfo.location.latitude, d.node.nodeinfo.location.longitude]);
|
var p = map.project([d.node.location.latitude, d.node.location.longitude]);
|
||||||
|
|
||||||
p.x -= s.x;
|
p.x -= s.x;
|
||||||
p.y -= s.y;
|
p.y -= s.y;
|
||||||
|
|
||||||
helper.positionClients(ctx, p, d.startAngle, d.node.statistics.clients, startDistance);
|
helper.positionClients(ctx, p, d.startAngle, d.node, startDistance);
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.fillStyle = 'rgba(220, 0, 103, 0.7)';
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
return tile;
|
return tile;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
define(['leaflet', 'rbush', 'helper', 'moment'],
|
define(['leaflet', 'rbush', 'helper', 'moment'],
|
||||||
function (L, rbush, helper, moment) {
|
function (L, RBush, helper, moment) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var groupOnline;
|
var groupOnline;
|
||||||
@ -35,14 +35,14 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
|
|||||||
return function (d) {
|
return function (d) {
|
||||||
var font = fontSize + 'px ' + bodyStyle.fontFamily;
|
var font = fontSize + 'px ' + bodyStyle.fontFamily;
|
||||||
return {
|
return {
|
||||||
position: L.latLng(d.nodeinfo.location.latitude, d.nodeinfo.location.longitude),
|
position: L.latLng(d.location.latitude, d.location.longitude),
|
||||||
label: d.nodeinfo.hostname,
|
label: d.hostname,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
fillStyle: fillStyle,
|
fillStyle: fillStyle,
|
||||||
height: fontSize * 1.2,
|
height: fontSize * 1.2,
|
||||||
font: font,
|
font: font,
|
||||||
stroke: stroke,
|
stroke: stroke,
|
||||||
width: measureText(font, d.nodeinfo.hostname).width
|
width: measureText(font, d.hostname).width
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -76,33 +76,33 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
|
|||||||
return { minX: x, minY: y, maxX: x + width, maxY: y + height };
|
return { minX: x, minY: y, maxX: x + width, maxY: y + height };
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkMarker(dict, iconFunc, router) {
|
function mkMarker(dict, iconFunc) {
|
||||||
return function (d) {
|
return function (d) {
|
||||||
var m = L.circleMarker([d.nodeinfo.location.latitude, d.nodeinfo.location.longitude], iconFunc(d));
|
var m = L.circleMarker([d.location.latitude, d.location.longitude], iconFunc(d));
|
||||||
|
|
||||||
m.resetStyle = function resetStyle() {
|
m.resetStyle = function resetStyle() {
|
||||||
m.setStyle(iconFunc(d));
|
m.setStyle(iconFunc(d));
|
||||||
};
|
};
|
||||||
|
|
||||||
m.on('click', function () {
|
m.on('click', function () {
|
||||||
router.fullUrl({ node: d.nodeinfo.node_id });
|
router.fullUrl({ node: d.node_id });
|
||||||
});
|
});
|
||||||
m.bindTooltip(d.nodeinfo.hostname);
|
m.bindTooltip(helper.escape(d.hostname));
|
||||||
|
|
||||||
dict[d.nodeinfo.node_id] = m;
|
dict[d.node_id] = m;
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLinksToMap(dict, linkScale, graph, router) {
|
function addLinksToMap(dict, linkScale, graph) {
|
||||||
graph = graph.filter(function (d) {
|
graph = graph.filter(function (d) {
|
||||||
return 'distance' in d && !d.vpn;
|
return 'distance' in d && d.type.indexOf('vpn') !== 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
return graph.map(function (d) {
|
return graph.map(function (d) {
|
||||||
var opts = {
|
var opts = {
|
||||||
color: linkScale(1 / d.tq),
|
color: linkScale((d.source_tq + d.target_tq) / 2),
|
||||||
weight: 4,
|
weight: 4,
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
dashArray: 'none'
|
dashArray: 'none'
|
||||||
@ -114,7 +114,9 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
|
|||||||
line.setStyle(opts);
|
line.setStyle(opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
line.bindTooltip(d.source.node.nodeinfo.hostname + ' – ' + d.target.node.nodeinfo.hostname + '<br><strong>' + helper.showDistance(d) + ' / ' + helper.showTq(d) + '</strong>');
|
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.on('click', function () {
|
line.on('click', function () {
|
||||||
router.fullUrl({ link: d.id });
|
router.fullUrl({ link: d.id });
|
||||||
});
|
});
|
||||||
@ -125,7 +127,7 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIcon(config, color) {
|
function getIcon(color) {
|
||||||
return Object.assign({}, config.icon.base, config.icon[color]);
|
return Object.assign({}, config.icon.base, config.icon[color]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,12 +138,12 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
|
|||||||
this.prepareLabels();
|
this.prepareLabels();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setData: function (data, map, nodeDict, linkDict, linkScale, router, config) {
|
setData: function (data, map, nodeDict, linkDict, linkScale) {
|
||||||
var iconOnline = getIcon(config, 'online');
|
var iconOnline = getIcon('online');
|
||||||
var iconOffline = getIcon(config, 'offline');
|
var iconOffline = getIcon('offline');
|
||||||
var iconLost = getIcon(config, 'lost');
|
var iconLost = getIcon('lost');
|
||||||
var iconAlert = getIcon(config, 'alert');
|
var iconAlert = getIcon('alert');
|
||||||
var iconNew = getIcon(config, 'new');
|
var iconNew = getIcon('new');
|
||||||
// Check if init or data is already set
|
// Check if init or data is already set
|
||||||
if (groupLines) {
|
if (groupLines) {
|
||||||
groupOffline.clearLayers();
|
groupOffline.clearLayers();
|
||||||
@ -151,38 +153,36 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
|
|||||||
groupLines.clearLayers();
|
groupLines.clearLayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router);
|
var lines = addLinksToMap(linkDict, linkScale, data.links);
|
||||||
groupLines = L.featureGroup(lines).addTo(map);
|
groupLines = L.featureGroup(lines).addTo(map);
|
||||||
|
|
||||||
var nodesOnline = helper.subtract(data.nodes.all.filter(helper.online), data.nodes.new);
|
var nodesOnline = helper.subtract(data.nodes.online, data.nodes.new).filter(helper.hasLocation);
|
||||||
var nodesOffline = helper.subtract(data.nodes.all.filter(helper.offline), data.nodes.lost);
|
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 markersOnline = nodesOnline.filter(helper.hasLocation)
|
var markersOnline = nodesOnline.map(mkMarker(nodeDict, function () {
|
||||||
.map(mkMarker(nodeDict, function () {
|
|
||||||
return iconOnline;
|
return iconOnline;
|
||||||
}, router));
|
}));
|
||||||
|
|
||||||
var markersOffline = nodesOffline.filter(helper.hasLocation)
|
var markersOffline = nodesOffline.map(mkMarker(nodeDict, function () {
|
||||||
.map(mkMarker(nodeDict, function () {
|
|
||||||
return iconOffline;
|
return iconOffline;
|
||||||
}, router));
|
}));
|
||||||
|
|
||||||
var markersNew = data.nodes.new.filter(helper.hasLocation)
|
var markersNew = nodesNew.map(mkMarker(nodeDict, function () {
|
||||||
.map(mkMarker(nodeDict, function () {
|
|
||||||
return iconNew;
|
return iconNew;
|
||||||
}, router));
|
}));
|
||||||
|
|
||||||
var markersLost = data.nodes.lost.filter(helper.hasLocation)
|
var markersLost = nodesLost.map(mkMarker(nodeDict, function (d) {
|
||||||
.map(mkMarker(nodeDict, function (d) {
|
var age = moment(data.now).diff(d.lastseen, 'days', true);
|
||||||
if (d.lastseen.isAfter(moment(data.now).subtract(config.maxAgeAlert, 'days'))) {
|
if (age <= config.maxAgeAlert) {
|
||||||
return iconAlert;
|
return iconAlert;
|
||||||
}
|
}
|
||||||
|
if (age <= config.maxAge) {
|
||||||
if (d.lastseen.isAfter(moment(data.now).subtract(config.maxAge, 'days'))) {
|
|
||||||
return iconLost;
|
return iconLost;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, router));
|
}));
|
||||||
|
|
||||||
groupOffline = L.featureGroup(markersOffline).addTo(map);
|
groupOffline = L.featureGroup(markersOffline).addTo(map);
|
||||||
groupLost = L.featureGroup(markersLost).addTo(map);
|
groupLost = L.featureGroup(markersLost).addTo(map);
|
||||||
@ -190,10 +190,10 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
|
|||||||
groupNew = L.featureGroup(markersNew).addTo(map);
|
groupNew = L.featureGroup(markersNew).addTo(map);
|
||||||
|
|
||||||
this.data = {
|
this.data = {
|
||||||
online: nodesOnline.filter(helper.hasLocation),
|
online: nodesOnline,
|
||||||
offline: nodesOffline.filter(helper.hasLocation),
|
offline: nodesOffline,
|
||||||
new: data.nodes.new.filter(helper.hasLocation),
|
new: nodesNew,
|
||||||
lost: data.nodes.lost.filter(helper.hasLocation)
|
lost: nodesLost
|
||||||
};
|
};
|
||||||
this.updateLayer();
|
this.updateLayer();
|
||||||
},
|
},
|
||||||
@ -214,9 +214,9 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
|
|||||||
// - color (string)
|
// - color (string)
|
||||||
|
|
||||||
var labelsOnline = d.online.map(prepareLabel(null, 11, 8, true));
|
var labelsOnline = d.online.map(prepareLabel(null, 11, 8, true));
|
||||||
var labelsOffline = d.offline.map(prepareLabel('rgba(212, 62, 42, 0.9)', 9, 5, false));
|
var labelsOffline = d.offline.map(prepareLabel(config.icon.offline.color, 9, 5, false));
|
||||||
var labelsNew = d.new.map(prepareLabel('rgba(48, 99, 20, 0.9)', 11, 8, true));
|
var labelsNew = d.new.map(prepareLabel(config.map.labelNewColor, 11, 8, true));
|
||||||
var labelsLost = d.lost.map(prepareLabel('rgba(212, 62, 42, 0.9)', 11, 8, true));
|
var labelsLost = d.lost.map(prepareLabel(config.icon.lost.color, 11, 8, true));
|
||||||
|
|
||||||
var labels = []
|
var labels = []
|
||||||
.concat(labelsNew)
|
.concat(labelsNew)
|
||||||
@ -239,7 +239,7 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (var z = minZoom; z <= maxZoom; z++) {
|
for (var z = minZoom; z <= maxZoom; z++) {
|
||||||
trees[z] = rbush(9);
|
trees[z] = new RBush(9);
|
||||||
trees[z].load(labels.map(nodeToRect(z)));
|
trees[z].load(labels.map(nodeToRect(z)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,7 +291,7 @@ define(['leaflet', 'rbush', 'helper', 'moment'],
|
|||||||
}).sort().reverse()[0];
|
}).sort().reverse()[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.labels = rbush(9);
|
this.labels = new RBush(9);
|
||||||
this.labels.load(labels.map(mapRTree));
|
this.labels.load(labels.map(mapRTree));
|
||||||
|
|
||||||
this.redraw();
|
this.redraw();
|
||||||
|
@ -2,39 +2,10 @@ define(['leaflet'], function (L) {
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
return L.CircleMarker.extend({
|
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) {
|
initialize: function (latlng) {
|
||||||
this.accuracyCircle = L.circle(latlng, 0, this.accuracyCircle);
|
this.accuracyCircle = L.circle(latlng, 0, config.locate.accuracyCircle);
|
||||||
this.outerCircle = L.circleMarker(latlng, this.outerCircle);
|
this.outerCircle = L.circleMarker(latlng, config.locate.outerCircle);
|
||||||
L.CircleMarker.prototype.initialize.call(this, latlng, this.innerCircle);
|
L.CircleMarker.prototype.initialize.call(this, latlng, config.locate.innerCircle);
|
||||||
|
|
||||||
this.on('remove', function () {
|
this.on('remove', function () {
|
||||||
this._map.removeLayer(this.accuracyCircle);
|
this._map.removeLayer(this.accuracyCircle);
|
||||||
|
@ -2,28 +2,18 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
|
|||||||
'use strict';
|
'use strict';
|
||||||
V = V.default;
|
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) {
|
function showUptime(uptime) {
|
||||||
var s = '';
|
// 1000ms are 1 second and 60 second are 1min: 60 * 1000 = 60000
|
||||||
uptime /= 3600;
|
var s = uptime / 60000;
|
||||||
|
if (Math.abs(s) < 60) {
|
||||||
if (uptime !== undefined) {
|
return Math.round(s) + ' m';
|
||||||
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 s;
|
return Math.round(s) + ' d';
|
||||||
}
|
}
|
||||||
|
|
||||||
var headings = [{
|
var headings = [{
|
||||||
@ -31,7 +21,7 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
|
|||||||
}, {
|
}, {
|
||||||
name: 'node.nodes',
|
name: 'node.nodes',
|
||||||
sort: function (a, b) {
|
sort: function (a, b) {
|
||||||
return a.nodeinfo.hostname.localeCompare(b.nodeinfo.hostname);
|
return a.hostname.localeCompare(b.hostname);
|
||||||
},
|
},
|
||||||
reverse: false
|
reverse: false
|
||||||
}, {
|
}, {
|
||||||
@ -45,47 +35,43 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
|
|||||||
name: 'node.links',
|
name: 'node.links',
|
||||||
class: 'ion-share-alt',
|
class: 'ion-share-alt',
|
||||||
sort: function (a, b) {
|
sort: function (a, b) {
|
||||||
return a.meshlinks - b.meshlinks;
|
return a.neighbours.length - b.neighbours.length;
|
||||||
},
|
},
|
||||||
reverse: true
|
reverse: true
|
||||||
}, {
|
}, {
|
||||||
name: 'node.clients',
|
name: 'node.clients',
|
||||||
class: 'ion-people',
|
class: 'ion-people',
|
||||||
sort: function (a, b) {
|
sort: function (a, b) {
|
||||||
return ('clients' in a.statistics ? a.statistics.clients : -1) -
|
return a.clients - b.clients;
|
||||||
('clients' in b.statistics ? b.statistics.clients : -1);
|
|
||||||
},
|
},
|
||||||
reverse: true
|
reverse: true
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return function (router) {
|
return function () {
|
||||||
function renderRow(d) {
|
function renderRow(d) {
|
||||||
var td0Content = [];
|
var td0Content = '';
|
||||||
var td1Content = [];
|
if (helper.hasLocation(d)) {
|
||||||
var aClass = ['hostname', d.flags.online ? 'online' : 'offline'];
|
td0Content = V.h('span', { props: { className: 'icon ion-location', title: _.t('location.location') } });
|
||||||
|
}
|
||||||
|
|
||||||
td1Content.push(V.h('a', {
|
var td1Content = V.h('a', {
|
||||||
props: {
|
props: {
|
||||||
className: aClass.join(' '),
|
className: ['hostname', d.is_online ? 'online' : 'offline'].join(' '),
|
||||||
href: router.generateLink({ node: d.nodeinfo.node_id })
|
href: router.generateLink({ node: d.node_id })
|
||||||
}, on: {
|
}, on: {
|
||||||
click: function (e) {
|
click: function (e) {
|
||||||
router.fullUrl({ node: d.nodeinfo.node_id }, e);
|
router.fullUrl({ node: d.node_id }, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, d.nodeinfo.hostname));
|
}, d.hostname);
|
||||||
|
|
||||||
if (helper.hasLocation(d)) {
|
return V.h('tr', [
|
||||||
td0Content.push(V.h('span', { props: { className: 'icon ion-location' } }));
|
V.h('td', td0Content),
|
||||||
}
|
V.h('td', td1Content),
|
||||||
|
V.h('td', showUptime(d.uptime)),
|
||||||
var td0 = V.h('td', td0Content);
|
V.h('td', d.neighbours.length),
|
||||||
var td1 = V.h('td', td1Content);
|
V.h('td', d.clients)
|
||||||
var td2 = V.h('td', showUptime(d.uptime));
|
]);
|
||||||
var td3 = V.h('td', d.meshlinks.toString());
|
|
||||||
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);
|
var table = new SortTable(headings, 1, renderRow);
|
||||||
@ -101,8 +87,11 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
|
|||||||
this.setData = function setData(d) {
|
this.setData = function setData(d) {
|
||||||
var data = d.nodes.all.map(function (e) {
|
var data = d.nodes.all.map(function (e) {
|
||||||
var n = Object.create(e);
|
var n = Object.create(e);
|
||||||
n.uptime = getUptime(d.now, e);
|
if (e.is_online) {
|
||||||
n.meshlinks = e.meshlinks || 0;
|
n.uptime = d.now - new Date(e.uptime).getTime();
|
||||||
|
} else {
|
||||||
|
n.uptime = e.lastseen - d.now;
|
||||||
|
}
|
||||||
return n;
|
return n;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
define(['d3-interpolate', 'snabbdom', 'filters/genericnode', 'helper'],
|
define(['d3-interpolate', 'snabbdom', 'utils/version', 'filters/genericnode', 'helper'],
|
||||||
function (d3Interpolate, V, Filter, helper) {
|
function (d3Interpolate, V, versionCompare, Filter, helper) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
return function (config, filterManager) {
|
|
||||||
var self = this;
|
|
||||||
var scale = d3Interpolate.interpolate('#770038', '#dc0067');
|
|
||||||
V = V.default;
|
V = V.default;
|
||||||
|
|
||||||
|
return function (filterManager) {
|
||||||
|
var self = this;
|
||||||
|
var scale = d3Interpolate.interpolate(config.forceGraph.tqFrom, config.forceGraph.tqTo);
|
||||||
|
var time;
|
||||||
|
|
||||||
var statusTable;
|
var statusTable;
|
||||||
var fwTable;
|
var fwTable;
|
||||||
var hwTable;
|
var hwTable;
|
||||||
var geoTable;
|
var geoTable;
|
||||||
var autoTable;
|
var autoTable;
|
||||||
var siteTable;
|
var gatewayTable;
|
||||||
|
var gateway6Table;
|
||||||
function showStatGlobal(o) {
|
var domainTable;
|
||||||
return helper.showStat(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
function count(nodes, key, f) {
|
function count(nodes, key, f) {
|
||||||
var dict = {};
|
var dict = {};
|
||||||
@ -61,14 +60,13 @@ define(['d3-interpolate', 'snabbdom', 'filters/genericnode', 'helper'],
|
|||||||
|
|
||||||
var filter = new Filter(_.t(name), d[2], d[0], d[3]);
|
var filter = new Filter(_.t(name), d[2], d[0], d[3]);
|
||||||
|
|
||||||
var a = V.h('a', { props: { href: '#' }, on: { click: addFilter(filter) } }, d[0]);
|
var a = V.h('a', { on: { click: addFilter(filter) } }, d[0]);
|
||||||
|
|
||||||
var th = V.h('th', a);
|
var th = V.h('th', a);
|
||||||
var td = V.h('td', V.h('span', {
|
var td = V.h('td', V.h('span', {
|
||||||
style: {
|
style: {
|
||||||
width: Math.round(v * 100) + '%',
|
width: 'calc(25px + ' + Math.round(v * 90) + '%)',
|
||||||
backgroundColor: scale(v),
|
backgroundColor: scale(v)
|
||||||
color: 'white'
|
|
||||||
}
|
}
|
||||||
}, d[1].toFixed(0)));
|
}, d[1].toFixed(0)));
|
||||||
|
|
||||||
@ -79,56 +77,53 @@ define(['d3-interpolate', 'snabbdom', 'filters/genericnode', 'helper'],
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.setData = function setData(data) {
|
self.setData = function setData(data) {
|
||||||
var onlineNodes = data.nodes.all.filter(helper.online);
|
var onlineNodes = data.nodes.online;
|
||||||
var nodes = onlineNodes.concat(data.nodes.lost);
|
var nodes = onlineNodes.concat(data.nodes.lost);
|
||||||
var nodeDict = {};
|
time = data.timestamp;
|
||||||
|
|
||||||
data.nodes.all.forEach(function (d) {
|
function hostnameOfNodeID(nodeid) {
|
||||||
nodeDict[d.nodeinfo.node_id] = d;
|
var gateway = data.nodeDict[nodeid];
|
||||||
});
|
if (gateway) {
|
||||||
|
return gateway.hostname;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var statusDict = count(nodes, ['flags', 'online'], function (d) {
|
var gatewayDict = count(nodes, ['gateway'], hostnameOfNodeID);
|
||||||
|
var gateway6Dict = count(nodes, ['gateway6'], hostnameOfNodeID);
|
||||||
|
|
||||||
|
var statusDict = count(nodes, ['is_online'], function (d) {
|
||||||
return d ? 'online' : 'offline';
|
return d ? 'online' : 'offline';
|
||||||
});
|
});
|
||||||
var fwDict = count(nodes, ['nodeinfo', 'software', 'firmware', 'release']);
|
var fwDict = count(nodes, ['firmware', 'release']);
|
||||||
var hwDict = count(nodes, ['nodeinfo', 'hardware', 'model']);
|
var hwDict = count(nodes, ['model']);
|
||||||
var geoDict = count(nodes, ['nodeinfo', 'location'], function (d) {
|
var geoDict = count(nodes, ['location'], function (d) {
|
||||||
return d && d.longitude && d.latitude ? _.t('yes') : _.t('no');
|
return d && d.longitude && d.latitude ? _.t('yes') : _.t('no');
|
||||||
});
|
});
|
||||||
|
|
||||||
var autoDict = count(nodes, ['nodeinfo', 'software', 'autoupdater'], function (d) {
|
var autoDict = count(nodes, ['autoupdater'], function (d) {
|
||||||
if (d === null) {
|
if (d.enabled) {
|
||||||
return null;
|
|
||||||
} else if (d.enabled) {
|
|
||||||
return d.branch;
|
return d.branch;
|
||||||
}
|
}
|
||||||
return _.t('node.deactivated');
|
return _.t('node.deactivated');
|
||||||
});
|
});
|
||||||
|
|
||||||
var siteDict = count(nodes, ['nodeinfo', 'system', 'site_code'], function (d) {
|
var domainDict = count(nodes, ['domain'], function (d) {
|
||||||
var rt = d;
|
if (config.domainNames) {
|
||||||
if (config.siteNames) {
|
config.domainNames.some(function (t) {
|
||||||
config.siteNames.forEach(function (t) {
|
if (d === t.domain) {
|
||||||
if (d === t.site) {
|
d = t.name;
|
||||||
rt = t.name;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return rt;
|
return d;
|
||||||
});
|
});
|
||||||
|
|
||||||
statusTable = fillTable('node.status', statusTable, statusDict.sort(function (a, b) {
|
statusTable = fillTable('node.status', statusTable, statusDict.sort(function (a, b) {
|
||||||
return b[1] - a[1];
|
return b[1] - a[1];
|
||||||
}));
|
}));
|
||||||
fwTable = fillTable('node.firmware', fwTable, fwDict.sort(function (a, b) {
|
fwTable = fillTable('node.firmware', fwTable, fwDict.sort(versionCompare));
|
||||||
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) {
|
hwTable = fillTable('node.hardware', hwTable, hwDict.sort(function (a, b) {
|
||||||
return b[1] - a[1];
|
return b[1] - a[1];
|
||||||
}));
|
}));
|
||||||
@ -138,39 +133,54 @@ define(['d3-interpolate', 'snabbdom', 'filters/genericnode', 'helper'],
|
|||||||
autoTable = fillTable('node.update', autoTable, autoDict.sort(function (a, b) {
|
autoTable = fillTable('node.update', autoTable, autoDict.sort(function (a, b) {
|
||||||
return b[1] - a[1];
|
return b[1] - a[1];
|
||||||
}));
|
}));
|
||||||
siteTable = fillTable('node.site', siteTable, siteDict.sort(function (a, b) {
|
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) {
|
||||||
return b[1] - a[1];
|
return b[1] - a[1];
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
self.render = function render(el) {
|
self.render = function render(el) {
|
||||||
var h2;
|
|
||||||
self.renderSingle(el, 'node.status', statusTable);
|
self.renderSingle(el, 'node.status', statusTable);
|
||||||
self.renderSingle(el, 'node.firmware', fwTable);
|
self.renderSingle(el, 'node.firmware', fwTable);
|
||||||
self.renderSingle(el, 'node.hardware', hwTable);
|
self.renderSingle(el, 'node.hardware', hwTable);
|
||||||
self.renderSingle(el, 'node.visible', geoTable);
|
self.renderSingle(el, 'node.visible', geoTable);
|
||||||
self.renderSingle(el, 'node.update', autoTable);
|
self.renderSingle(el, 'node.update', autoTable);
|
||||||
self.renderSingle(el, 'node.site', siteTable);
|
self.renderSingle(el, 'node.selectedGatewayIPv4', gatewayTable);
|
||||||
|
self.renderSingle(el, 'node.selectedGatewayIPv6', gateway6Table);
|
||||||
|
self.renderSingle(el, 'node.domain', domainTable);
|
||||||
|
|
||||||
if (config.globalInfos) {
|
if (config.globalInfos) {
|
||||||
|
var images = document.createElement('div');
|
||||||
|
el.appendChild(images);
|
||||||
|
var img = [];
|
||||||
|
var subst = {
|
||||||
|
'{TIME}': time,
|
||||||
|
'{LOCALE}': _.locale()
|
||||||
|
};
|
||||||
config.globalInfos.forEach(function (globalInfo) {
|
config.globalInfos.forEach(function (globalInfo) {
|
||||||
h2 = document.createElement('h2');
|
img.push(V.h('h2', globalInfo.name));
|
||||||
h2.textContent = globalInfo.name;
|
img.push(helper.showStat(V, globalInfo, subst));
|
||||||
el.appendChild(h2);
|
|
||||||
el.appendChild(showStatGlobal(globalInfo));
|
|
||||||
});
|
});
|
||||||
|
V.patch(images, V.h('div', img));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.renderSingle = function renderSingle(el, heading, table) {
|
self.renderSingle = function renderSingle(el, heading, table) {
|
||||||
|
if (table.children.length > 0) {
|
||||||
var h2 = document.createElement('h2');
|
var h2 = document.createElement('h2');
|
||||||
h2.classList.add('proportion-header');
|
h2.classList.add('proportion-header');
|
||||||
h2.textContent = _.t(heading);
|
h2.textContent = _.t(heading);
|
||||||
h2.onclick = function onclick() {
|
h2.onclick = function onclick() {
|
||||||
table.classList.toggle('hide');
|
table.elm.classList.toggle('hide');
|
||||||
};
|
};
|
||||||
el.appendChild(h2);
|
el.appendChild(h2);
|
||||||
el.appendChild(table.elm);
|
el.appendChild(table.elm);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return self;
|
return self;
|
||||||
};
|
};
|
||||||
|
@ -15,10 +15,13 @@ define(function () {
|
|||||||
el.appendChild(sidebar);
|
el.appendChild(sidebar);
|
||||||
|
|
||||||
var button = document.createElement('button');
|
var button = document.createElement('button');
|
||||||
|
var visibility = new CustomEvent('visibility');
|
||||||
sidebar.appendChild(button);
|
sidebar.appendChild(button);
|
||||||
|
|
||||||
button.classList.add('sidebarhandle', 'shadow');
|
button.classList.add('sidebarhandle');
|
||||||
|
button.setAttribute('aria-label', _.t('sidebar.toggle'));
|
||||||
button.onclick = function onclick() {
|
button.onclick = function onclick() {
|
||||||
|
button.dispatchEvent(visibility);
|
||||||
sidebar.classList.toggle('hidden');
|
sidebar.classList.toggle('hidden');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,7 +30,7 @@ define(function () {
|
|||||||
sidebar.appendChild(container);
|
sidebar.appendChild(container);
|
||||||
|
|
||||||
self.getWidth = function getWidth() {
|
self.getWidth = function getWidth() {
|
||||||
if (gridBreakpoints.lg[0] > window.innerWidth) {
|
if (gridBreakpoints.lg[0] > window.innerWidth || sidebar.classList.contains('hidden')) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (gridBreakpoints.xl[0] > window.innerWidth) {
|
} else if (gridBreakpoints.xl[0] > window.innerWidth) {
|
||||||
return gridBreakpoints.lg[1];
|
return gridBreakpoints.lg[1];
|
||||||
@ -54,6 +57,7 @@ define(function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.container = sidebar;
|
self.container = sidebar;
|
||||||
|
self.button = button;
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ define(['moment', 'snabbdom', 'helper'], function (moment, V, helper) {
|
|||||||
'use strict';
|
'use strict';
|
||||||
V = V.default;
|
V = V.default;
|
||||||
|
|
||||||
return function (nodes, field, router, title) {
|
return function (nodes, field, title) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var el;
|
var el;
|
||||||
var tbody;
|
var tbody;
|
||||||
@ -34,32 +34,27 @@ define(['moment', 'snabbdom', 'helper'], function (moment, V, helper) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var items = list.map(function (d) {
|
var items = list.map(function (d) {
|
||||||
var time = moment(d[field]).from(data.now);
|
var td0Content = '';
|
||||||
var td0Content = [];
|
if (helper.hasLocation(d)) {
|
||||||
var td1Content = [];
|
td0Content = V.h('span', { props: { className: 'icon ion-location', title: _.t('location.location') } });
|
||||||
|
}
|
||||||
|
|
||||||
var aClass = ['hostname', d.flags.online ? 'online' : 'offline'];
|
var td1Content = V.h('a', {
|
||||||
|
|
||||||
td1Content.push(V.h('a', {
|
|
||||||
props: {
|
props: {
|
||||||
className: aClass.join(' '),
|
className: ['hostname', d.is_online ? 'online' : 'offline'].join(' '),
|
||||||
href: router.generateLink({ node: d.nodeinfo.node_id })
|
href: router.generateLink({ node: d.node_id })
|
||||||
}, on: {
|
}, on: {
|
||||||
click: function (e) {
|
click: function (e) {
|
||||||
router.fullUrl({ node: d.nodeinfo.node_id }, e);
|
router.fullUrl({ node: d.node_id }, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, d.nodeinfo.hostname));
|
}, d.hostname);
|
||||||
|
|
||||||
if (helper.hasLocation(d)) {
|
return V.h('tr', [
|
||||||
td0Content.push(V.h('span', { props: { className: 'icon ion-location' } }));
|
V.h('td', td0Content),
|
||||||
}
|
V.h('td', td1Content),
|
||||||
|
V.h('td', moment(d[field]).from(data.now))
|
||||||
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);
|
var tbodyNew = V.h('tbody', items);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
define(function () {
|
define(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
return function (config) {
|
return function () {
|
||||||
function setTitle(d) {
|
function setTitle(d) {
|
||||||
var title = [config.siteName];
|
var title = [config.siteName];
|
||||||
|
|
||||||
@ -17,11 +17,11 @@ define(function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.gotoNode = function gotoNode(d) {
|
this.gotoNode = function gotoNode(d) {
|
||||||
setTitle(d.nodeinfo.hostname);
|
setTitle(d.hostname);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.gotoLink = function gotoLink(d) {
|
this.gotoLink = function gotoLink(d) {
|
||||||
setTitle((d.source.node ? d.source.node.nodeinfo.hostname : d.source.id) + ' \u21D4 ' + d.target.node.nodeinfo.hostname);
|
setTitle(d[0].source.hostname + ' \u21D4 ' + d[0].target.hostname);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.gotoLocation = function gotoLocation() {
|
this.gotoLocation = function gotoLocation() {
|
||||||
|
@ -27,9 +27,9 @@ define({
|
|||||||
},
|
},
|
||||||
|
|
||||||
sortByKey: function sortByKey(key, d) {
|
sortByKey: function sortByKey(key, d) {
|
||||||
return d.slice().sort(function (a, b) {
|
return d.sort(function (a, b) {
|
||||||
return a[key] - b[key];
|
return b[key] - a[key];
|
||||||
}).reverse();
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
limit: function limit(key, m, d) {
|
limit: function limit(key, m, d) {
|
||||||
@ -48,10 +48,6 @@ define({
|
|||||||
return 1;
|
return 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
trueDefault: function trueDefault(d) {
|
|
||||||
return d === undefined ? true : d;
|
|
||||||
},
|
|
||||||
|
|
||||||
dictGet: function dictGet(dict, key) {
|
dictGet: function dictGet(dict, key) {
|
||||||
var k = key.shift();
|
var k = key.shift();
|
||||||
|
|
||||||
@ -76,31 +72,21 @@ define({
|
|||||||
return s;
|
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) {
|
hasLocation: function hasLocation(d) {
|
||||||
return 'location' in d.nodeinfo &&
|
return 'location' in d &&
|
||||||
Math.abs(d.nodeinfo.location.latitude) < 90 &&
|
Math.abs(d.location.latitude) < 90 &&
|
||||||
Math.abs(d.nodeinfo.location.longitude) < 180;
|
Math.abs(d.location.longitude) < 180;
|
||||||
},
|
},
|
||||||
|
|
||||||
subtract: function subtract(a, b) {
|
subtract: function subtract(a, b) {
|
||||||
var ids = {};
|
var ids = {};
|
||||||
|
|
||||||
b.forEach(function (d) {
|
b.forEach(function (d) {
|
||||||
ids[d.nodeinfo.node_id] = true;
|
ids[d.node_id] = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
return a.filter(function (d) {
|
return a.filter(function (d) {
|
||||||
return !(d.nodeinfo.node_id in ids);
|
return !ids[d.node_id];
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -115,59 +101,35 @@ define({
|
|||||||
},
|
},
|
||||||
|
|
||||||
showTq: function showTq(d) {
|
showTq: function showTq(d) {
|
||||||
return (1 / d.tq * 100).toFixed(0) + '%';
|
return (d * 100).toFixed(0) + '%';
|
||||||
},
|
},
|
||||||
|
|
||||||
attributeEntry: function attributeEntry(el, label, value) {
|
attributeEntry: function attributeEntry(V, children, label, value) {
|
||||||
if (value === null || value === undefined) {
|
if (value !== undefined) {
|
||||||
return '';
|
if (typeof value !== 'object') {
|
||||||
|
value = V.h('td', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var tr = document.createElement('tr');
|
children.push(V.h('tr', [
|
||||||
var th = document.createElement('th');
|
V.h('th', _.t(label)),
|
||||||
th.textContent = _.t(label);
|
value
|
||||||
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) {
|
||||||
showStat: function showStat(o, subst) {
|
var content = V.h('img', { attrs: { src: require('helper').listReplace(o.image, 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) {
|
if (o.href) {
|
||||||
var link = document.createElement('a');
|
return V.h('p', V.h('a', {
|
||||||
link.target = '_blank';
|
attrs:
|
||||||
link.href = require('helper').listReplace(o.href, subst);
|
{
|
||||||
link.appendChild(content);
|
href: require('helper').listReplace(o.href, subst),
|
||||||
|
target: '_blank',
|
||||||
if (o.title) {
|
title: require('helper').listReplace(o.title, subst)
|
||||||
link.title = require('helper').listReplace(o.title, subst);
|
|
||||||
}
|
}
|
||||||
|
}, content));
|
||||||
p.appendChild(link);
|
|
||||||
} else {
|
|
||||||
p.appendChild(content);
|
|
||||||
}
|
}
|
||||||
|
return V.h('p', content);
|
||||||
return p;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getTileBBox: function getTileBBox(s, map, tileSize, margin) {
|
getTileBBox: function getTileBBox(s, map, tileSize, margin) {
|
||||||
@ -176,20 +138,35 @@ define({
|
|||||||
|
|
||||||
return { minX: br.lat, minY: tl.lng, maxX: tl.lat, maxY: br.lng };
|
return { minX: br.lat, minY: tl.lng, maxX: tl.lat, maxY: br.lng };
|
||||||
},
|
},
|
||||||
positionClients: function positionClients(ctx, p, startAngle, clients, startDistance) {
|
positionClients: function positionClients(ctx, p, startAngle, node, startDistance) {
|
||||||
if (clients === 0) {
|
if (node.clients === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var radius = 3;
|
var radius = 3;
|
||||||
var a = 1.2;
|
var a = 1.2;
|
||||||
|
var mode = 0;
|
||||||
|
|
||||||
for (var orbit = 0, i = 0; i < clients; orbit++) {
|
ctx.beginPath();
|
||||||
|
ctx.fillStyle = config.client.wifi24;
|
||||||
|
|
||||||
|
for (var orbit = 0, i = 0; i < node.clients; orbit++) {
|
||||||
var distance = startDistance + orbit * 2 * radius * a;
|
var distance = startDistance + orbit * 2 * radius * a;
|
||||||
var n = Math.floor((Math.PI * distance) / (a * radius));
|
var n = Math.floor((Math.PI * distance) / (a * radius));
|
||||||
var delta = clients - i;
|
var delta = node.clients - i;
|
||||||
|
|
||||||
for (var j = 0; j < Math.min(delta, n); i++, j++) {
|
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 angle = 2 * Math.PI / n * j;
|
||||||
var x = p.x + distance * Math.cos(angle + startAngle);
|
var x = p.x + distance * Math.cos(angle + startAngle);
|
||||||
var y = p.y + distance * Math.sin(angle + startAngle);
|
var y = p.y + distance * Math.sin(angle + startAngle);
|
||||||
@ -198,5 +175,32 @@ define({
|
|||||||
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
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, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
define(['polyglot', 'moment', 'helper'], function (Polyglot, moment, helper) {
|
define(['polyglot', 'moment', 'helper'], function (Polyglot, moment, helper) {
|
||||||
'use strict';
|
'use strict';
|
||||||
return function (config) {
|
return function () {
|
||||||
var router;
|
var router;
|
||||||
|
|
||||||
function languageSelect(el) {
|
function languageSelect(el) {
|
||||||
var select = document.createElement('select');
|
var select = document.createElement('select');
|
||||||
select.className = 'language-switch';
|
select.className = 'language-switch';
|
||||||
|
select.setAttribute('aria-label', 'Language');
|
||||||
select.addEventListener('change', setSelectLocale);
|
select.addEventListener('change', setSelectLocale);
|
||||||
el.appendChild(select);
|
el.appendChild(select);
|
||||||
|
|
||||||
@ -20,13 +21,8 @@ define(['polyglot', 'moment', 'helper'], function (Polyglot, moment, helper) {
|
|||||||
router.fullUrl({ lang: event.target.value }, false, true);
|
router.fullUrl({ lang: event.target.value }, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLocale(lang) {
|
|
||||||
localStorage.setItem('language', getLocale(lang));
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLocale(input) {
|
function getLocale(input) {
|
||||||
var language = input || localStorage.getItem('language') || navigator.languages && navigator.languages[0] || navigator.language || navigator.userLanguage;
|
var language = input || navigator.languages && navigator.languages[0] || navigator.language;
|
||||||
var locale = config.supportedLocale[0];
|
var locale = config.supportedLocale[0];
|
||||||
config.supportedLocale.some(function (item) {
|
config.supportedLocale.some(function (item) {
|
||||||
if (language.indexOf(item) !== -1) {
|
if (language.indexOf(item) !== -1) {
|
||||||
@ -59,6 +55,7 @@ define(['polyglot', 'moment', 'helper'], function (Polyglot, moment, helper) {
|
|||||||
|
|
||||||
function init(r) {
|
function init(r) {
|
||||||
router = r;
|
router = r;
|
||||||
|
/** global: _ */
|
||||||
window._ = new Polyglot({ locale: getLocale(router.getLang()), allowMissing: true });
|
window._ = new Polyglot({ locale: getLocale(router.getLang()), allowMissing: true });
|
||||||
helper.getJSON('locale/' + _.locale() + '.json?' + config.cacheBreaker).then(setTranslation);
|
helper.getJSON('locale/' + _.locale() + '.json?' + config.cacheBreaker).then(setTranslation);
|
||||||
document.querySelector('html').setAttribute('lang', _.locale());
|
document.querySelector('html').setAttribute('lang', _.locale());
|
||||||
@ -67,7 +64,6 @@ define(['polyglot', 'moment', 'helper'], function (Polyglot, moment, helper) {
|
|||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
getLocale: getLocale,
|
getLocale: getLocale,
|
||||||
setLocale: setLocale,
|
|
||||||
languageSelect: languageSelect
|
languageSelect: languageSelect
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ define(function () {
|
|||||||
var self = {};
|
var self = {};
|
||||||
|
|
||||||
self.distance = function distance(a, b) {
|
self.distance = function distance(a, b) {
|
||||||
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2);
|
return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.distancePoint = function distancePoint(a, b) {
|
self.distancePoint = function distancePoint(a, b) {
|
||||||
|
144
lib/utils/node.js
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
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;
|
||||||
|
});
|
@ -3,7 +3,7 @@ define(['Navigo'], function (Navigo) {
|
|||||||
|
|
||||||
return function (language) {
|
return function (language) {
|
||||||
var init = false;
|
var init = false;
|
||||||
var objects = { nodes: {}, links: {} };
|
var objects = {};
|
||||||
var targets = [];
|
var targets = [];
|
||||||
var views = {};
|
var views = {};
|
||||||
var current = {};
|
var current = {};
|
||||||
@ -16,17 +16,20 @@ define(['Navigo'], function (Navigo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function gotoNode(d) {
|
function gotoNode(d) {
|
||||||
if (d.nodeId in objects.nodes) {
|
if (objects.nodeDict[d.nodeId]) {
|
||||||
targets.forEach(function (t) {
|
targets.forEach(function (t) {
|
||||||
t.gotoNode(objects.nodes[d.nodeId]);
|
t.gotoNode(objects.nodeDict[d.nodeId], objects.nodeDict);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function gotoLink(d) {
|
function gotoLink(d) {
|
||||||
if (d.linkId in objects.links) {
|
var link = objects.links.filter(function (value) {
|
||||||
|
return value.id === d.linkId;
|
||||||
|
});
|
||||||
|
if (link) {
|
||||||
targets.forEach(function (t) {
|
targets.forEach(function (t) {
|
||||||
t.gotoLink(objects.links[d.linkId]);
|
t.gotoLink(link);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,7 +54,7 @@ define(['Navigo'], function (Navigo) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (lang && lang !== state.lang && lang === language.getLocale(lang)) {
|
if (lang && lang !== state.lang && lang === language.getLocale(lang)) {
|
||||||
language.setLocale(lang);
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!init || viewValue && viewValue !== state.view) {
|
if (!init || viewValue && viewValue !== state.view) {
|
||||||
@ -79,10 +82,10 @@ define(['Navigo'], function (Navigo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var router = new Navigo(null, true);
|
var router = new Navigo(null, true, '#!');
|
||||||
|
|
||||||
router
|
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({
|
.on({
|
||||||
'*': function () {
|
'*': function () {
|
||||||
router.fullUrl();
|
router.fullUrl();
|
||||||
@ -90,7 +93,7 @@ define(['Navigo'], function (Navigo) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.generateLink = function generateLink(data, full, deep) {
|
router.generateLink = function generateLink(data, full, deep) {
|
||||||
var result = '#';
|
var result = '#!';
|
||||||
|
|
||||||
if (full) {
|
if (full) {
|
||||||
data = Object.assign({}, state, data);
|
data = Object.assign({}, state, data);
|
||||||
@ -116,7 +119,7 @@ define(['Navigo'], function (Navigo) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
router.getLang = function getLang() {
|
router.getLang = function getLang() {
|
||||||
var lang = location.hash.match(/^\/?#\/([\w]{2})\//);
|
var lang = location.hash.match(/^\/?#!?\/([\w]{2})\//);
|
||||||
if (lang) {
|
if (lang) {
|
||||||
state.lang = language.getLocale(lang[1]);
|
state.lang = language.getLocale(lang[1]);
|
||||||
return lang[1];
|
return lang[1];
|
||||||
@ -139,16 +142,7 @@ define(['Navigo'], function (Navigo) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
router.setData = function setData(data) {
|
router.setData = function setData(data) {
|
||||||
objects.nodes = {};
|
objects = data;
|
||||||
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;
|
return router;
|
||||||
|
99
lib/utils/version.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
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);
|
||||||
|
};
|
||||||
|
});
|
96
locale/cz.json
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
@ -6,6 +6,7 @@
|
|||||||
"links": "Verbindungen",
|
"links": "Verbindungen",
|
||||||
"clients": "Nutzer",
|
"clients": "Nutzer",
|
||||||
"distance": "Entfernung",
|
"distance": "Entfernung",
|
||||||
|
"connectionType": "Verbindungsart",
|
||||||
"tq": "Übertragungsqualität",
|
"tq": "Übertragungsqualität",
|
||||||
"lastOnline": "online, letzte Nachricht %{time} (%{date})",
|
"lastOnline": "online, letzte Nachricht %{time} (%{date})",
|
||||||
"lastOffline": "offline, letzte Nachricht %{time} (%{date})",
|
"lastOffline": "offline, letzte Nachricht %{time} (%{date})",
|
||||||
@ -16,17 +17,19 @@
|
|||||||
"hardware": "Geräte-Modell",
|
"hardware": "Geräte-Modell",
|
||||||
"visible": "Auf der Karte sichtbar",
|
"visible": "Auf der Karte sichtbar",
|
||||||
"update": "Auto-Update",
|
"update": "Auto-Update",
|
||||||
"site": "Site",
|
"domain": "Domain",
|
||||||
"gateway": "Gateway",
|
"gateway": "Gateway",
|
||||||
"coordinates": "Koordinaten",
|
"coordinates": "Koordinaten",
|
||||||
"contact": "Kontakt",
|
"contact": "Kontakt",
|
||||||
"primaryMac": "Primäre MAC",
|
"primaryMac": "Primäre MAC",
|
||||||
"id": "Knoten ID",
|
"id": "Knoten ID",
|
||||||
"firstSeen": "Erstmals gesehen",
|
"firstSeen": "Erstmals gesehen",
|
||||||
"systemLoad": "Load average",
|
"systemLoad": "Systemlast",
|
||||||
"ram": "Speicherauslastung",
|
"ram": "Speicherauslastung",
|
||||||
"ipAddresses": "IP Adressen",
|
"ipAddresses": "IP Adressen",
|
||||||
"selectedGateway": "Gewähltes Gateway",
|
"nexthop": "Nächster Sprung",
|
||||||
|
"selectedGatewayIPv4": "Gewähltes ipv4 Gateway",
|
||||||
|
"selectedGatewayIPv6": "Gewähltes ipv6 Gateway",
|
||||||
"link": "Verbindung |||| Verbindungen",
|
"link": "Verbindung |||| Verbindungen",
|
||||||
"node": "Knoten",
|
"node": "Knoten",
|
||||||
"new": "Neue Knoten",
|
"new": "Neue Knoten",
|
||||||
@ -44,18 +47,20 @@
|
|||||||
"clients": "mit %{smart_count} Nutzer |||| mit %{smart_count} Nutzern",
|
"clients": "mit %{smart_count} Nutzer |||| mit %{smart_count} Nutzern",
|
||||||
"gateway": "auf %{smart_count} Gateway |||| auf %{smart_count} Gateways",
|
"gateway": "auf %{smart_count} Gateway |||| auf %{smart_count} Gateways",
|
||||||
"lastUpdate": "Letzte Aktualisierung",
|
"lastUpdate": "Letzte Aktualisierung",
|
||||||
"nodeNew": "Knoten ist neu",
|
"nodeNew": "neu",
|
||||||
"nodeOnline": "Knoten ist online",
|
"nodeOnline": "online",
|
||||||
"nodeOffline": "Knoten ist offline",
|
"nodeOffline": "offline",
|
||||||
"aboutInfo": "<h2>Über Meshviewer</h2><p>Mit Doppelklick kann man in die Karte hinein zoomen und Shift+Doppelklick heraus zoomen.</p>",
|
"aboutInfo": "<h2>Über Meshviewer</h2><p>Mit Doppelklick kann man in die Karte hinein zoomen und Shift+Doppelklick heraus zoomen.</p>",
|
||||||
"actual": "Aktuell",
|
"actual": "Aktuell",
|
||||||
"stats": "Statistiken",
|
"stats": "Statistiken",
|
||||||
"about": "Über"
|
"about": "Über",
|
||||||
|
"toggle": "Seitenleiste anzeigen/ausblenden"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"switchView": "Ansicht wechseln",
|
"switchView": "Ansicht wechseln",
|
||||||
"location": "Koordinaten wählen",
|
"location": "Koordinaten wählen",
|
||||||
"tracking": "Lokalisierung"
|
"tracking": "Lokalisierung",
|
||||||
|
"fullscreen": "Vollbildmodus wechseln"
|
||||||
},
|
},
|
||||||
"momentjs": {
|
"momentjs": {
|
||||||
"calendar": {
|
"calendar": {
|
||||||
@ -85,5 +90,8 @@
|
|||||||
"yes": "ja",
|
"yes": "ja",
|
||||||
"no": "nein",
|
"no": "nein",
|
||||||
"unknown": "unbekannt",
|
"unknown": "unbekannt",
|
||||||
"none": "keine"
|
"others": "andere",
|
||||||
|
"none": "keine",
|
||||||
|
"remove": "entfernen",
|
||||||
|
"close": "schließen"
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"links": "Links",
|
"links": "Links",
|
||||||
"clients": "Clients",
|
"clients": "Clients",
|
||||||
"distance": "Distance",
|
"distance": "Distance",
|
||||||
|
"connectionType": "Connection type",
|
||||||
"tq": "Transmit quality",
|
"tq": "Transmit quality",
|
||||||
"lastOnline": "online, last message %{time} (%{date})",
|
"lastOnline": "online, last message %{time} (%{date})",
|
||||||
"lastOffline": "offline, last message %{time} (%{date})",
|
"lastOffline": "offline, last message %{time} (%{date})",
|
||||||
@ -16,7 +17,7 @@
|
|||||||
"hardware": "Hardware model",
|
"hardware": "Hardware model",
|
||||||
"visible": "Visible on the map",
|
"visible": "Visible on the map",
|
||||||
"update": "Auto update",
|
"update": "Auto update",
|
||||||
"site": "Site",
|
"domain": "Domain",
|
||||||
"gateway": "Gateway",
|
"gateway": "Gateway",
|
||||||
"coordinates": "Coordinates",
|
"coordinates": "Coordinates",
|
||||||
"contact": "Contact",
|
"contact": "Contact",
|
||||||
@ -26,7 +27,9 @@
|
|||||||
"systemLoad": "Load average",
|
"systemLoad": "Load average",
|
||||||
"ram": "Memory usage",
|
"ram": "Memory usage",
|
||||||
"ipAddresses": "IP addresses",
|
"ipAddresses": "IP addresses",
|
||||||
"selectedGateway": "Selected gateway",
|
"nexthop": "Nexthop",
|
||||||
|
"selectedGatewayIPv4": "Selected ipv4-gateway",
|
||||||
|
"selectedGatewayIPv6": "Selected ipv6-gateway",
|
||||||
"link": "Link |||| Links",
|
"link": "Link |||| Links",
|
||||||
"node": "Node |||| Nodes",
|
"node": "Node |||| Nodes",
|
||||||
"new": "New nodes",
|
"new": "New nodes",
|
||||||
@ -44,18 +47,20 @@
|
|||||||
"clients": "with %{smart_count} client |||| with %{smart_count} clients",
|
"clients": "with %{smart_count} client |||| with %{smart_count} clients",
|
||||||
"gateway": "on %{smart_count} gateway |||| on %{smart_count} gateways",
|
"gateway": "on %{smart_count} gateway |||| on %{smart_count} gateways",
|
||||||
"lastUpdate": "Last update",
|
"lastUpdate": "Last update",
|
||||||
"nodeNew": "Node is new",
|
"nodeNew": "new",
|
||||||
"nodeOnline": "Node is online",
|
"nodeOnline": "online",
|
||||||
"nodeOffline": "Node is offline",
|
"nodeOffline": "offline",
|
||||||
"aboutInfo": "<h2>About Meshviewer</h2> <p>You can zoom in with double-click and zoom out with shift+double-click</p>",
|
"aboutInfo": "<h2>About Meshviewer</h2> <p>You can zoom in with double-click and zoom out with shift+double-click</p>",
|
||||||
"actual": "Current",
|
"actual": "Current",
|
||||||
"stats": "Statistics",
|
"stats": "Statistics",
|
||||||
"about": "About"
|
"about": "About",
|
||||||
|
"toggle": "Toggle Sidebar"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"switchView": "Switch view",
|
"switchView": "Switch view",
|
||||||
"location": "Pick coordinates",
|
"location": "Pick coordinates",
|
||||||
"tracking": "Localisation"
|
"tracking": "Localisation",
|
||||||
|
"fullscreen": "Toggle fullscreen"
|
||||||
},
|
},
|
||||||
"momentjs": {
|
"momentjs": {
|
||||||
"calendar": {
|
"calendar": {
|
||||||
@ -85,5 +90,8 @@
|
|||||||
"yes": "yes",
|
"yes": "yes",
|
||||||
"no": "no",
|
"no": "no",
|
||||||
"unknown": "unknown",
|
"unknown": "unknown",
|
||||||
"none": "none"
|
"others": "other",
|
||||||
|
"none": "none",
|
||||||
|
"remove": "remove",
|
||||||
|
"close": "close"
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"links": "Connexion",
|
"links": "Connexion",
|
||||||
"clients": "Clients",
|
"clients": "Clients",
|
||||||
"distance": "Distance",
|
"distance": "Distance",
|
||||||
|
"connectionType": "Type de connexion",
|
||||||
"tq": "Qualité de transmission",
|
"tq": "Qualité de transmission",
|
||||||
"lastOnline": "en ligne, dernier message %{time} (%{date})",
|
"lastOnline": "en ligne, dernier message %{time} (%{date})",
|
||||||
"lastOffline": "hors ligne, dernier message %{time} (%{date})",
|
"lastOffline": "hors ligne, dernier message %{time} (%{date})",
|
||||||
@ -16,7 +17,7 @@
|
|||||||
"hardware": "Modèle matériel",
|
"hardware": "Modèle matériel",
|
||||||
"visible": "Visible sur la carte",
|
"visible": "Visible sur la carte",
|
||||||
"update": "Mise à jour automatique",
|
"update": "Mise à jour automatique",
|
||||||
"site": "Site",
|
"domain": "Domain",
|
||||||
"gateway": "Passerelle",
|
"gateway": "Passerelle",
|
||||||
"coordinates": "Coordonnées",
|
"coordinates": "Coordonnées",
|
||||||
"contact": "Contact",
|
"contact": "Contact",
|
||||||
@ -26,7 +27,9 @@
|
|||||||
"systemLoad": "Charge moyenne",
|
"systemLoad": "Charge moyenne",
|
||||||
"ram": "Utilisation de la mémoire",
|
"ram": "Utilisation de la mémoire",
|
||||||
"ipAddresses": "Adresse IP",
|
"ipAddresses": "Adresse IP",
|
||||||
"selectedGateway": "Passerelle sélectionné",
|
"nexthop": "Nexthop",
|
||||||
|
"selectedGatewayIPv4": "Selected ipv4-gateway",
|
||||||
|
"selectedGatewayIPv6": "Selected ipv6-gateway",
|
||||||
"link": "Connexion |||| Connexions",
|
"link": "Connexion |||| Connexions",
|
||||||
"node": "Nœud |||| Nœuds",
|
"node": "Nœud |||| Nœuds",
|
||||||
"new": "Nouveaux nœuds",
|
"new": "Nouveaux nœuds",
|
||||||
@ -50,7 +53,8 @@
|
|||||||
"aboutInfo": "<h2>Sur Meshviewer</h2> <p>Vous pouvez zoomer avec double-clic et effectuer un zoom arrière avec shift + double-clic</p>",
|
"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",
|
"actual": "Actuel",
|
||||||
"stats": "Statistiques",
|
"stats": "Statistiques",
|
||||||
"about": "À propros"
|
"about": "À propros",
|
||||||
|
"toggle": "Toggle Sidebar"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"switchView": "Basculer l’affichage",
|
"switchView": "Basculer l’affichage",
|
||||||
@ -85,5 +89,8 @@
|
|||||||
"yes": "oui",
|
"yes": "oui",
|
||||||
"no": "non",
|
"no": "non",
|
||||||
"unknown": "inconnu",
|
"unknown": "inconnu",
|
||||||
"none": "aucun"
|
"others": "autres",
|
||||||
|
"none": "aucun",
|
||||||
|
"remove": "supprimer",
|
||||||
|
"close": "fermer"
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"links": "Ссылки",
|
"links": "Ссылки",
|
||||||
"clients": "Клиенты",
|
"clients": "Клиенты",
|
||||||
"distance": "Расстояние",
|
"distance": "Расстояние",
|
||||||
|
"connectionType": "Тип подключения",
|
||||||
"tq": "Качество связи",
|
"tq": "Качество связи",
|
||||||
"lastOnline": "в сети, последнее сообщение %{time} (%{date})",
|
"lastOnline": "в сети, последнее сообщение %{time} (%{date})",
|
||||||
"lastOffline": "не в сети, последнее сообщение %{time} (%{date})",
|
"lastOffline": "не в сети, последнее сообщение %{time} (%{date})",
|
||||||
@ -16,7 +17,7 @@
|
|||||||
"hardware": "Тип оборудования",
|
"hardware": "Тип оборудования",
|
||||||
"visible": "Видно на карте",
|
"visible": "Видно на карте",
|
||||||
"update": "Автообновление",
|
"update": "Автообновление",
|
||||||
"site": "Сайт",
|
"domain": "Сайт",
|
||||||
"gateway": "Шлюз",
|
"gateway": "Шлюз",
|
||||||
"coordinates": "Координаты",
|
"coordinates": "Координаты",
|
||||||
"contact": "Контакты",
|
"contact": "Контакты",
|
||||||
@ -26,7 +27,9 @@
|
|||||||
"systemLoad": "Средняя загрузка",
|
"systemLoad": "Средняя загрузка",
|
||||||
"ram": "Используемая память",
|
"ram": "Используемая память",
|
||||||
"ipAddresses": "IP адреса",
|
"ipAddresses": "IP адреса",
|
||||||
"selectedGateway": "Выбранный шлюз",
|
"nexthop": "Следующий скачок",
|
||||||
|
"selectedGatewayIPv4": "Выбранный шлюз ipv4",
|
||||||
|
"selectedGatewayIPv6": "Выбранный шлюз ipv6",
|
||||||
"link": "Ссылка |||| Ссылки",
|
"link": "Ссылка |||| Ссылки",
|
||||||
"node": "Узел |||| Узлы",
|
"node": "Узел |||| Узлы",
|
||||||
"new": "Новые узлы",
|
"new": "Новые узлы",
|
||||||
@ -50,7 +53,8 @@
|
|||||||
"aboutInfo": "<h2>О Meshviewer</h2> <p>Вы можете увеличить масштаб двойным щелчком мыши и уменьшить с shift + двойной щелчок</p>",
|
"aboutInfo": "<h2>О Meshviewer</h2> <p>Вы можете увеличить масштаб двойным щелчком мыши и уменьшить с shift + двойной щелчок</p>",
|
||||||
"actual": "Текущее",
|
"actual": "Текущее",
|
||||||
"stats": "Статистика",
|
"stats": "Статистика",
|
||||||
"about": "О продукте"
|
"about": "О продукте",
|
||||||
|
"toggle": "Включить панель"
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"switchView": "Переключить вид",
|
"switchView": "Переключить вид",
|
||||||
@ -85,5 +89,8 @@
|
|||||||
"yes": "да",
|
"yes": "да",
|
||||||
"no": "нет",
|
"no": "нет",
|
||||||
"unknown": "неизвестно",
|
"unknown": "неизвестно",
|
||||||
"none": "нет"
|
"others": "другие",
|
||||||
|
"none": "нет",
|
||||||
|
"remove": "убрать",
|
||||||
|
"close": "закрыть"
|
||||||
}
|
}
|
||||||
|
96
locale/tr.json
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
77
package.json
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ffrgb-meshviewer",
|
"name": "meshviewer",
|
||||||
|
"version": "11.1.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -9,28 +10,30 @@
|
|||||||
"url": "https://github.com/ffrgb/meshviewer/issues"
|
"url": "https://github.com/ffrgb/meshviewer/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "^7.2.2",
|
"babel-eslint": "^10.0.1",
|
||||||
"browser-sync": "^2.18.8",
|
"browser-sync": "^2.26.5",
|
||||||
"eslint": "^3.19.0",
|
"del": "^5.1.0",
|
||||||
"eslint-config-airbnb-es5": "^1.1.0",
|
"eslint": "^6.5.1",
|
||||||
|
"eslint-config-airbnb-es5": "^1.2.0",
|
||||||
"eslint-config-defaults": "^9.0.0",
|
"eslint-config-defaults": "^9.0.0",
|
||||||
"eslint-plugin-react": "^6.10.3",
|
"eslint-plugin-react": "^7.12.4",
|
||||||
"gulp": "github:gulpjs/gulp#4.0",
|
"gulp": "^4.0.1",
|
||||||
"gulp-autoprefixer": "^3.1.1",
|
"gulp-autoprefixer": "^7.0.1",
|
||||||
"gulp-cache-bust": "^1.1.0",
|
"gulp-cache-bust": "^1.4.0",
|
||||||
|
"gulp-cli": "^2.2.0",
|
||||||
"gulp-environments": "^0.1.2",
|
"gulp-environments": "^0.1.2",
|
||||||
"gulp-eslint": "^3.0.1",
|
"gulp-eslint": "^6.0.0",
|
||||||
"gulp-htmlmin": "^3.0.0",
|
"gulp-htmlmin": "^5.0.1",
|
||||||
"gulp-inject": "^4.2.0",
|
"gulp-inject": "^5.0.2",
|
||||||
"gulp-jsonminify": "^1.0.0",
|
"gulp-inline-source": "^4.0.0",
|
||||||
"gulp-kyh-inline-source": "^3.0.2",
|
"gulp-jsonminify": "^1.1.0",
|
||||||
"gulp-load-plugins": "^1.5.0",
|
"gulp-load-plugins": "^2.0.1",
|
||||||
"gulp-real-favicon": "^0.2.2",
|
"gulp-real-favicon": "^0.3.2",
|
||||||
"gulp-requirejs-optimize": "^1.2.0",
|
"gulp-requirejs-optimize": "^1.3.0",
|
||||||
"gulp-sass": "^3.1.0",
|
"gulp-sass": "^4.0.2",
|
||||||
"gulp-sass-lint": "^1.3.2",
|
"gulp-sass-lint": "^1.4.0",
|
||||||
"gulp-sourcemaps": "^2.6.0",
|
"gulp-sourcemaps": "^2.6.5",
|
||||||
"gulp-uglify": "^2.1.2"
|
"gulp-uglify": "^3.0.2"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"env": {
|
"env": {
|
||||||
@ -42,17 +45,23 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"almond": "^0.3.3",
|
"almond": "^0.3.3",
|
||||||
"d3-drag": "^1.0.4",
|
"d3-drag": "^1.2.4",
|
||||||
"d3-force": "^1.0.6",
|
"d3-force": "^1.2.1",
|
||||||
"d3-selection": "^1.0.5",
|
"d3-selection": "^1.4.0",
|
||||||
"d3-zoom": "^1.1.3",
|
"d3-zoom": "^1.8.3",
|
||||||
"leaflet": "^1.0.3",
|
"leaflet": "^1.5.1",
|
||||||
"moment": "^2.17.1",
|
"moment": "^2.24.0",
|
||||||
"navigo": "^4.6.0",
|
"navigo": "^7.1.2",
|
||||||
"node-polyglot": "^2.2.2",
|
"node-polyglot": "2.2.2",
|
||||||
"promise-polyfill": "^6.0.2",
|
"promise-polyfill": "^8.1.3",
|
||||||
"rbush": "^2.0.1",
|
"rbush": "^3.0.1",
|
||||||
"requirejs": "^2.3.2",
|
"requirejs": "^2.3.6",
|
||||||
"snabbdom": "^0.6.4"
|
"snabbdom": "^0.7.3"
|
||||||
}
|
},
|
||||||
|
"scripts": {
|
||||||
|
"gulp": "./node_modules/gulp-cli/bin/gulp.js"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1% in DE"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
72
polyfill.js
@ -7,56 +7,8 @@ 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') {
|
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
|
if (target == null) { // TypeError if undefined or null
|
||||||
throw new TypeError('Cannot convert undefined or null to object');
|
throw new TypeError('Cannot convert undefined or null to object');
|
||||||
}
|
}
|
||||||
@ -78,3 +30,25 @@ if (typeof Object.assign !== 'function') {
|
|||||||
return to;
|
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');
|
||||||
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
@import 'custom/variables';
|
@import 'custom/variables';
|
||||||
|
|
||||||
// Mixins
|
// Mixins
|
||||||
@import 'mixins/shadow';
|
|
||||||
@import 'mixins/icon';
|
@import 'mixins/icon';
|
||||||
@import 'mixins/font';
|
@import 'mixins/font';
|
||||||
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
// Original is in LESS and can be found here: https://gist.github.com/gefangenimnetz/3ef3e18364edf105c5af
|
|
||||||
@mixin shadow($level: 1) {
|
|
||||||
@if $level == 1 {
|
|
||||||
box-shadow: 0 1px 3px 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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,6 +13,12 @@ header {
|
|||||||
border-bottom: 1px solid darken($color-white, 10%);
|
border-bottom: 1px solid darken($color-white, 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea,
|
||||||
|
input {
|
||||||
|
background: transparent;
|
||||||
|
color: $color-black, 100;
|
||||||
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
h3,
|
h3,
|
||||||
@ -22,11 +28,7 @@ h6 {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1,
|
||||||
font-size: 2em;
|
|
||||||
padding: .67em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
padding: .83em 0;
|
padding: .83em 0;
|
||||||
@ -37,6 +39,7 @@ h3 {
|
|||||||
padding: 1em 0;
|
padding: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
h2,
|
h2,
|
||||||
h3 {
|
h3 {
|
||||||
padding-left: $button-distance;
|
padding-left: $button-distance;
|
||||||
@ -57,6 +60,10 @@ img {
|
|||||||
a {
|
a {
|
||||||
color: $color-online;
|
color: $color-online;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
color: darken($color-online, 15%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@ -70,3 +77,15 @@ strong {
|
|||||||
.hide {
|
.hide {
|
||||||
display: none !important; // sass-lint:disable-line no-important
|
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;
|
||||||
|
}
|
||||||
|
@ -29,7 +29,8 @@ button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active,
|
||||||
|
&:focus {
|
||||||
box-shadow: 0 0 0 2px $color-primary;
|
box-shadow: 0 0 0 2px $color-primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,20 +38,6 @@ button {
|
|||||||
color: $color-primary;
|
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
|
// Tooltip
|
||||||
&[data-tooltip] {
|
&[data-tooltip] {
|
||||||
&::after {
|
&::after {
|
||||||
@ -78,9 +65,6 @@ button {
|
|||||||
&.close {
|
&.close {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@if $shadows == 1 {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
color: transparentize($color-black, .5);
|
color: transparentize($color-black, .5);
|
||||||
float: right;
|
float: right;
|
||||||
font-size: $button-font-size;
|
font-size: $button-font-size;
|
||||||
@ -90,3 +74,33 @@ button {
|
|||||||
width: auto;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -57,6 +57,10 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: transparentize($color-primary, .95);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
@ -1,7 +1,30 @@
|
|||||||
.infobox {
|
.infobox {
|
||||||
.clients {
|
.clients,
|
||||||
color: $color-online;
|
.gateway {
|
||||||
font-family: $font-family-icons;
|
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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input,
|
input,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
header {
|
header {
|
||||||
h2 {
|
h1 {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -17,13 +17,23 @@ header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.legend {
|
.legend {
|
||||||
.symbol {
|
a {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.symbol {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
vertical-align: -5%;
|
vertical-align: -5%;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dot looks compared to thin font a bit darker - lighten it 10%
|
// Dot looks compared to thin font a bit darker - lighten it 10%
|
||||||
@ -45,7 +55,20 @@ header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-online,
|
.legend-24ghz {
|
||||||
.legend-offline {
|
.symbol {
|
||||||
margin-left: 1em;
|
background-color: $color-24ghz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-5ghz {
|
||||||
|
.symbol {
|
||||||
|
background-color: $color-5ghz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-others {
|
||||||
|
.symbol {
|
||||||
|
background-color: $color-others;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: map-get($grid-breakpoints, lg) - 1) {
|
@media screen and (max-width: map-get($grid-breakpoints, lg) - 1) {
|
||||||
right: -1rem;
|
right: .1rem;
|
||||||
top: 0;
|
top: 0;
|
||||||
transform: scale(.8);
|
transform: scale(.8);
|
||||||
|
transform-origin: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
background: $color-new;
|
background: $color-new;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 1.4em;
|
height: 1.4em;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
|
@ -16,9 +16,14 @@
|
|||||||
|
|
||||||
span {
|
span {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
color: $color-white;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
min-width: 1.5em;
|
min-width: 1.5em;
|
||||||
padding: .25em .5em;
|
padding: .25em .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,13 @@
|
|||||||
.sidebarhandle {
|
.sidebarhandle {
|
||||||
left: $button-distance;
|
left: $button-distance;
|
||||||
transform: scale(-1, 1);
|
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) {
|
@media screen and (max-width: map-get($grid-breakpoints, lg) - 1) {
|
||||||
width: auto;
|
width: auto;
|
||||||
@ -23,7 +30,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.node-list,
|
.node-list,
|
||||||
.node-links {
|
.node-links,
|
||||||
|
.link-list {
|
||||||
th,
|
th,
|
||||||
td {
|
td {
|
||||||
&:first-child {
|
&:first-child {
|
||||||
@ -40,36 +48,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link-list {
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
&:nth-child(2) {
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.node-links {
|
.node-links {
|
||||||
padding-bottom: 15px;
|
padding-bottom: 15px;
|
||||||
|
|
||||||
th,
|
th,
|
||||||
td {
|
td {
|
||||||
&:first-child {
|
&:first-child {
|
||||||
width: 50px;
|
width: 35px;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-list {
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
&:nth-child(1) {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
width: 66%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@if $shadows == 1 {
|
|
||||||
@include shadow(2);
|
|
||||||
} @else {
|
|
||||||
border-right: 1px solid darken($color-white, 10%);
|
|
||||||
}
|
|
||||||
background: transparentize($color-white, .03);
|
background: transparentize($color-white, .03);
|
||||||
|
border-right: 1px solid darken($color-white, 10%);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
overflow-y: visible;
|
overflow-y: visible;
|
||||||
|
|
||||||
@ -91,9 +92,6 @@
|
|||||||
.container,
|
.container,
|
||||||
.infobox {
|
.infobox {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@if $shadows == 1 {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,13 +118,19 @@
|
|||||||
left: $sidebar-width + 2 * $button-distance;
|
left: $sidebar-width + 2 * $button-distance;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: $button-distance;
|
top: $button-distance;
|
||||||
transition: left .5s, box-shadow .5s, color .5s, transform .5s;
|
transition: left .5s, color .5s, transform .5s;
|
||||||
z-index: 1010;
|
z-index: 1010;
|
||||||
|
|
||||||
&::after {
|
&::before {
|
||||||
content: '\f124';
|
content: '\f124';
|
||||||
padding-right: .125em;
|
padding-right: .125em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[aria-label] {
|
||||||
|
&::after {
|
||||||
|
transform: translate(-45px, 52px) !important; // sass-lint:disable-line no-important
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.online {
|
.online {
|
||||||
@ -136,7 +140,3 @@
|
|||||||
.offline {
|
.offline {
|
||||||
color: $color-offline;
|
color: $color-offline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unseen {
|
|
||||||
color: $color-unseen;
|
|
||||||
}
|
|
||||||
|
@ -22,6 +22,16 @@ table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
&.header {
|
||||||
|
font-size: 1.2em;
|
||||||
|
|
||||||
|
th {
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
td,
|
td,
|
||||||
th {
|
th {
|
||||||
line-height: 1.41em;
|
line-height: 1.41em;
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
.tabs {
|
.tabs {
|
||||||
@if $shadows == 1 {
|
background: transparentize($color-black, .98);
|
||||||
@include shadow(1);
|
|
||||||
} @else {
|
|
||||||
border: 0 solid darken($color-white, 10%);
|
border: 0 solid darken($color-white, 10%);
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
}
|
|
||||||
background: transparentize($color-black, .98);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
display: -webkit-flex; // sass-lint:disable-line no-vendor-prefixes no-duplicate-properties
|
display: -webkit-flex; // sass-lint:disable-line no-vendor-prefixes no-duplicate-properties
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
@ -9,7 +9,10 @@ $color-primary: #dc0067 !default;
|
|||||||
$color-new: #459c18 !default;
|
$color-new: #459c18 !default;
|
||||||
$color-online: #1566a9 !default;
|
$color-online: #1566a9 !default;
|
||||||
$color-offline: #cf3e2a !default;
|
$color-offline: #cf3e2a !default;
|
||||||
$color-unseen: #d89100 !default;
|
|
||||||
|
$color-24ghz: $color-primary !default;
|
||||||
|
$color-5ghz: #e3a619 !default;
|
||||||
|
$color-others: #0a9c92 !default;
|
||||||
|
|
||||||
$color-map-background: #f8f4f0 !default;
|
$color-map-background: #f8f4f0 !default;
|
||||||
|
|
||||||
@ -43,8 +46,5 @@ $grid-breakpoints: (
|
|||||||
$sidebar-width: map-get($grid-breakpoints, xl) * .45 !default;
|
$sidebar-width: map-get($grid-breakpoints, xl) * .45 !default;
|
||||||
$sidebar-width-small: map-get($grid-breakpoints, lg) * .45 !default;
|
$sidebar-width-small: map-get($grid-breakpoints, lg) * .45 !default;
|
||||||
|
|
||||||
// En/disable box-shadows
|
|
||||||
$shadows: 0 !default;
|
|
||||||
|
|
||||||
// En/disable included font
|
// En/disable included font
|
||||||
$use-included-font: 1 !default;
|
$use-included-font: 1 !default;
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
// Overwrite normal style (colors) - shadows are ignored
|
// Overwrite normal style (colors)
|
||||||
@import 'modules/variables';
|
@import 'modules/variables';
|
||||||
@import 'custom/variables';
|
@import 'custom/variables';
|
||||||
|
|
||||||
$color-white: #111;
|
$color-white: #1c1c13;
|
||||||
$color-black: #fefefe;
|
$color-black: #fefefe;
|
||||||
$color-map-background: #0d151c;
|
$color-map-background: #0d151c;
|
||||||
|
|
||||||
|
$color-online: lighten($color-online, 25%);
|
||||||
|
|
||||||
html {
|
html {
|
||||||
//@import 'modules/base';
|
//@import 'modules/base';
|
||||||
body {
|
body,
|
||||||
|
textarea,
|
||||||
|
input {
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
color: lighten($color-black, 100);
|
color: lighten($color-black, 100);
|
||||||
}
|
}
|
||||||
@ -18,6 +22,15 @@ html {
|
|||||||
border-bottom-color: lighten($color-white, 10%);
|
border-bottom-color: lighten($color-white, 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $color-online;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
color: darken($color-online, 15%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//@import 'modules/leaflet';
|
//@import 'modules/leaflet';
|
||||||
.leaflet-container {
|
.leaflet-container {
|
||||||
background: $color-map-background;
|
background: $color-map-background;
|
||||||
|