mirror of
https://github.com/octoleo/syncthing.git
synced 2024-12-23 11:28:59 +00:00
387 lines
16 KiB
HTML
387 lines
16 KiB
HTML
<!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 href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
|
|
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.0.13/css/all.css"/>
|
|
|
|
<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="//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>
|
|
Currently {{ relays.length }} relays online ({{ totals.goMaxProcs }} cores in total).
|
|
</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 transferred relative 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="http://www.maxmind.com">http://www.maxmind.com</a>.
|
|
</p>
|
|
</div>
|
|
|
|
|
|
<script type="text/javascript" src="//code.jquery.com/jquery-2.1.4.min.js"></script>
|
|
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
|
|
<script type="text/javascript" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
|
<script type="text/javascript" src="//maps.googleapis.com/maps/api/js?key=AIzaSyDk5WJ8s7ueLKb99X5DbQ-vkWtPDAKqYs0"></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 = new google.maps.Map(document.getElementById('map'), {
|
|
zoom: 1,
|
|
mapTypeId: google.maps.MapTypeId.ROADMAP
|
|
});
|
|
$scope.mapBounds = new google.maps.LatLngBounds();
|
|
$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);
|
|
}
|
|
});
|
|
|
|
$scope.map.fitBounds($scope.mapBounds);
|
|
if ($scope.relays.length == 1) {
|
|
$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 google.maps.Marker({
|
|
map: $scope.map,
|
|
position: new google.maps.LatLng(locParts[0], locParts[1]),
|
|
title: relay.url,
|
|
});
|
|
|
|
var scope = $rootScope.$new(true);
|
|
scope.relay = relay;
|
|
|
|
relay.marker.info = new google.maps.InfoWindow({
|
|
content: $compile($scope.tooltipTemplate)(scope)[0],
|
|
});
|
|
|
|
relay.showMarker = function() {
|
|
relay.marker.info.open($scope.map, relay.marker);
|
|
}
|
|
|
|
relay.hideMarker = function() {
|
|
relay.marker.info.close();
|
|
}
|
|
|
|
relay.marker.addListener('mouseover', relay.showMarker);
|
|
relay.marker.addListener('mouseout', relay.hideMarker);
|
|
|
|
$scope.mapBounds.extend(relay.marker.position);
|
|
}
|
|
|
|
function addCircleToMap(relay) {
|
|
relay.marker.circle = new google.maps.Circle({
|
|
strokeColor: '#FF0000',
|
|
strokeOpacity: 0.8,
|
|
strokeWeight: 2,
|
|
fillColor: '#FF0000',
|
|
fillOpacity: 0.35,
|
|
map: $scope.map,
|
|
center: relay.marker.position,
|
|
radius: ((relay.stats.bytesProxied * 100) / $scope.totals.bytesProxied) * 10000
|
|
});
|
|
}
|
|
|
|
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>
|