Refactor javascript, always show table, add sorting

This commit is contained in:
Audrius Butkevicius committed 2016-04-14 22:01:25 +01:00
1 parent 50068b0b0f
commit dd364c962f
2 files changed
+239 -182

No files matched your search

+2 -2
View File
@@ -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
View File
@@ -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">&hellip;
</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>