| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- <!DOCTYPE html>
- <html lang="en" ng-app="syncthing" ng-controller="relayDataController">
- <head>
- <meta charset="utf-8"/>
- <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
- <meta name="description" content=""/>
- <meta name="author" content=""/>
- <title>Relay stats</title>
- <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css"/>
- <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
- <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css"/>
- <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"
- integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
- crossorigin=""/>
- <script src="https://unpkg.com/[email protected]/dist/leaflet.js"
- integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
- crossorigin=""></script>
- <style>
- #map {
- height: 600px;
- }
- .ng-cloak {
- display: none;
- }
- table {
- font-size: 11px !important;
- width: 100%;
- border: 1px;
- }
- td {
- padding: 0px !important;
- }
- tfoot td {
- font-weight: bold;
- }
- </style>
- </head>
- <body class="ng-cloak">
- <div class="container">
- <h1>Relay Pool Data</h1>
- <div ng-if="relays === undefined" class="text-center">
- <img src="https://cdnjs.cloudflare.com/ajax/libs/galleriffic/2.0.1/css/loader.gif" alt=""/>
- <p>Please wait while we gather data…</p>
- </div>
- <div>
- <div ng-show="relays !== undefined" class="ng-hide">
- <p>
- The relays listed on this page are not managed or vetted by the Syncthing project.
- Each relay is the responsibility of the relay operator.
- Currently {{ relays.length }} relays are online.
- </p>
- </div>
- <div id="map"></div> <!-- Can't hide the map, otherwise it freaks out -->
- <p>The circle size represents how much bytes the relay has transferred relatively to other relays.</p>
- </div>
- <div>
- <table class="table table-striped table-condensed table">
- <thead>
- <tr>
- <th rowspan="2">Address</td>
- <th rowspan="2">
- <a ng-click="sortType = 'stats.numActiveSessions'; sortReverse = !sortReverse">
- Sessions
- <span ng-show="sortType == 'stats.numActiveSessions' && !sortReverse" class="fas fa-caret-down"></span>
- <span ng-show="sortType == 'stats.numActiveSessions' && sortReverse" class="fas fa-caret-up"></span>
- </a>
- </th>
- <th rowspan="2">
- <a ng-click="sortType = 'stats.numConnections'; sortReverse = !sortReverse">
- Connections
- <span ng-show="sortType == 'stats.numConnections' && !sortReverse" class="fas fa-caret-down"></span>
- <span ng-show="sortType == 'stats.numConnections' && sortReverse" class="fas fa-caret-up"></span>
- </a>
- </th>
- <th rowspan="2">
- <a ng-click="sortType = 'stats.bytesProxied'; sortReverse = !sortReverse">
- Data relayed
- <span ng-show="sortType == 'stats.bytesProxied' && !sortReverse" class="fas fa-caret-down"></span>
- <span ng-show="sortType == 'stats.bytesProxied' && sortReverse" class="fas fa-caret-up"></span>
- </a>
- </th>
- <th colspan="6" class="text-center">Transfer rate in the last period</th>
- <th rowspan="2">
- <a ng-click="sortType = 'stats.uptimeSeconds'; sortReverse = !sortReverse">
- Uptime hours
- <span ng-show="sortType == 'stats.uptimeSeconds' && !sortReverse" class="fas fa-caret-down"></span>
- <span ng-show="sortType == 'status.uptimeSeconds' && sortReverse" class="fas fa-caret-up"></span>
- </a>
- </th>
- <th rowspan="2">
- <a ng-click="sortType = 'stats.options[\'provided-by\'] || \'\''; sortReverse = !sortReverse">
- Provided by
- <span ng-show="sortType == 'stats.options[\'provided-by\'] || \'\'' && !sortReverse" class="fas fa-caret-down"></span>
- <span ng-show="sortType == 'stats.options[\'provided-by\'] || \'\'' && sortReverse" class="fas fa-caret-up"></span>
- </a>
- </th>
- </tr>
- <tr>
- <th>
- <a ng-click="sortType = 'stats.kbps10s1m5m15m30m60m[0]'; sortReverse = !sortReverse">
- 10s
- <span ng-show="sortType == 'stats.kbps10s1m5m15m30m60m[0]' && !sortReverse" class="fas fa-caret-down"></span>
- <span ng-show="sortType == 'stats.kbps10s1m5m15m30m60m[0]' && sortReverse" class="fas fa-caret-up"></span>
- </a>
- </th>
- <th>
- <a ng-click="sortType = 'stats.kbps10s1m5m15m30m60m[1]'; sortReverse = !sortReverse">
- 1m
- <span ng-show="sortType == 'stats.kbps10s1m5m15m30m60m[1]' && !sortReverse" class="fas fa-caret-down"></span>
- <span ng-show="sortType == 'stats.kbps10s1m5m15m30m60m[1]' && sortReverse" class="fas fa-caret-up"></span>
- </a>
- </th>
- <th>
- <a ng-click="sortType = 'stats.kbps10s1m5m15m30m60m[2]'; sortReverse = !sortReverse">
- 5m
- <span ng-show="sortType == 'stats.kbps10s1m5m15m30m60m[2]' && !sortReverse" class="fas fa-caret-down"></span>
- <span ng-show="sortType == 'stats.kbps10s1m5m15m30m60m[2]' && sortReverse" class="fas fa-caret-up"></span>
- </a>
- </th>
- <th>
- <a ng-click="sortType = 'stats.kbps10s1m5m15m30m60m[3]'; sortReverse = !sortReverse">
- 15m
- <span ng-show="sortType == 'stats.kbps10s1m5m15m30m60m[3]' && !sortReverse" class="fas fa-caret-down"></span>
- <span ng-show="sortType == 'stats.kbps10s1m5m15m30m60m[3]' && sortReverse" class="fas fa-caret-up"></span>
- </a>
- </th>
- <th>
- <a ng-click="sortType = 'stats.kbps10s1m5m15m30m60m[4]'; sortReverse = !sortReverse">
- 30m
- <span ng-show="sortType == 'stats.kbps10s1m5m15m30m60m[4]' && !sortReverse" class="fas fa-caret-down"></span>
- <span ng-show="sortType == 'stats.kbps10s1m5m15m30m60m[4]' && sortReverse" class="fas fa-caret-up"></span>
- </a>
- </th>
- <th>
- <a ng-click="sortType = 'stats.kbps10s1m5m15m30m60m[5]'; sortReverse = !sortReverse">
- 60m
- <span ng-show="sortType == 'stats.kbps10s1m5m15m30m60m[5]' && !sortReverse" class="fas fa-caret-down"></span>
- <span ng-show="sortType == 'stats.kbps10s1m5m15m30m60m[5]' && sortReverse" class="fas fa-caret-up"></span>
- </a>
- </th>
- </tr>
- </thead>
- <tbody>
- <tr ng-repeat="relay in relays | orderBy:sortType:sortReverse:sortCompare" ng-mouseover="relay.showMarker()" ng-mouseleave="relay.hideMarker()">
- <td>{{ relay.address }}</td>
- <td ng-if="!relay.stats" colspan="11"></td>
- <td ng-if-start="relay.stats">{{ relay.stats.numActiveSessions }}</td>
- <td>{{ relay.stats.numConnections }}</td>
- <td>{{ relay.stats.bytesProxied | bytes }}</td>
- <td>{{ relay.stats.kbps10s1m5m15m30m60m[0] * 128 | bytes }}/s</td>
- <td>{{ relay.stats.kbps10s1m5m15m30m60m[1] * 128 | bytes }}/s</td>
- <td>{{ relay.stats.kbps10s1m5m15m30m60m[2] * 128 | bytes }}/s</td>
- <td>{{ relay.stats.kbps10s1m5m15m30m60m[3] * 128 | bytes }}/s</td>
- <td>{{ relay.stats.kbps10s1m5m15m30m60m[4] * 128 | bytes }}/s</td>
- <td>{{ relay.stats.kbps10s1m5m15m30m60m[5] * 128 | bytes }}/s</td>
- <td ng-if="relay.stats.uptimeSeconds != undefined">{{ relay.stats.uptimeSeconds/60/60 | number:0 }}</td>
- <td ng-if="relay.stats.uptimeSeconds == undefined"></td>
- <td title="{{ relay.stats.options['provided-by'] || '' }}" ng-if-end>
- {{ relay.stats.options['provided-by'] || '' | limitTo:50 }}
- <span ng-if="(relay.stats.options['provided-by'] || '').length > 50">…
- </td>
- </tr>
- </tbody>
- <tfoot>
- <tr>
- <td>Totals</td>
- <td>{{ totals.numActiveSessions }}</td>
- <td>{{ totals.numConnections }}</td>
- <td>{{ totals.bytesProxied | bytes }}</td>
- <td>{{ totals.kbps10s1m5m15m30m60m[0] * 128 | bytes }}/s</td>
- <td>{{ totals.kbps10s1m5m15m30m60m[1] * 128 | bytes }}/s</td>
- <td>{{ totals.kbps10s1m5m15m30m60m[2] * 128 | bytes }}/s</td>
- <td>{{ totals.kbps10s1m5m15m30m60m[3] * 128 | bytes }}/s</td>
- <td>{{ totals.kbps10s1m5m15m30m60m[4] * 128 | bytes }}/s</td>
- <td>{{ totals.kbps10s1m5m15m30m60m[5] * 128 | bytes }}/s</td>
- <td>{{ totals.uptimeSeconds/60/60 | number:0 }} hours</td>
- <td>{{ relays.length }} relays</td>
- </tr>
- </tfoor>
- </table>
- </div>
- <hr>
- <p>
- This product includes GeoLite2 data created by MaxMind, available from
- <a href="https://www.maxmind.com">https://www.maxmind.com</a>.
- </p>
- </div>
- <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
- <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
- <script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
- </body>
- <script>
- angular.module('syncthing', [
- ])
- .config(['$httpProvider', function($httpProvider) {
- $httpProvider.defaults.timeout = 5000;
- }])
- .filter('bytes', function() {
- return function(bytes, precision) {
- if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) return '-';
- if (typeof precision === 'undefined') precision = 1;
- var units = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'],
- number = Math.floor(Math.log(bytes) / Math.log(1024));
- var value = (bytes / Math.pow(1000, Math.floor(number)));
- if (!isFinite(value)) {
- value = 0;
- precision = 0;
- }
- if (!isFinite(number)) {
- units = 'bytes';
- } else {
- units = units[number];
- }
- return value.toFixed(precision) + ' ' + units;
- }
- })
- .controller('relayDataController', ['$scope', '$rootScope', '$http', '$q', '$compile', '$timeout', function($scope, $rootScope, $http, $q, $compile, $timeout) {
- $scope.totals = {
- bytesProxied: 0,
- goMaxProcs: 0,
- kbps10s1m5m15m30m60m: [0, 0, 0, 0, 0, 0],
- numActiveSessions: 0,
- numConnections: 0,
- numPendingSessionKeys: 0,
- numProxies: 0,
- uptimeSeconds: 0,
- };
- $scope.map = L.map('map').setView([40.90296, 1.90925], 2);
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
- {
- attribution: 'Leaflet',
- maxZoom: 17
- }).addTo($scope.map);
- $scope.tooltipTemplate = $('#infoTemplate').html();
- $scope.usedLocations = {};
- $scope.sortType = 'stats.numActiveSessions';
- $scope.sortReverse = true;
- $scope.sortCompare = function(a, b) {
- if (a.value == b.value) {
- return 0;
- }
- if (a.type == "undefined" || a.type == "null") {
- return -1;
- }
- if (b.type == "undefined" || b.type == "null") {
- return 1;
- }
- return a.value > b.value ? 1 : -1;
- }
- $http.get("/endpoint").then(function(response) {
- $scope.relays = response.data.relays;
- angular.forEach($scope.relays, function(relay) {
- relay.uri = constructURI(relay.url);
- relay.address = relay.url.split('/')[2];
- addMarkerToMap(relay);
- if (relay.stats) {
- angular.forEach($scope.totals, function(value, key) {
- if (typeof $scope.totals[key] == 'number') {
- $scope.totals[key] += relay.stats[key];
- } else if (typeof $scope.totals[key] == 'object' && $scope.totals[key] instanceof Array) {
- angular.forEach($scope.totals[key], function(value, index) {
- $scope.totals[key][index] += relay.stats[key][index];
- });
- }
- });
- }
- });
- // After the totals were calculated, add circles.
- angular.forEach($scope.relays, function(relay) {
- if (relay.stats) {
- addCircleToMap(relay);
- }
- });
- if ($scope.relays.length == 1) {
- //Center to only relay with zoom
- $scope.map.panTo(new L.LatLng(relays[0].location.latitude, relays[0].location.longitude));
- $scope.map.setZoom(13);
- }
- });
- function addMarkerToMap(relay) {
- var loc = relay.location.latitude + "," + relay.location.longitude;
- // Deal with overlapping markers
- while (loc in $scope.usedLocations) {
- var locParts = loc.split(',');
- locParts = [parseFloat(locParts[0]), parseFloat(locParts[1])];
- locParts[Math.round(Math.random())] += 0.5 * (Math.random() >= 0.5 ? 1 : -1);
- loc = locParts.join(',');
- }
- $scope.usedLocations[loc] = true;
- var locParts = loc.split(',');
- relay.marker = new L.Marker([relay.location.latitude, relay.location.longitude],{
- title: relay.url,
- });
- var scope = $rootScope.$new(true);
- scope.relay = relay;
- var icon = new L.Icon({
- iconSize: [18, 28], // size of the icon
- iconAnchor: [9, 28], // point of the icon which will correspond to marker's location
- shadowAnchor: [0, 0], // the same for the shadow
- popupAnchor: [0, -27], // popup anchor
- shadowSize: [0,0],
- iconUrl: 'https://cdn.rawgit.com/pointhi/leaflet-color-markers/master/img/marker-icon-red.png',
- shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
- });
- relay.marker = new L.marker(new L.latLng(locParts[0], locParts[1]),{icon})
- .bindPopup($compile($scope.tooltipTemplate)(scope)[0],{})
- .on('mouseover', function (e) {
- this.openPopup();
- }).on('mouseout', function (e) {
- this.closePopup();
- }).addTo($scope.map);
- relay.showMarker = function() {
- relay.marker.openPopup();
- }
-
- relay.hideMarker = function() {
- relay.marker.closePopup();
- }
- }
- function addCircleToMap(relay) {
- console.log(relay.location.latitude)
- L.circle([relay.location.latitude, relay.location.longitude],
- {
- radius: ((relay.stats.bytesProxied * 100) / $scope.totals.bytesProxied) * 10000,
- color: "FF0000",
- fillColor: "#FF0000",
- fillOpacity: 0.35,
- }).addTo($scope.map);
- }
- function constructURI(url) {
- var uri = document.createElement('a');
- // HAX, otherwise doesn't work
- uri.href = url.replace('relay://', 'http://');
- // Convert query string to object
- uri.args = {};
- angular.forEach(uri.search.replace(/^\?/, '').split('&'), function(query) {
- var split = query.split('=');
- uri.args[split[0]] = split[1];
- });
- return uri;
- }
- }]);
- </script>
- <script type="text/template" id="infoTemplate">
- <div>
- <p><b>{{ relay.uri.hostname }}</b> <span ng-if="relay.stats.options['provided-by']">provided by <u>{{ relay.stats.options['provided-by'] }}</u></span></p>
- <div ng-if="relay.stats">
- <span ng-if="relay.stats.startTime">Start time: {{ relay.stats.startTime | date:"medium" }}</br></span>
- <span ng-if="relay.stats.bytesProxied != undefined">Proxied: {{ relay.stats.bytesProxied | bytes }}</br></span>
- <span ng-if="relay.stats.numActiveSessions != undefined">Sessions: {{ relay.stats.numActiveSessions }}</br></span>
- <span ng-if="relay.stats.numConnections != undefined">Clients: {{ relay.stats.numConnections }}</br></span>
- <span ng-if="relay.stats.options.pools">Pools: {{ relay.stats.options.pools.join(', ') }}</br></span>
- <span ng-if="relay.stats.options['global-rate'] != undefined">
- <span ng-if="relay.stats.options['global-rate'] > 0">Global rate limit: {{ relay.stats.options['global-rate'] | bytes }}/s</span>
- <span ng-if="relay.stats.options['global-rate'] == 0">Global rate limit: unlimited</span>
- <br/>
- </span>
- <span ng-if="relay.stats.options['per-session-rate'] != undefined">
- <span ng-if="relay.stats.options['per-session-rate'] > 0">Session rate limit: {{ relay.stats.options['per-session-rate'] | bytes }}/s</span>
- <span ng-if="relay.stats.options['per-session-rate'] == 0">Session rate limit: unlimited</span>
- <br/>
- </span>
- </div>
- <div ng-if="!relay.stats">
- Data unavailable.
- <div>
- </div>
- </script>
- </html>
|