Compare commits

..

243 Commits

Author SHA1 Message Date
8a756f0ecb „config.js“ ändern
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-11 09:42:48 +00:00
b1a1df1a56 „config.js“ ändern
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-11 08:12:22 +00:00
35cbfad9b0 Changed Stat Images
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-03 10:10:05 +00:00
68de16e73b „config.js“ ändern
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-13 23:30:50 +00:00
9fe1d0fab8 „config.js“ ändern
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-13 18:41:36 +00:00
538e47ed0e „config.js“ ändern
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-13 17:31:39 +00:00
fb12482833 „config.js“ ändern
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-29 17:22:38 +00:00
f5317e4559 Add Rifu data Files
Some checks failed
continuous-integration/drone/push Build is failing
2020-12-29 17:19:57 +00:00
07fbfe6a63 Added New Tile Server
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-12 17:40:23 +00:00
0fc62b342e Remove Rifu Data Source
All checks were successful
continuous-integration/drone/push Build is passing
2020-09-13 09:33:01 +00:00
b6a4d02507 „config.js“ ändern
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-19 22:06:10 +00:00
4d046c2c8e Add temporary Data Folder for Rifu
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-19 21:09:09 +00:00
nils
71da7ebd8c Update stretch build image
All checks were successful
continuous-integration/drone/push Build is passing
2019-12-09 13:24:39 +00:00
nils
50188b9b1a Removed unnecessary RUN command
All checks were successful
continuous-integration/drone/push Build is passing
2019-12-07 14:08:51 +00:00
nils
d29919556d Update build status
All checks were successful
continuous-integration/drone/push Build is passing
2019-12-06 07:23:38 +00:00
nils
0cd0af750d Publish to fftdf 2019-12-06 07:16:51 +00:00
stebifan
7429c5dc58
Update config.js 2019-11-23 11:22:34 +01:00
a37f26c256
fixed repo 2019-11-23 00:45:21 +01:00
a7cfaa8b0a
more testing 2019-11-23 00:42:27 +01:00
fa9d990428
forgot to add files... 2019-11-23 00:40:03 +01:00
78ab3c0796
add debugging step 2019-11-23 00:34:29 +01:00
e65544a31b
remove global flag 2019-11-23 00:32:09 +01:00
6563c9073e
... 2019-11-23 00:30:41 +01:00
d1e9c75c0a
install gulp as well 2019-11-23 00:28:56 +01:00
ab67d494c9
further testing 2019-11-23 00:27:23 +01:00
cf3d47bdd7
testing Dockerfile 2019-11-23 00:25:52 +01:00
441e1e61be
error in file 2019-11-23 00:20:08 +01:00
b4b9d42bf4
test for multistage build 2019-11-23 00:17:15 +01:00
a768496858
forgot steps 2019-11-22 23:53:39 +01:00
d987c63461
more testing 2019-11-22 23:53:01 +01:00
536a6c920f
new version 2019-11-22 23:49:56 +01:00
f14ba5d954
added drone file 2019-11-22 23:48:49 +01:00
1347254bb2
Merge branch 'ffrgb-develop' into develop 2019-11-17 11:28:24 +01:00
614093d2af
Merge branch 'develop' of git://github.com/ffrgb/meshviewer into ffrgb-develop 2019-11-17 11:28:03 +01:00
stebifan
3c0cd2a36d
Update config.js 2019-11-10 15:21:24 +01:00
stebifan
4fd562b84b
Update config.js 2019-11-10 15:02:45 +01:00
stebifan
e448b02bb4
Add FFRS tile Server 2019-11-09 18:00:37 +01:00
Xaver Maierhofer
4d47587278
[BUGFIX] Correct EditorConfig 2019-10-18 16:21:43 +02:00
Xaver Maierhofer
1fa034e1e8
[TASK] Set nodejs v12 as standard & remove EOL v11 2019-10-18 15:39:15 +02:00
Xaver Maierhofer
1bcc6bfd10
[TASK] Move browserlist 2019-10-18 15:29:21 +02:00
Xaver Maierhofer
35a79ba4f6 [TASK] Upgrade packages & build tools 2019-10-18 15:13:37 +02:00
Xaver Maierhofer
d28e834cd5 [TASK] Rbush 3 2019-10-18 14:58:42 +02:00
David Bauer
f40179535f [BUGFIX] Fix spelling error on offline page 2019-10-10 16:18:26 +02:00
Xaver Maierhofer
1dfe8951ac
[TASK] Remove outdated links 2019-05-26 23:41:07 +02:00
stebifan
3a3a3e014c
Update config.js 2019-05-07 16:42:11 +02:00
stebifan
e083ed13a7
Update config.js 2019-05-07 16:39:07 +02:00
stebifan
8a89c0f5db
Update config.js 2019-05-07 16:36:06 +02:00
stebifan
ec0e0e5445
Removed tdf7 2019-04-27 21:53:17 +02:00
Xaver Maierhofer
157da0aeba
[TASK] Add node.js 12 to travisci 2019-04-27 16:51:47 +02:00
Xaver Maierhofer
8959e1e7cb
[BUGFIX] Set input font/background color 2019-04-26 22:43:00 +02:00
Xaver Maierhofer
ddb1482a67
[TASK] Update gulp 2019-04-21 21:02:01 +02:00
Xaver Maierhofer
6c8c12be1e
[TASK] Dependency updates 2019-04-06 23:12:58 +02:00
Xaver Maierhofer
22af6d1bce
[BUGFIX] Inline css/js in index.html 2019-03-15 14:14:03 +01:00
Xaver Maierhofer
90a19a4e74
[TASK] Dependency updates 2019-03-08 16:46:07 +01:00
stebifan
b6f1dd066e
Update config.js 2019-02-08 14:43:13 +01:00
Xaver Maierhofer
d021d312d7
[TASK] Update dependencies 2019-02-03 14:34:44 +01:00
stebifan
98e7a55f6f
Update config.js 2019-02-03 12:29:31 +01:00
stebifan
2736461604
Update config.js 2019-01-31 19:36:27 +01:00
stebifan
c0ea13a8fa
Update config.js 2019-01-26 20:24:31 +01:00
stebifan
2200c4bc19
Update config.js 2019-01-26 19:52:09 +01:00
Xaver Maierhofer
c99e4d8c42
[TASK] Set version to 11.1.0 2018-12-15 16:59:33 +01:00
Xaver Maierhofer
aeb849aca8
[TASK] Add nodes as title and link to link-infobox 2018-12-01 22:02:50 +01:00
Xaver Maierhofer
3dc4d10082
[TASK] Remove netlify info 2018-12-01 21:59:55 +01:00
Xaver Maierhofer
97cda8cb18
[TASK] Update from travis.org to .com 2018-12-01 21:58:41 +01:00
Xaver Maierhofer
5ad56139ac
[BUGFIX] Update gulp-inline-source & injectFaviconMarkups modified attribute 2018-11-28 21:24:55 +01:00
Xaver Maierhofer
c45f367fcd
[TASK] Add del package for gulp 2018-11-28 13:41:38 +01:00
Xaver Maierhofer
29ea71a4d0
[TASK] Update packages to remove flatmap-stream 2018-11-28 13:39:38 +01:00
srauscher
d2033af7d7 [BUGFIX] Change Ghz to GHz (#278) 2018-11-28 13:33:35 +01:00
stebifan
69e37f779f
Update config.default.js 2018-11-06 19:32:56 +01:00
Xaver Maierhofer
a34812f2a9
[TASK] Update example stat images 2018-10-27 15:14:51 +02:00
Xaver Maierhofer
c02cf3be95
[BUGFIX] Clear link/node in sidebar destory 2018-10-27 15:08:40 +02:00
Xaver Maierhofer
84085727a6
[TASK] Travis node.js v11 2018-10-27 14:31:30 +02:00
Xaver Maierhofer
8fdc8dcabb
[TASK] Avoid unnassary loops in domain name mapping 2018-10-21 19:02:57 +02:00
Xaver Maierhofer
77c94a1f2e
[TASK] Update dependencys (browser-sync, eslint) 2018-10-13 22:19:01 +02:00
Xaver Maierhofer
2604b2b731
[TASK] Use Promises for GeoJSON
Allow ajax requests, single or list of objects
2018-10-07 16:39:07 +02:00
Xaver Maierhofer
31d0209cc2
[TASK] Update packages & add SHA-1 integrity 2018-10-07 15:26:18 +02:00
Geno
bc82e07354 [TASK] Replace site with domain 2018-10-07 15:04:12 +02:00
Xaver Maierhofer
fd9eab724b
[TASK] Libary updates gulp4, eslint, d3js, leaflet 2018-08-28 14:21:40 +02:00
stebifan
5d1d72d95d
Merge pull request #3 from ffrgb/develop
merge upstream
2018-08-06 22:27:57 +02:00
Xaver Maierhofer
8bf3498744
[TASK] Prevent XSS in tooltip 2018-08-06 17:50:57 +02:00
Xaver Maierhofer
2192500d05
[TASK] Remove unnecessary moment 2018-08-06 16:50:27 +02:00
Xaver Maierhofer
712c3f21ce
[TASK] Remove obsolete windows, rename macosx to osx 2018-08-03 21:02:06 +02:00
Xaver Maierhofer
3d113c6247
[TASK] Remove bithound 2018-08-03 20:50:47 +02:00
Xaver Maierhofer
9c596531e2
[TASK] Optimize code 2018-08-03 20:08:08 +02:00
Xaver Maierhofer
e1aa152055
[BUGFIX] Gulp html inject order 2018-08-02 02:14:31 +02:00
Xaver Maierhofer
d29bb31311
[TASK] Remove localStorage 2018-08-02 01:58:41 +02:00
Xaver Maierhofer
2c6303d820
[TASK] Add dynamic title to offline html and multiple metatags 2018-08-02 01:15:19 +02:00
Xaver Maierhofer
2a7e1cdaa6
[TASK] No startup browser 2018-08-02 00:58:50 +02:00
Xaver Maierhofer
3311e70296
[BUGFIX] Fullscreen icon switch 2018-08-01 21:16:41 +02:00
Xaver Maierhofer
e1d3a3d7b2
[BUGFIX] Fullscreen firefox legacy & chrome 2018-08-01 16:05:50 +02:00
Xaver Maierhofer
25212adb81
[TASK] Add optional fullscreen mode 2018-07-31 23:25:23 +02:00
Xaver Maierhofer
4fd4e27a8b
[TASK] Add GeoJSON support
JSON needs to be added as array in config.js oder config.default.js
It needs a json and a option part (for style)

e.g.
geo: [
      {
        json: [
          {
            'type': 'Feature',
            'geometry': {
              'type': 'Polygon',
              'coordinates': [
                [
                  [
                    12.04925537109375,
                    49.036517514836994
                  ],
                  [
                    12.033462524414062,
                    49.021660359632115
                  ],
                  [
                    12.058181762695312,
                    48.99553703238219
                  ],
                  [
                    12.11311340332031,
                    49.001843917978526
                  ],
                  [
                    12.122726440429686,
                    49.03381654386847
                  ],
                  [
                    12.04925537109375,
                    49.036517514836994
                  ]
                ]
              ]
            }
          }
        ],
        option: {
          style: {
            color: '#e23535',
            weight: 5,
            opacity: 0.4,
            fillColor: '#6de922',
            fillOpacity: 0.1
          }
        }
      }
    ]
2018-07-27 20:04:47 +02:00
Xaver Maierhofer
bfb1111744
[TASK] Improve night colors 2018-07-25 23:46:32 +02:00
Xaver Maierhofer
81a26b5560
[!!!][TASK] Indexable urls 2018-07-22 15:30:09 +02:00
Xaver Maierhofer
02e02f9219
[BUGFIX] URL router can fail at high load 2018-07-22 14:44:34 +02:00
Xaver Maierhofer
20a8c4583a
[TASK] Update leaflet map to 1.3.3 2018-07-20 16:42:56 +02:00
Xaver Maierhofer
51be472ce4
[BUGFIX] Use polygot 2.2 with AMD support 2018-07-13 21:53:01 +02:00
Xaver Maierhofer
e75d865fbf
[TASK] Update github issue templates 2018-07-13 20:08:23 +02:00
Xaver Maierhofer
4f078f4b56
[TASK] Update to eslint 5 2018-07-13 17:00:25 +02:00
Xaver Maierhofer
15f8f4bd74
[TASK] Add nodejs 10 and remove 9 from travis 2018-07-09 02:09:46 +02:00
stebifan
3c72e12c3a
Update config.js 2018-06-25 22:07:47 +02:00
stebifan
28df47c15f
Update config.js 2018-06-25 22:04:50 +02:00
stebifan
ded730f36c
Update config.js 2018-06-25 21:59:48 +02:00
stebifan
ad4ee19344
Update config.js 2018-06-25 21:59:11 +02:00
stebifan
48aa3a486b
Merge pull request #2 from ffrgb/develop
Update Fork
2018-06-25 21:53:09 +02:00
Xaver Maierhofer
ad89c910ab
[TASK] Move to v8 promies polyfill & eslint5 2018-06-25 20:45:29 +02:00
stebifan
7aab2f1462
Update config.js 2018-06-10 11:27:38 +02:00
stebifan
61b9e9d351
Update config.js 2018-06-10 11:20:03 +02:00
Xaver Maierhofer
3085c14c3b
[TASK] Add posibility for links DSGVO 2018-05-24 00:51:41 +02:00
Xaver Maierhofer
100cf626a5
[TASK] Update favicon generation 0.15 to 0.16 2018-05-11 22:22:37 +02:00
Xaver Maierhofer
6d57ad35c3
[TASK] Update moment, promies polyfill & gulp utilities 2018-05-11 22:10:42 +02:00
stebifan
0c03efbfff
Update config.js 2018-04-19 12:06:43 +02:00
stebifan
ef9b9c3c3f
Update config.js 2018-04-19 12:01:26 +02:00
stebifan
1b89f86d74
Update config.js 2018-04-18 00:51:47 +02:00
stebifan
b294a5d405
Update config.js 2018-04-18 00:27:32 +02:00
stebifan
957cc892a9
Update config.js 2018-04-17 23:52:35 +02:00
stebifan
6c0e667259
Update config.js 2018-04-16 22:59:38 +02:00
stebifan
13f84d5dbc
Update config.js 2018-04-16 22:49:55 +02:00
stebifan
7bd2883732
Update config.js 2018-04-10 22:16:28 +02:00
stebifan
5343b5dfaf
Update config.js 2018-04-10 16:51:02 +02:00
stebifan
82c9309a1d
Update config.js 2018-04-09 22:21:31 +02:00
stebifan
3714795e2b
Merge pull request #1 from ffrgb/develop
Merge Changes
2018-04-09 22:15:41 +02:00
Xaver Maierhofer
300a73213b
[TASK] Add OpenGraph, twitter card & Microdata 2018-03-24 00:51:37 +01:00
Xaver Maierhofer
4dd8dc9c45
[TASK] Upgrade dependencies 2018-03-24 00:17:10 +01:00
Xaver Maierhofer
87eb98f542
[TASK] Add simple offline service worker 2018-03-11 13:53:03 +01:00
Xaver Maierhofer
8e8cdd63af
[TASK] Update example data 2018-03-05 13:09:39 +01:00
Xaver Maierhofer
75c650b107
[TASK] Update momentJS and build tools 2018-03-05 13:09:39 +01:00
Geno
f115600e5b [TASK] Add source/target address to link variables
Support for babel
2018-02-25 18:01:49 +01:00
Geno
c32e951cf1 [BUGFIX] Bar width max 100% 2018-02-25 17:33:57 +01:00
Xaver Maierhofer
4bc2256f2f
[BUGFIX] Update d3-selection path 2018-02-17 19:04:22 +01:00
Felix Kaechele
76fb09326e [BUGFIX] Allow negative coordinates
The world is more than just the part east of the prime meridian and
north of the equator 😜

This allows deeplinks to have negative coordinates. Also fixes the
coordinate picker when selecting coordinates with negative values.

Signed-off-by: Felix Kaechele <felix@kaechele.ca>
2018-02-11 12:59:37 +01:00
Xaver Maierhofer
9212b0e427
[TASK] Upgrade navigo, d3-selection, promies polyfill & dev dependencys 2018-02-11 12:53:40 +01:00
Geno
fc596ee45f [BUGFIX] Correct filled loadavg bar with nproc > 1 2018-02-11 12:42:48 +01:00
Xaver Maierhofer
380b13d04b
[TASK] Show rectangle gateway in forcegraph 2018-01-19 22:53:59 +01:00
Xaver Maierhofer
66127de355
[TASK] Show offline nodes in forcegraph 2018-01-19 22:27:42 +01:00
Xaver Maierhofer
bd670c85fd
[TASK] Update to leaflet 1.3.1 2018-01-19 01:03:38 +01:00
Xaver Maierhofer
3c33dd63ac
[TASK] Upgrade to leaflet 1.3 2018-01-15 18:18:53 +01:00
Xaver Maierhofer
dc81543bc2
[TASK] Update gulp-cache-bust 2018-01-14 15:17:27 +01:00
Xaver Maierhofer
5c6f478fe6
[TASk] Upgrade to navigo 7 2018-01-13 01:10:54 +01:00
Xaver Maierhofer
198b97ad18
[TASK] Release v10.0.0 2018-01-05 23:41:41 +01:00
Xaver Maierhofer
0aa3473a62
[TASK] Allow errors on gulp serve 2018-01-05 22:59:34 +01:00
Xaver Maierhofer
e0cef88beb
[TASK] Upgrade browser-sync & gulp-(eslint|sourcemaps) 2018-01-05 22:55:17 +01:00
Xaver Maierhofer
71ab6e65e4
[TASK] Add title for icons 2018-01-05 22:28:55 +01:00
Xaver Maierhofer
1673fe1248
[TASK] Add link type in lists 2018-01-05 22:28:30 +01:00
Xaver Maierhofer
c3cda56fe9
[TASK] Update navigo, autoprefixer and uglify-js 2018-01-01 21:26:37 +01:00
Xaver Maierhofer
bac2977b3d
[BUGFIX] Call correct self.resetView 2018-01-01 21:16:08 +01:00
Xaver Maierhofer
b48b4f41f0
[BUGFIX] GatewayCols variable name 2018-01-01 21:02:53 +01:00
Xaver Maierhofer
201c74d29b [TASK] Add zoom modifier for forcegraph 2017-12-31 05:15:03 +01:00
Xaver Maierhofer
5702b5f21b [TASK] Split into rows - long gateway names 2017-12-31 05:13:02 +01:00
Xaver Maierhofer
b1a5e472e4 [TASk] Browsersync update 2017-12-29 19:29:08 +01:00
Xaver Maierhofer
f8bf473666 [TASK] Update to gulp4 alpha3 2017-12-28 19:27:57 +01:00
Xaver Maierhofer
8da8154edd [BUGFIX] Version compare chars only 2017-12-28 15:20:03 +01:00
Xaver Maierhofer
c9dd968c92 [BUGFIX] Internet Explorer canvas remove 2017-12-27 19:52:47 +01:00
Martin Geno
ed80c7657e
[TASK] Sorting versions with debian standard 2017-12-27 09:28:14 +01:00
Xaver Maierhofer
f729349a1f
[TASK] Add turkish language 2017-12-27 00:45:42 +01:00
Xaver Maierhofer
a30d12312a
[BUGFIX] Support CustomEvent in IE 2017-12-26 18:14:23 +01:00
Xaver Maierhofer
7fa0c5e522
[TASK] Zoom in visible area 2017-12-26 15:06:29 +01:00
Xaver Maierhofer
6af7ba6796
[TASK] Add CZ translation 2017-12-24 23:45:48 +01:00
Xaver Maierhofer
bdac3c01e2
[TASK] Upgrade all dependencies to latest version 2017-12-24 21:53:31 +01:00
Xaver Maierhofer
a71eff2d39
[TASK] Remove npm travis test 2017-12-23 13:41:27 +01:00
Xaver Maierhofer
ad02f7789d
[TASK] Update libarys (browsersync,rbush,moment,snabbdom) 2017-12-23 13:29:10 +01:00
Xaver Maierhofer
0e20617435
[TASK] Update dependencies 2017-12-13 21:51:54 +01:00
Xaver Maierhofer
2aadc39022
[DOC] Links to doc.meshviewer.org 2017-11-21 23:55:15 +01:00
Xaver Maierhofer
87e4c1d2f9 [BUGFIX] Assinging window.config with stringify 2017-11-21 09:36:33 +01:00
Xaver Maierhofer
e99c38970a
[BUGFIX] 0 value in loadavg 2017-11-18 12:08:39 +01:00
Xaver Maierhofer
0c9860192b
[TASK] Add multiple links to link infobox 2017-11-14 17:37:20 +01:00
Xaver Maierhofer
7bfbb1b909 [TASK] Add title to html (before js) 2017-11-12 11:11:33 +01:00
Xaver Maierhofer
72674fd6da
[DOC] Update Sponsoring/Support section 2017-11-12 02:29:30 +01:00
Xaver Maierhofer
338c90a2d1
[TASK] Remove crowdin 2017-11-12 02:20:48 +01:00
Xaver Maierhofer
844bf99641 [TASK] Add connection type to tooltip 2017-11-11 11:41:43 +01:00
Xaver Maierhofer
dabfbfba83 [TASK] Use js as config 2017-11-10 20:30:29 +01:00
Xaver Maierhofer
1c14ec79ab
[TASK] Add local gulp as script to yarn 2017-11-08 23:42:12 +01:00
Xaver Maierhofer
18a2a17d97 [TASK] Define complete subst 2017-11-06 23:32:02 +01:00
Xaver Maierhofer
d0b6031d5f [BUGFIX] JS error then applying filter 2017-11-06 21:44:44 +01:00
Xaver Maierhofer
2c7500f1bb [TASK] Make more colors configurable 2017-11-06 12:12:37 +01:00
Xaver Maierhofer
cc18e53430
[TASK] Upgrade packages for faster sass build in nodejs v9 2017-11-05 17:02:15 +01:00
Xaver Maierhofer
6add4f0916
[TASK] Wait for loaded language 2017-11-05 16:58:41 +01:00
Xaver Maierhofer
b89e99b79e
[TASK] Adjust depth for network 2017-11-04 19:47:05 +01:00
Xaver Maierhofer
fd6c7c7f1e [TASK] Pass children to attributeEntry 2017-11-04 19:44:59 +01:00
Xaver Maierhofer
6091a8b82c [TASK] Dynamic node attributes via config 2017-11-04 19:44:59 +01:00
Geno
c407f2e334 [BUGFIX] Show contact 2017-11-03 20:44:13 +01:00
Xaver Maierhofer
9a4836257f
[BUGFIX] Safari querySelector foreach 2017-11-02 22:38:22 +01:00
Martin Geno
4315f18efe [BUGFIX] site_code attribute in statistics 2017-11-02 19:51:18 +01:00
Xaver Maierhofer
ed93a202d6
[TASK] Add NodeJs 9 test 2017-11-02 01:10:42 +01:00
Xaver Maierhofer
aa89f06342
[TASK] Simplify labellayer & remove unnessary condition 2017-11-02 00:47:31 +01:00
Xaver Maierhofer
1887a3270c
[TASK] Update locale 2017-11-02 00:31:48 +01:00
Geno
9f95fa9c95 [BUGFIX] Select correct goto link 2017-11-01 11:43:20 +01:00
Xaver Maierhofer
1ec81fd45c
[TASK] Remove unused polyfill 2017-10-31 20:20:48 +01:00
Martin Geno
31e8667658
[TASK] Show different vpn-links 2017-10-31 18:13:42 +01:00
Xaver Maierhofer
4caf38e990
[TASK] Cleanup old code 2017-10-31 13:57:20 +01:00
Geno
a0378348b5
[BUGFIX] Highlighting in forcegraph 2017-10-31 13:57:20 +01:00
Xaver Maierhofer
519f37cd14
[BUGFIX] Calc width for proportion bars 2017-10-31 13:57:16 +01:00
Ruben Barkow
1909eb291e
[DOC] Improve grammar in CONTRIBUTING.md 2017-10-31 13:57:16 +01:00
Xaver Maierhofer
8d6d508bba
[BUGFIX] Only request locale file once 2017-10-31 13:57:12 +01:00
Xaver Maierhofer
da029735cf
[TASK] Update d3 zoom & navigo fix 2017-10-31 13:57:12 +01:00
Xaver Maierhofer
f060884b04
[BUGFIX] Long hostname in node detail links 2017-10-31 13:57:11 +01:00
Xaver Maierhofer
57ee21f8ec
[BUGFIX] Use less force after init 2017-10-31 13:57:07 +01:00
Xaver Maierhofer
af589ee227
[TASK] Remove outdated code 2017-10-29 20:52:17 +01:00
Geno
d6b84eba22 [BUGFIX] Filter links by node + perfomance using dict/map 2017-10-29 20:14:09 +01:00
Geno
fb857717fd [BUGFIX] Forcegraph delete and add nodes again on same position 2017-10-29 19:04:02 +01:00
Geno
b4bd941197 [TASK] display & filter gateways IP4/6 and nexthop 2017-10-29 16:10:25 +01:00
Martin Geno
13eacf5fa8
[BUGFIX] Remove link direction 2017-10-29 15:11:41 +01:00
Xaver Maierhofer
e0630808e3
[TASK] Put clients drawing together 2017-10-29 15:11:41 +01:00
Geno
35fd75e4f6
[BUGFIX] forcegraph show only online nodes 2017-10-29 15:11:41 +01:00
Geno
914f6a344b
[TASK] Show last update relativ to now 2017-10-29 15:11:40 +01:00
Geno
10ab1ead2c
[TASK] Different colors on map and forcegraph for clients 2017-10-29 15:11:40 +01:00
Xaver Maierhofer
b3ef2460b3
[TASK] Remove outdated link filter 2017-10-29 15:11:40 +01:00
Xaver Maierhofer
a9630ffa78
[TASK] Improve accessbility 2017-10-29 15:11:40 +01:00
Xaver Maierhofer
2a3dbc9842
[TASK] Remove box-shadow css 2017-10-29 15:11:40 +01:00
Martin Geno
4787aa7f62
[BUGFIX] Fix uptime in nodelist 2017-10-29 15:11:40 +01:00
Xaver Maierhofer
77ac4ca3f5
[TASK] Split clients into 2,4 Ghz, 5 Ghz & others 2017-10-29 15:11:40 +01:00
Xaver Maierhofer
db16ea8375
[!!!][TASK] Refactor to meshviewer.json 2017-10-29 15:11:24 +01:00
Xaver Maierhofer
7c8456b18a
[TASK] Remove unused moment 2017-10-15 22:53:00 +02:00
Xaver Maierhofer
24abeb74bd [TASK] Move changelog to gitbooks 2017-10-15 22:39:43 +02:00
Xaver Maierhofer
ec4732610b [TASK] Drop NodeJs v4 & update babel eslint v8 2017-10-15 10:40:20 +02:00
Xaver Maierhofer
cb0aa1317c [TASK] Update navigo, d3 & moment.js 2017-10-14 14:48:01 +02:00
Xaver Maierhofer
f0789392b5 [TASK] Update js & scss linter 2017-10-14 13:49:44 +02:00
Xaver Maierhofer
93fb72788a [TASK] Update german translation 2017-10-14 13:17:52 +02:00
Xaver Maierhofer
def55bad15
[TASK] Update gulp-sourcemap & eslint 2017-09-18 22:03:00 +02:00
Moritz Warning
e2a6200d75
[BUGFIX] Allow missing site 2017-09-08 22:09:33 +02:00
Moritz Warning
a5d4140bda
[BUGFIX] Show available firmware information 2017-09-06 23:34:50 +02:00
Xaver Maierhofer
9aff29a634
[TASK] Replace Precise with Trusty in travis 2017-09-04 23:04:05 +02:00
Xaver Maierhofer
f9892e5401
[TASK] Upgrade dependencies 2017-09-04 22:21:16 +02:00
Xaver Maierhofer
329a0c44fc
[TASK] Update map attribution 2017-09-04 21:54:39 +02:00
Moritz Warning
779fa0a630
[TASK] Hide stats title when table is empty 2017-09-03 13:20:22 +02:00
Moritz Warning
c24cf3cfb5 [TASK] Add space between label and connection count 2017-09-02 23:01:42 +02:00
wahram
375627ab00 [TASK] Replace Math.pow to improve performance 2017-08-25 09:23:51 +02:00
Xaver Maierhofer
1995855693
[TASK] Upgrade leaflet 1.2 2017-08-15 17:41:58 +02:00
Xaver Maierhofer
1bd72654b8
[TASK] Navigo update & glup cli 2017-08-06 14:37:33 +02:00
Xaver Maierhofer
ea057cf90f
[TASK] Upgrade dependencies 2017-07-28 22:54:00 +02:00
Xaver Maierhofer
0cbf619c13
[TASK] Upgrade to eslint 4 2017-07-12 23:44:42 +02:00
Xaver Maierhofer
94b4a10bd4
[TASK] Leaflet 1.1 and minor updates 2017-07-12 23:36:13 +02:00
Xaver Maierhofer
b37ac2ddce
[TASK] Travis nodejs v8 2017-06-07 22:30:39 +02:00
Xaver Maierhofer
717ba27992
[TASK] Optimize favicons 2017-06-04 16:41:13 +02:00
Xaver Maierhofer
374e73998a
[TASK] Upgrade to navigo v5 & d3-zoom v1.3 2017-05-30 20:15:27 +02:00
Xaver Maierhofer
9c3f57dd3f [BUGFIX] Sasslint leading-zero 2017-05-29 23:41:25 +02:00
Xaver Maierhofer
6b67e4f714 [BUGFIX] Mobile button position 2017-05-28 20:15:12 +02:00
Xaver Maierhofer
2fba8c1ac9 [TASK] Add gulp-cli as dev dependency
Easier to deploy via provisioning
2017-05-25 23:54:19 +02:00
Xaver Maierhofer
8df5c2ce80 [TASK] Upgrade to uglify3 and minor lib updates 2017-05-24 16:15:50 +02:00
Martin Geno
3e63b6432e
[TASK] Add animation & improve behaviour of forcegraph 2017-05-16 23:43:15 +02:00
Xaver Maierhofer
0987b4b39a
[TASK] Update libarys d3 & autoprefixer 2017-05-14 22:57:52 +02:00
Martin Geno
d0c5fb2dab
[BUGFIX] check for gateways without mesh 2017-05-14 00:50:20 +02:00
Xaver Maierhofer
ecf73dd7ab [TASK] Add gateway filter 2017-05-13 14:49:34 +02:00
104 changed files with 6806 additions and 4289 deletions

View File

@ -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
View 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

View File

@ -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

View File

@ -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"] }]

View File

@ -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.

View File

@ -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. -->

View 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. -->

View File

@ -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

View File

@ -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
View 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/

View File

@ -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
View File

@ -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();
}); });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 624 B

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -1,5 +1,6 @@
{ {
"name": "Freifunk Regensburg", "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"
} }

View File

@ -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=\"Freifunk Regensburg\">\n<meta name=\"application-name\" content=\"Freifunk Regensburg\">\n<meta name=\"msapplication-TileColor\" content=\"#dc0067\">\n<meta name=\"msapplication-TileImage\" content=\"./mstile-144x144.png\">\n<meta name=\"msapplication-config\" content=\"./browserconfig.xml\">\n<meta name=\"theme-color\" content=\"#dc0067\">","compression":"true","overlapping_markups":["link[rel=\"apple-touch-icon\"]","meta[name=\"apple-mobile-web-app-title\"]","link[rel=\"shortcut\"]","link[rel=\"shortcut icon\"]","link[rel=\"icon\",sizes=\"16x16\"]","link[rel=\"icon\",sizes=\"32x32\"]","meta[name=\"msapplication-TileColor\"]","meta[name=\"msapplication-TileImage\"]","meta[name=\"msapplication-config\"]","meta[name=\"application-name\"]","link[rel=\"manifest\"]","meta[name=\"theme-color\"]","link[rel=\"mask-icon\"]"]},"files_location":{"type":"path","path":"."},"preview_picture_url":"https://realfavicongenerator.net/files/03dc81277d21a8ed4bb836b4c05ada2ee75b9e3c/favicon_preview.png","version":"0.14"} {"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"}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -7,9 +7,9 @@ $cache-breaker: unique-id();
font-family: 'ionicons'; font-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
View 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 -->'
};
};

View File

@ -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
View 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">&copy; Freifunk-Rhein-Sieg</a> <a href="http://www.openstreetmap.org/about/" target="_blank">&copy; OpenStreetMap contributors</a>'
}
},
{
'name': 'OSM',
'url': 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
'config': {
'maxZoom': 20,
'attribution': '<a href="http://www.openmaptiles.org/" target="_blank">&copy; OpenMapTiles</a> <a href="http://www.openstreetmap.org/about/" target="_blank">&copy; OpenStreetMap contributors</a>'
}
}
],
// Set a visible frame
'fixedCenter': [
// Northwest
[
50.8428,
7.0367
],
// Southeast
[
50.776,
7.1919
]
],
'domainNames': [
{
'site': 'tdf',
'name': 'Troisdorf'
},
{
'site': 'inn',
'name': 'Innenstadt'
},
{
'site': 'rifu',
'name': 'Richtfunk'
},
{
'site': 'flu',
'name': 'Soziale Netze'
}
],
'linkList': [
{
'title': 'Impressum',
'href': 'http://freifunk-troisdorf.de/kontakt/impressum/'
},
{
'title': 'Datenschutz',
'href': 'http://freifunk-troisdorf.de/datenschutz/'
}
]
};
};

View File

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

View File

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

View File

@ -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
}, },

View File

@ -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'));
}; };

View File

@ -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
}, },

View File

@ -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'
})) }))

View File

@ -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));
}; };

View File

@ -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
})) }))

View File

@ -1,23 +1,43 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html itemscope itemtype="http://schema.org/WebPage">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Freifunk Regensburg e.V. - Meshviewer</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <meta name="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 &amp; Knoten... Karten &amp; Knoten...
</p> </p>

23
html/offline.html Normal file
View 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>

View File

@ -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>' +

View File

@ -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
}; };
}; };
}); });

View File

@ -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);
}; };

View File

@ -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');

View File

@ -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;

View File

@ -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() {
draw.setHighlight(null); moveTo(function calcToReset() {
transform.k = (ZOOM_MIN + 1) / 2; draw.setHighlight(null);
moveTo(0, 0); return [0, 0, (ZOOM_MIN + config.forceGraph.zoomModifier) / 2];
redraw(); }, true);
}; };
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) {

View File

@ -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);
@ -76,7 +73,7 @@ define(['helper'], function (helper) {
var zero = transform.invert([0, 0]); var zero = transform.invert([0, 0]);
var area = transform.invert([width, height]); var area = transform.invert([width, height]);
if (d.source.x < zero[0] && d.target.x < zero[0] || d.source.y < zero[1] && d.target.y < zero[1] || if (d.source.x < zero[0] && d.target.x < zero[0] || d.source.y < zero[1] && d.target.y < zero[1] ||
d.source.x > area[0] && d.target.x > area[0] || d.source.y > area[1] && d.target.y > area[1]) { d.source.x > area[0] && d.target.x > area[0] || d.source.y > area[1] && d.target.y > area[1]) {
return; return;
} }
ctx.beginPath(); ctx.beginPath();
@ -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 {

View File

@ -1,131 +1,143 @@
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');
buttons.classList.add('buttons'); buttons.classList.add('buttons');
var fanout = new DataDistributor(); var fanout = new DataDistributor();
var fanoutUnfiltered = new DataDistributor(); var fanoutUnfiltered = new DataDistributor();
fanoutUnfiltered.add(fanout); fanoutUnfiltered.add(fanout);
function removeContent() { function removeContent() {
if (!content) { if (!content) {
return; return;
}
router.removeTarget(content);
fanout.remove(content);
content.destroy();
content = null;
} }
function addContent(K) { router.removeTarget(content);
removeContent(); fanout.remove(content);
content = new K(config, linkScale, sidebar.getWidth, router, buttons); content.destroy();
content.render(contentDiv);
fanout.add(content); content = null;
router.addTarget(content); }
function addContent(K) {
removeContent();
content = new K(linkScale, sidebar, buttons);
content.render(contentDiv);
fanout.add(content);
router.addTarget(content);
}
function mkView(K) {
return function () {
addContent(K);
};
}
var loader = document.getElementsByClassName('loader')[0];
loader.classList.add('hide');
contentDiv = document.createElement('div');
contentDiv.classList.add('content');
document.body.appendChild(contentDiv);
sidebar = new Sidebar(document.body);
contentDiv.appendChild(buttons);
var buttonToggle = document.createElement('button');
buttonToggle.classList.add('ion-eye');
buttonToggle.setAttribute('aria-label', _.t('button.switchView'));
buttonToggle.onclick = function onclick() {
var data;
if (content.constructor === Map) {
data = { view: 'graph', lat: undefined, lng: undefined, zoom: undefined };
} else {
data = { view: 'map' };
} }
router.fullUrl(data, false, true);
};
function mkView(K) { buttons.appendChild(buttonToggle);
return function () {
addContent(K);
};
}
var loader = document.getElementsByClassName('loader')[0]; if (config.fullscreen || config.fullscreenFrame && window.frameElement) {
loader.classList.add('hide'); var buttonFullscreen = document.createElement('button');
buttonFullscreen.classList.add('ion-full-enter');
contentDiv = document.createElement('div'); buttonFullscreen.setAttribute('aria-label', _.t('button.fullscreen'));
contentDiv.classList.add('content'); buttonFullscreen.onclick = function onclick() {
document.body.appendChild(contentDiv); helper.fullscreen(buttonFullscreen);
sidebar = new Sidebar(document.body);
contentDiv.appendChild(buttons);
var buttonToggle = document.createElement('button');
buttonToggle.classList.add('ion-eye', 'shadow');
buttonToggle.setAttribute('data-tooltip', _.t('button.switchView'));
buttonToggle.onclick = function onclick() {
var data;
if (content.constructor === Map) {
data = { view: 'graph', lat: undefined, lng: undefined, zoom: undefined };
} else {
data = { view: 'map' };
}
router.fullUrl(data, false, true);
}; };
buttons.appendChild(buttonToggle); buttons.appendChild(buttonFullscreen);
}
var title = new Title(config); 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);
fanout.add(nodelist); fanoutUnfiltered.add(infobox);
fanout.add(linklist); fanout.add(nodelist);
fanout.add(statistics); fanout.add(linklist);
fanout.add(statistics);
sidebar.add(header); sidebar.add(header);
header.add(legend); header.add(legend);
overview.add(newnodeslist); overview.add(newnodeslist);
overview.add(lostnodeslist); overview.add(lostnodeslist);
var filterGUI = new FilterGUI(fanout); var filterGUI = new FilterGUI(fanout);
fanout.watchFilters(filterGUI); fanout.watchFilters(filterGUI);
header.add(filterGUI); header.add(filterGUI);
var hostnameFilter = new HostnameFilter(); var hostnameFilter = new HostnameFilter();
fanout.addFilter(hostnameFilter); fanout.addFilter(hostnameFilter);
sidebar.add(tabs); sidebar.add(tabs);
tabs.add('sidebar.actual', overview); tabs.add('sidebar.actual', overview);
tabs.add('node.nodes', nodelist); tabs.add('node.nodes', nodelist);
tabs.add('node.links', linklist); tabs.add('node.links', linklist);
tabs.add('sidebar.stats', statistics); tabs.add('sidebar.stats', statistics);
tabs.add('sidebar.about', about); tabs.add('sidebar.about', about);
router.addTarget(title); router.addTarget(title);
router.addTarget(infobox); router.addTarget(infobox);
router.addView('map', mkView(Map)); router.addView('map', mkView(Map));
router.addView('graph', mkView(ForceGraph)); router.addView('graph', mkView(ForceGraph));
self.setData = fanoutUnfiltered.setData; self.setData = fanoutUnfiltered.setData;
return self; return self;
}; };
}); });

View File

@ -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;
}; };
}); });

View File

@ -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);
}; };

View File

@ -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;

View File

@ -1,297 +1,194 @@
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'),
helper.attributeEntry(attributes, 'node.ipAddresses', showIPs(d)); 'IPv4'
helper.attributeEntry(attributes, 'node.selectedGateway', helper.dictGet(d.statistics, ['gateway'])); ])
helper.attributeEntry(attributes, 'node.update', showAutoupdate(d)); ];
helper.attributeEntry(attributes, 'node.clients', showClients(d));
el.appendChild(attributes); if (node.gateway6 !== undefined) {
gatewayCols.push(V.h('span', [
nodeIdLink(node.gateway6),
V.h('br'),
'IPv6'
]));
}
if (d.neighbours.length > 0) { return V.h('td', { props: { className: 'gateway' } }, gatewayCols);
var h3 = document.createElement('h3');
h3.textContent = _.t('node.link', d.neighbours.length) + '(' + d.neighbours.length + ')';
el.appendChild(h3);
var headings = [{
name: ''
}, {
name: 'node.nodes',
sort: function (a, b) {
return a.node.nodeinfo.hostname.localeCompare(b.node.nodeinfo.hostname);
},
reverse: false
}, {
name: 'node.clients',
class: 'ion-people',
sort: function (a, b) {
return ('clients' in a.node.statistics ? a.node.statistics.clients : -1) -
('clients' in b.node.statistics ? b.node.statistics.clients : -1);
},
reverse: true
}, {
name: 'node.tq',
class: 'ion-connection-bars',
sort: function (a, b) {
return a.link.tq - b.link.tq;
},
reverse: true
}, {
name: 'node.distance',
class: 'ion-arrow-resize',
sort: function (a, b) {
return (a.link.distance === undefined ? -1 : a.link.distance) -
(b.link.distance === undefined ? -1 : b.link.distance);
},
reverse: true
}];
var table = new SortTable(headings, 1, renderNeighbourRow);
table.setData(d.neighbours);
table.el.elm.classList.add('node-links');
el.appendChild(table.el.elm);
} }
if (config.nodeInfos) { function renderNeighbourRow(n) {
config.nodeInfos.forEach(function (nodeInfo) { var icons = [V.h('span', { props: { className: 'icon ion-' + (n.link.type.indexOf('wifi') === 0 ? 'wifi' : 'share-alt'), title: _.t(n.link.type) } })];
var h4 = document.createElement('h4'); if (helper.hasLocation(n.node)) {
h4.textContent = nodeInfo.name; icons.push(V.h('span', { props: { className: 'ion-location', title: _.t('location.location') } }));
el.appendChild(h4); }
el.appendChild(showStatImg(nodeInfo, d));
return V.h('tr', [
V.h('td', icons),
V.h('td', nodeLink(n.node)),
V.h('td', n.node.clients),
V.h('td', [V.h('a', {
style: {
color: linkScale((n.link.source_tq + n.link.target_tq) / 2)
},
props: {
title: n.link.source.hostname + ' - ' + n.link.target.hostname,
href: router.generateLink({ link: n.link.id })
}, on: {
click: function (e) {
router.fullUrl({ link: n.link.id }, e);
}
}
}, helper.showTq(n.link.source_tq) + ' - ' + helper.showTq(n.link.target_tq))]),
V.h('td', helper.showDistance(n.link))
]);
}
var self = this;
var header = document.createElement('h2');
var table = document.createElement('table');
var images = document.createElement('div');
var neighbours = document.createElement('h3');
var headings = [{
name: '',
sort: function (a, b) {
return a.link.type.localeCompare(b.link.type);
}
}, {
name: 'node.nodes',
sort: function (a, b) {
return a.node.hostname.localeCompare(b.node.hostname);
},
reverse: false
}, {
name: 'node.clients',
class: 'ion-people',
sort: function (a, b) {
return a.node.clients - b.node.clients;
},
reverse: true
}, {
name: 'node.tq',
class: 'ion-connection-bars',
sort: function (a, b) {
return a.link.source_tq - b.link.source_tq;
},
reverse: true
}, {
name: 'node.distance',
class: 'ion-arrow-resize',
sort: function (a, b) {
return (a.link.distance === undefined ? -1 : a.link.distance) -
(b.link.distance === undefined ? -1 : b.link.distance);
},
reverse: true
}];
var tableNeighbour = new SortTable(headings, 1, renderNeighbourRow);
el.appendChild(header);
el.appendChild(table);
el.appendChild(neighbours);
el.appendChild(tableNeighbour.el);
el.appendChild(images);
self.render = function render() {
V.patch(header, V.h('h2', d.hostname));
var children = [];
config.nodeAttr.forEach(function (row) {
var field = d[row.value];
if (typeof row.value === 'function') {
field = row.value(d, nodeDict);
} else if (nodef['show' + row.value] !== undefined) {
field = nodef['show' + row.value](d);
}
if (field) {
if (typeof field !== 'object') {
field = V.h('td', field);
}
children.push(V.h('tr', [
row.name !== undefined ? V.h('th', _.t(row.name)) : null,
field
]));
}
}); });
}
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) {
var img = [];
config.nodeInfos.forEach(function (nodeInfo) {
img.push(V.h('h4', nodeInfo.name));
img.push(showStatImg(nodeInfo, d));
});
images = V.patch(images, V.h('div', img));
}
};
self.setData = function setData(data) {
if (data.nodeDict[d.node_id]) {
d = data.nodeDict[d.node_id];
}
self.render();
};
return self;
}; };
}); });

View File

@ -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;

View File

@ -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);
}; };
}; };
}); });

View File

@ -2,105 +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 });
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) {
@ -108,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, nodeDict: nodeDict
nodes: graph.nodes
}
}; };
} }
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();
@ -165,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);
}); });
}; };

View File

@ -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
View 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);
}
}
});
});

View File

@ -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) {

View File

@ -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;
} }
}); });

View File

@ -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();

View File

@ -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);

View File

@ -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;
return s; if (Math.abs(s) < 24) {
return Math.round(s) + ' h';
}
s /= 24;
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
}, { }, {
@ -52,40 +42,36 @@ define(['sorttable', 'snabbdom', 'helper'], function (SortTable, V, helper) {
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 = [];
var aClass = ['hostname', d.flags.online ? 'online' : 'offline'];
td1Content.push(V.h('a', {
props: {
className: aClass.join(' '),
href: router.generateLink({ node: d.nodeinfo.node_id })
}, on: {
click: function (e) {
router.fullUrl({ node: d.nodeinfo.node_id }, e);
}
}
}, d.nodeinfo.hostname));
if (helper.hasLocation(d)) { if (helper.hasLocation(d)) {
td0Content.push(V.h('span', { props: { className: 'icon ion-location' } })); td0Content = V.h('span', { props: { className: 'icon ion-location', title: _.t('location.location') } });
} }
var td0 = V.h('td', td0Content); var td1Content = V.h('a', {
var td1 = V.h('td', td1Content); props: {
var td2 = V.h('td', showUptime(d.uptime)); className: ['hostname', d.is_online ? 'online' : 'offline'].join(' '),
var td3 = V.h('td', d.neighbours.length); href: router.generateLink({ node: d.node_id })
var td4 = V.h('td', Number('clients' in d.statistics ? d.statistics.clients : 0).toFixed(0)); }, on: {
click: function (e) {
router.fullUrl({ node: d.node_id }, e);
}
}
}, d.hostname);
return V.h('tr', [td0, td1, td2, td3, td4]); return V.h('tr', [
V.h('td', td0Content),
V.h('td', td1Content),
V.h('td', showUptime(d.uptime)),
V.h('td', d.neighbours.length),
V.h('td', d.clients)
]);
} }
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.neighbours = e.neighbours; n.uptime = d.now - new Date(e.uptime).getTime();
} else {
n.uptime = e.lastseen - d.now;
}
return n; return n;
}); });

View File

@ -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';
V = V.default;
return function (config, filterManager) { return function (filterManager) {
var self = this; var self = this;
var scale = d3Interpolate.interpolate('#770038', '#dc0067'); var scale = d3Interpolate.interpolate(config.forceGraph.tqFrom, config.forceGraph.tqTo);
V = V.default; 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) {
var h2 = document.createElement('h2'); if (table.children.length > 0) {
h2.classList.add('proportion-header'); var h2 = document.createElement('h2');
h2.textContent = _.t(heading); h2.classList.add('proportion-header');
h2.onclick = function onclick() { h2.textContent = _.t(heading);
table.elm.classList.toggle('hide'); h2.onclick = function onclick() {
}; table.elm.classList.toggle('hide');
el.appendChild(h2); };
el.appendChild(table.elm); el.appendChild(h2);
el.appendChild(table.elm);
}
}; };
return self; return self;
}; };

View File

@ -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;
}; };

View File

@ -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 = [];
var td1Content = [];
var aClass = ['hostname', d.flags.online ? 'online' : 'offline'];
td1Content.push(V.h('a', {
props: {
className: aClass.join(' '),
href: router.generateLink({ node: d.nodeinfo.node_id })
}, on: {
click: function (e) {
router.fullUrl({ node: d.nodeinfo.node_id }, e);
}
}
}, d.nodeinfo.hostname));
if (helper.hasLocation(d)) { if (helper.hasLocation(d)) {
td0Content.push(V.h('span', { props: { className: 'icon ion-location' } })); td0Content = V.h('span', { props: { className: 'icon ion-location', title: _.t('location.location') } });
} }
var td0 = V.h('td', td0Content); var td1Content = V.h('a', {
var td1 = V.h('td', td1Content); props: {
var td2 = V.h('td', time); className: ['hostname', d.is_online ? 'online' : 'offline'].join(' '),
href: router.generateLink({ node: d.node_id })
}, on: {
click: function (e) {
router.fullUrl({ node: d.node_id }, e);
}
}
}, d.hostname);
return V.h('tr', [td0, td1, td2]); return V.h('tr', [
V.h('td', td0Content),
V.h('td', td1Content),
V.h('td', moment(d[field]).from(data.now))
]);
}); });
var tbodyNew = V.h('tbody', items); var tbodyNew = V.h('tbody', items);

View File

@ -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() {

View File

@ -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');
var th = document.createElement('th');
th.textContent = _.t(label);
tr.appendChild(th);
var td = document.createElement('td');
if (typeof value === 'function') {
value(td);
} else {
td.appendChild(document.createTextNode(value));
}
tr.appendChild(td);
el.appendChild(tr);
return td;
},
showStat: function showStat(o, subst) {
var content;
subst = typeof subst !== 'undefined' ? subst : {};
content = document.createElement('img');
content.src = require('helper').listReplace(o.image, subst);
var p = document.createElement('p');
if (o.href) {
var link = document.createElement('a');
link.target = '_blank';
link.href = require('helper').listReplace(o.href, subst);
link.appendChild(content);
if (o.title) {
link.title = require('helper').listReplace(o.title, subst);
} }
p.appendChild(link); children.push(V.h('tr', [
} else { V.h('th', _.t(label)),
p.appendChild(content); value
]));
} }
},
showStat: function showStat(V, o, subst) {
var content = V.h('img', { attrs: { src: require('helper').listReplace(o.image, subst) } });
return p; if (o.href) {
return V.h('p', V.h('a', {
attrs:
{
href: require('helper').listReplace(o.href, subst),
target: '_blank',
title: require('helper').listReplace(o.title, subst)
}
}, content));
}
return V.h('p', content);
}, },
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, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&#34;')
.replace(/'/g, '&#39;');
} }
}); });

View File

@ -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
}; };
}; };

View File

@ -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
View 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;
});

View File

@ -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
View 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
View 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"
}

View File

@ -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"
} }

View File

@ -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"
} }

View File

@ -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 laffichage", "switchView": "Basculer laffichage",
@ -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"
} }

View File

@ -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
View 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"
}

View File

@ -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.3", "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": "^7.0.0", "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.6", "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.7.1", "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"
]
} }

View File

@ -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');
}

View File

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

View File

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

View File

@ -3,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';

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}
}
}
}

View File

@ -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 {

View File

@ -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,

View File

@ -1,5 +1,5 @@
header { header {
h2 { h1 {
display: inline-block; display: inline-block;
} }
} }
@ -17,13 +17,23 @@ header {
} }
.legend { .legend {
.symbol { a {
border-radius: 50%; margin-right: 10px;
display: inline-block;
height: 1em;
vertical-align: -5%;
width: 1em;
} }
span {
&:not(:first-child) {
margin-left: 1em;
}
}
}
.symbol {
border-radius: 50%;
display: inline-block;
height: 1em;
vertical-align: -5%;
width: 1em;
} }
// Dot looks compared to thin font a bit darker - lighten it 10% // 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;
}
} }

View File

@ -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;
} }
} }

View File

@ -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 {

View File

@ -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;
}
} }

View File

@ -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;
}

View File

@ -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;

View File

@ -1,11 +1,7 @@
.tabs { .tabs {
@if $shadows == 1 {
@include shadow(1);
} @else {
border: 0 solid darken($color-white, 10%);
border-bottom-width: 1px;
}
background: transparentize($color-black, .98); background: transparentize($color-black, .98);
border: 0 solid darken($color-white, 10%);
border-bottom-width: 1px;
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;

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