Archived
Refactor javascript, always show table, add sorting
This commit is contained in:
1 parent
50068b0b0f
commit
dd364c962f
2 files changed
+239
-182
No files matched your search
@@ -5,12 +5,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
AssetsBuildDate = "Wed, 13 Apr 2016 20:30:55 GMT"
|
||||
AssetsBuildDate = "Thu, 14 Apr 2016 21:00:33 GMT"
|
||||
)
|
||||
|
||||
func Assets() map[string][]byte {
|
||||
var assets = make(map[string][]byte, 1)
|
||||
|
||||
assets["index.html"], _ = base64.StdEncoding.DecodeString("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")
|
||||
assets["index.html"], _ = base64.StdEncoding.DecodeString("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 truncated
|
||||
return assets
|
||||
}
|
||||
+237
-180
@@ -19,9 +19,13 @@
|
||||
display: none;
|
||||
}
|
||||
table {
|
||||
font-size: 11px;
|
||||
font-size: 11px !important;
|
||||
width: 100%;
|
||||
border: 1px;
|
||||
|
||||
}
|
||||
td {
|
||||
padding: 0px !important;
|
||||
}
|
||||
tfoot td {
|
||||
font-weight: bold;
|
||||
@@ -32,57 +36,125 @@
|
||||
<body class="ng-cloak">
|
||||
<div class="container">
|
||||
<h1>Relay Pool Data</h2>
|
||||
<div ng-if="!started" class="text-center">
|
||||
<div ng-if="relays === undefined" class="text-center">
|
||||
<img src="//cdnjs.cloudflare.com/ajax/libs/galleriffic/2.0.1/css/loader.gif"/>
|
||||
<p>Please wait while we gather data</p>
|
||||
<p ng-repeat="entry in progress" class="ng-cloak">{{ entry.name }}... <span ng-if="entry.done">Done!</span></p>
|
||||
</div>
|
||||
<div>
|
||||
<div ng-show="started" class="ng-hide">
|
||||
<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 ng-show="started" class="ng-hide">The circle size represents how much bytes the relay transfered relative to other relays</p>
|
||||
<p>The circle size represents how much bytes the relay transfered relative to other relays</p>
|
||||
</div>
|
||||
<div ng-show="started" class="ng-hide">
|
||||
<table>
|
||||
<div>
|
||||
<table class="table table-striped table-condensed table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4"></th>
|
||||
<th colspan="6"">Transfer rate in the last period (per second)</th>
|
||||
<th colspan="2"></th>
|
||||
<tr/>
|
||||
<th rowspan="2">Address</td>
|
||||
<th rowspan="2">
|
||||
<a ng-click="sortType = 'status.numActiveSessions || 0'; sortReverse = !sortReverse">
|
||||
Sessions
|
||||
<span ng-show="sortType == 'status.numActiveSessions || 0' && !sortReverse" class="fa fa-caret-down"></span>
|
||||
<span ng-show="sortType == 'status.numActiveSessions || 0' && sortReverse" class="fa fa-caret-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th rowspan="2">
|
||||
<a ng-click="sortType = 'status.numConnections || 0'; sortReverse = !sortReverse">
|
||||
Connections
|
||||
<span ng-show="sortType == 'status.numConnections || 0' && !sortReverse" class="fa fa-caret-down"></span>
|
||||
<span ng-show="sortType == 'status.numConnections || 0' && sortReverse" class="fa fa-caret-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th rowspan="2">
|
||||
<a ng-click="sortType = 'status.bytesProxied || 0'; sortReverse = !sortReverse">
|
||||
Data relayed
|
||||
<span ng-show="sortType == 'status.bytesProxied || 0' && !sortReverse" class="fa fa-caret-down"></span>
|
||||
<span ng-show="sortType == 'status.bytesProxied || 0' && sortReverse" class="fa 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 = 'status.uptimeSeconds || 0'; sortReverse = !sortReverse">
|
||||
Uptime hours
|
||||
<span ng-show="sortType == 'status.uptimeSeconds || 0' && !sortReverse" class="fa fa-caret-down"></span>
|
||||
<span ng-show="sortType == 'status.uptimeSeconds || 0' && sortReverse" class="fa fa-caret-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th rowspan="2">
|
||||
<a ng-click="sortType = 'status.options[\'provided-by\'] || \'\''; sortReverse = !sortReverse">
|
||||
Provided by
|
||||
<span ng-show="sortType == 'status.options[\'provided-by\'] || \'\'' && !sortReverse" class="fa fa-caret-down"></span>
|
||||
<span ng-show="sortType == 'status.options[\'provided-by\'] || \'\'' && sortReverse" class="fa fa-caret-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Address</th>
|
||||
<th>Active Sessions</th>
|
||||
<th>Connections</th>
|
||||
<th>Data proxied</th>
|
||||
<th>10s</th>
|
||||
<th>1m</th>
|
||||
<th>5m</th>
|
||||
<th>15m</th>
|
||||
<th>30m</th>
|
||||
<th>60m</th>
|
||||
<th>Uptime</th>
|
||||
<th>Provided by</th>
|
||||
<th>
|
||||
<a ng-click="sortType = 'status.kbps10s1m5m15m30m60m[0] || 0'; sortReverse = !sortReverse">
|
||||
10s
|
||||
<span ng-show="sortType == 'status.kbps10s1m5m15m30m60m[0] || 0' && !sortReverse" class="fa fa-caret-down"></span>
|
||||
<span ng-show="sortType == 'status.kbps10s1m5m15m30m60m[0] || 0' && sortReverse" class="fa fa-caret-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="sortType = 'status.kbps10s1m5m15m30m60m[1] || 0'; sortReverse = !sortReverse">
|
||||
1m
|
||||
<span ng-show="sortType == 'status.kbps10s1m5m15m30m60m[1] || 0' && !sortReverse" class="fa fa-caret-down"></span>
|
||||
<span ng-show="sortType == 'status.kbps10s1m5m15m30m60m[1] || 0' && sortReverse" class="fa fa-caret-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="sortType = 'status.kbps10s1m5m15m30m60m[2] || 0'; sortReverse = !sortReverse">
|
||||
5m
|
||||
<span ng-show="sortType == 'status.kbps10s1m5m15m30m60m[2] || 0' && !sortReverse" class="fa fa-caret-down"></span>
|
||||
<span ng-show="sortType == 'status.kbps10s1m5m15m30m60m[2] || 0' && sortReverse" class="fa fa-caret-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="sortType = 'status.kbps10s1m5m15m30m60m[3] || 0'; sortReverse = !sortReverse">
|
||||
15m
|
||||
<span ng-show="sortType == 'status.kbps10s1m5m15m30m60m[3] || 0' && !sortReverse" class="fa fa-caret-down"></span>
|
||||
<span ng-show="sortType == 'status.kbps10s1m5m15m30m60m[3] || 0' && sortReverse" class="fa fa-caret-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="sortType = 'status.kbps10s1m5m15m30m60m[4] || 0'; sortReverse = !sortReverse">
|
||||
30m
|
||||
<span ng-show="sortType == 'status.kbps10s1m5m15m30m60m[4] || 0' && !sortReverse" class="fa fa-caret-down"></span>
|
||||
<span ng-show="sortType == 'status.kbps10s1m5m15m30m60m[4] || 0' && sortReverse" class="fa fa-caret-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="sortType = 'status.kbps10s1m5m15m30m60m[5] || 0'; sortReverse = !sortReverse">
|
||||
60m
|
||||
<span ng-show="sortType == 'status.kbps10s1m5m15m30m60m[5] || 0' && !sortReverse" class="fa fa-caret-down"></span>
|
||||
<span ng-show="sortType == 'status.kbps10s1m5m15m30m60m[5] || 0' && sortReverse" class="fa fa-caret-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="relay in relays">
|
||||
<td>{{ relay.url.split('/')[2] }}</td>
|
||||
<td>{{ status[relay.url].numActiveSessions }}</td>
|
||||
<td>{{ status[relay.url].numConnections }}</td>
|
||||
<td>{{ status[relay.url].bytesProxied | bytes }}</td>
|
||||
<td>{{ status[relay.url].kbps10s1m5m15m30m60m[0] * 128 | bytes }}/s</td>
|
||||
<td>{{ status[relay.url].kbps10s1m5m15m30m60m[1] * 128 | bytes }}/s</td>
|
||||
<td>{{ status[relay.url].kbps10s1m5m15m30m60m[2] * 128 | bytes }}/s</td>
|
||||
<td>{{ status[relay.url].kbps10s1m5m15m30m60m[3] * 128 | bytes }}/s</td>
|
||||
<td>{{ status[relay.url].kbps10s1m5m15m30m60m[4] * 128 | bytes }}/s</td>
|
||||
<td>{{ status[relay.url].kbps10s1m5m15m30m60m[5] * 128 | bytes }}/s</td>
|
||||
<td ng-if="status[relay.url].uptimeSeconds != undefined">{{ status[relay.url].uptimeSeconds/60/60 | number:0 }} hours</td>
|
||||
<td ng-if="status[relay.url].uptimeSeconds == undefined"></td>
|
||||
<td>{{ status[relay.url].options['provided-by'] || '' }}</td>
|
||||
<tr ng-repeat="relay in relays | orderBy:sortType:sortReverse ">
|
||||
<td>{{ relay.address }}</td>
|
||||
<td ng-if="relay.status === undefined" colspan="11" class="text-center">Looking up...</td>
|
||||
<td ng-if-start="relay.status !== undefined">{{ relay.status.numActiveSessions }}</td>
|
||||
<td>{{ relay.status.numConnections }}</td>
|
||||
<td>{{ relay.status.bytesProxied | bytes }}</td>
|
||||
<td>{{ relay.status.kbps10s1m5m15m30m60m[0] * 128 | bytes }}/s</td>
|
||||
<td>{{ relay.status.kbps10s1m5m15m30m60m[1] * 128 | bytes }}/s</td>
|
||||
<td>{{ relay.status.kbps10s1m5m15m30m60m[2] * 128 | bytes }}/s</td>
|
||||
<td>{{ relay.status.kbps10s1m5m15m30m60m[3] * 128 | bytes }}/s</td>
|
||||
<td>{{ relay.status.kbps10s1m5m15m30m60m[4] * 128 | bytes }}/s</td>
|
||||
<td>{{ relay.status.kbps10s1m5m15m30m60m[5] * 128 | bytes }}/s</td>
|
||||
<td ng-if="relay.status.uptimeSeconds != undefined">{{ relay.status.uptimeSeconds/60/60 | number:0 }}</td>
|
||||
<td ng-if="relay.status.uptimeSeconds == undefined"></td>
|
||||
<td title="{{ relay.status.options['provided-by'] || '' }}" ng-if-end>
|
||||
{{ relay.status.options['provided-by'] || '' | limitTo:50 }}
|
||||
<span ng-if="(relay.status.options['provided-by'] || '').length > 50">…
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
@@ -98,7 +170,7 @@
|
||||
<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></td>
|
||||
<td>{{ relays.length }} relays</td>
|
||||
</tr>
|
||||
</tfoor>
|
||||
</table>
|
||||
@@ -145,11 +217,6 @@
|
||||
}
|
||||
})
|
||||
.controller('relayDataController', ['$scope', '$rootScope', '$http', '$q', '$compile', '$timeout', function($scope, $rootScope, $http, $q, $compile, $timeout) {
|
||||
$scope.started = false;
|
||||
$scope.geoip = {};
|
||||
$scope.status = {};
|
||||
$scope.uri = {};
|
||||
$scope.progress = [];
|
||||
$scope.totals = {
|
||||
bytesProxied: 0,
|
||||
goMaxProcs: 0,
|
||||
@@ -160,176 +227,166 @@
|
||||
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 = 'status.numActiveSessions || 0';
|
||||
$scope.sortReverse = true;
|
||||
|
||||
var usedLocs = {};
|
||||
|
||||
function initProgress(name) {
|
||||
$scope.progress.push({name: name, done: false});
|
||||
}
|
||||
|
||||
function progressDone(name) {
|
||||
angular.forEach($scope.progress, function(progress) {
|
||||
if (progress.name == name) {
|
||||
progress.done = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var map;
|
||||
var template = $('#infoTemplate').html();
|
||||
|
||||
initProgress("Fetching relays");
|
||||
$http.get("/endpoint").then(function(response) {
|
||||
progressDone("Fetching relays");
|
||||
|
||||
$scope.relays = response.data.relays;
|
||||
|
||||
map = new google.maps.Map(document.getElementById('map'), {
|
||||
zoom: 1,
|
||||
mapTypeId: google.maps.MapTypeId.ROADMAP
|
||||
});
|
||||
|
||||
var promises = [];
|
||||
angular.forEach($scope.relays, function(relay) {
|
||||
var uri = document.createElement('a');
|
||||
|
||||
// HAX, otherwise doesn't work
|
||||
uri.href = relay.url.replace('relay://', 'http://');
|
||||
relay.uri = constructURI(relay.url);
|
||||
relay.address = relay.url.split('/')[2];
|
||||
|
||||
// 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];
|
||||
})
|
||||
addMarkerToMap(relay);
|
||||
|
||||
$scope.uri[relay.url] = uri;
|
||||
|
||||
var resolveStatus = $q.defer();
|
||||
|
||||
initProgress("Getting relay status for " + uri.hostname);
|
||||
|
||||
// Normal timeout doesn't deal with relays which accept the TCP connection
|
||||
// but don't respond (some firewalls do that), so deal with it this way.
|
||||
var timeoutRequest = $q.defer();
|
||||
|
||||
$http.get("http://" + uri.hostname + (uri.args.statusAddr || ":22070") + "/status", { timeout: timeoutRequest.promise }).then(function (response) {
|
||||
progressDone("Getting relay status for " + uri.hostname);
|
||||
$scope.status[relay.url] = response.data;
|
||||
angular.forEach($scope.totals, function(value, key) {
|
||||
if (typeof $scope.totals[key] == 'number') {
|
||||
$scope.totals[key] += response.data[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] += response.data[key][index];
|
||||
});
|
||||
}
|
||||
});
|
||||
resolveStatus.resolve(response.data);
|
||||
}, function() {
|
||||
progressDone("Getting relay status for " + uri.hostname);
|
||||
|
||||
resolveStatus.resolve(response.data);
|
||||
});
|
||||
|
||||
$timeout(function() {
|
||||
timeoutRequest.resolve();
|
||||
}, 5000);
|
||||
|
||||
promises.push(resolveStatus.promise);
|
||||
promises.push(getRelayStatus(relay));
|
||||
});
|
||||
|
||||
// Can only add circles once we know the totals for transfers, which means
|
||||
// we need to resolve all statuses.
|
||||
$q.all(promises).then(function() {
|
||||
$scope.started = true;
|
||||
var bounds = new google.maps.LatLngBounds();
|
||||
|
||||
angular.forEach($scope.relays, function(relay) {
|
||||
var scope = $rootScope.$new(true);
|
||||
var loc = relay.location.latitude + "," + relay.location.longitude;
|
||||
|
||||
// Deal with overlapping markers
|
||||
while (loc in usedLocs) {
|
||||
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(',');
|
||||
if (relay.status) {
|
||||
addCircleToMap(relay);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
usedLocs[loc] = true;
|
||||
$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(',');
|
||||
var position = new google.maps.LatLng(locParts[0], locParts[1]);
|
||||
locParts = [parseFloat(locParts[0]), parseFloat(locParts[1])];
|
||||
locParts[Math.round(Math.random())] += 0.5 * (Math.random() >= 0.5 ? 1 : -1);
|
||||
loc = locParts.join(',');
|
||||
}
|
||||
|
||||
scope.status = $scope.status[relay.url];
|
||||
scope.relay = relay;
|
||||
scope.uri = $scope.uri[relay.url];
|
||||
$scope.usedLocations[loc] = true;
|
||||
|
||||
var marker = new google.maps.Marker({
|
||||
position: position,
|
||||
map: map,
|
||||
title: relay.url,
|
||||
});
|
||||
var locParts = loc.split(',');
|
||||
|
||||
if (scope.status) {
|
||||
marker.circle = new google.maps.Circle({
|
||||
strokeColor: '#FF0000',
|
||||
strokeOpacity: 0.8,
|
||||
strokeWeight: 2,
|
||||
fillColor: '#FF0000',
|
||||
fillOpacity: 0.35,
|
||||
map: map,
|
||||
center: position,
|
||||
radius: ((scope.status.bytesProxied * 100) / $scope.totals.bytesProxied) * 10000
|
||||
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.marker.addListener('mouseover', function() {
|
||||
relay.marker.info.open($scope.map, relay.marker);
|
||||
});
|
||||
|
||||
relay.marker.addListener('mouseout', function() {
|
||||
relay.marker.info.close();
|
||||
});
|
||||
|
||||
$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.status.bytesProxied * 100) / $scope.totals.bytesProxied) * 10000
|
||||
});
|
||||
}
|
||||
|
||||
function getRelayStatus(relay) {
|
||||
// Normal timeout doesn't deal with relays which accept the TCP connection
|
||||
// but don't respond (some firewalls do that), so deal with it this way.
|
||||
var timeoutRequest = $q.defer();
|
||||
var resolveStatus = $q.defer();
|
||||
|
||||
$http.get("http://" + relay.uri.hostname + (relay.uri.args.statusAddr || ":22070") + "/status", { timeout: timeoutRequest.promise }).then(function (response) {
|
||||
relay.status = response.data;
|
||||
resolveStatus.resolve();
|
||||
angular.forEach($scope.totals, function(value, key) {
|
||||
if (typeof $scope.totals[key] == 'number') {
|
||||
$scope.totals[key] += response.data[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] += response.data[key][index];
|
||||
});
|
||||
}
|
||||
|
||||
var content = $compile(template)(scope)[0];
|
||||
|
||||
marker.info = new google.maps.InfoWindow();
|
||||
marker.info.setContent(content);
|
||||
|
||||
marker.addListener('mouseover', function() {
|
||||
marker.info.open(map, marker);
|
||||
});
|
||||
|
||||
marker.addListener('mouseout', function() {
|
||||
marker.info.close();
|
||||
});
|
||||
|
||||
bounds.extend(marker.position);
|
||||
});
|
||||
|
||||
map.fitBounds(bounds);
|
||||
if ($scope.relays.length == 1) {
|
||||
map.setZoom(13);
|
||||
}
|
||||
$scope.started = true;
|
||||
}, function() {
|
||||
relay.status = null;
|
||||
resolveStatus.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
$timeout(function() {
|
||||
timeoutRequest.resolve();
|
||||
}, 5000);
|
||||
|
||||
return resolveStatus.promise;
|
||||
}
|
||||
|
||||
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>{{ uri.hostname }}</b> <span ng-if="status.options['provided-by']">provided by <u>{{ status.options['provided-by'] }}</u></span></p>
|
||||
<div ng-if="status">
|
||||
<span ng-if="status.startTime">Start time: {{ status.startTime | date:"medium" }}</br></span>
|
||||
<span ng-if="status.bytesProxied != undefined">Proxied: {{ status.bytesProxied | bytes }}</br></span>
|
||||
<span ng-if="status.numActiveSessions != undefined">Sessions: {{ status.numActiveSessions }}</br></span>
|
||||
<span ng-if="status.numConnections != undefined">Clients: {{ status.numConnections }}</br></span>
|
||||
<span ng-if="status.options.pools">Pools: {{ status.options.pools.join(', ') }}</br></span>
|
||||
<span ng-if="status.options['global-rate'] != undefined">
|
||||
<span ng-if="status.options['global-rate'] > 0">Global rate limit: {{ status.options['global-rate'] | bytes }}/s</span>
|
||||
<span ng-if="status.options['global-rate'] == 0">Global rate limit: unlimited</span>
|
||||
<p><b>{{ relay.uri.hostname }}</b> <span ng-if="relay.status.options['provided-by']">provided by <u>{{ relay.status.options['provided-by'] }}</u></span></p>
|
||||
<div ng-if="relay.status">
|
||||
<span ng-if="relay.status.startTime">Start time: {{ relay.status.startTime | date:"medium" }}</br></span>
|
||||
<span ng-if="relay.status.bytesProxied != undefined">Proxied: {{ relay.status.bytesProxied | bytes }}</br></span>
|
||||
<span ng-if="relay.status.numActiveSessions != undefined">Sessions: {{ relay.status.numActiveSessions }}</br></span>
|
||||
<span ng-if="relay.status.numConnections != undefined">Clients: {{ relay.status.numConnections }}</br></span>
|
||||
<span ng-if="relay.status.options.pools">Pools: {{ relay.status.options.pools.join(', ') }}</br></span>
|
||||
<span ng-if="relay.status.options['global-rate'] != undefined">
|
||||
<span ng-if="relay.status.options['global-rate'] > 0">Global rate limit: {{ relay.status.options['global-rate'] | bytes }}/s</span>
|
||||
<span ng-if="relay.status.options['global-rate'] == 0">Global rate limit: unlimited</span>
|
||||
</br>
|
||||
</span>
|
||||
<span ng-if="status.options['per-session-rate'] != undefined">
|
||||
<span ng-if="status.options['per-session-rate'] > 0">Session rate limit: {{ status.options['per-session-rate'] | bytes }}/s</span>
|
||||
<span ng-if="status.options['per-session-rate'] == 0">Session rate limit: unlimited</span>
|
||||
<span ng-if="relay.status.options['per-session-rate'] != undefined">
|
||||
<span ng-if="relay.status.options['per-session-rate'] > 0">Session rate limit: {{ relay.status.options['per-session-rate'] | bytes }}/s</span>
|
||||
<span ng-if="relay.status.options['per-session-rate'] == 0">Session rate limit: unlimited</span>
|
||||
</br>
|
||||
</span>
|
||||
</div>
|
||||
<div ng-if="!status">
|
||||
<div ng-if="!relay.status">
|
||||
Data unavailable.
|
||||
<div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user