From 860580c7c80af38805123ca390056b881d65293a Mon Sep 17 00:00:00 2001 From: Henry Camacho Date: Sat, 18 Mar 2017 14:28:54 -0500 Subject: [PATCH] Updated to include selectable domain statistics. - added support for decent duration display - added support for byte count display (k/m/g/, etc) - added detail selector, you can watch n number of connections and refresh while keeping them open. - refresh button --- html/admin/index.html | 1 + html/admin/js/app.js | 87 ++++++++++-- html/admin/js/vendor/filter.js | 148 ++++++++++++++++++++ html/admin/partials/servers.html | 45 ++++-- main.go | 2 +- rvpn/genericlistener/api_collect_server.go | 2 + rvpn/genericlistener/api_collect_servers.go | 2 + rvpn/genericlistener/connection.go | 5 + 8 files changed, 267 insertions(+), 25 deletions(-) create mode 100644 html/admin/js/vendor/filter.js diff --git a/html/admin/index.html b/html/admin/index.html index 1af2fc6..aa3bc3a 100644 --- a/html/admin/index.html +++ b/html/admin/index.html @@ -67,6 +67,7 @@ + diff --git a/html/admin/js/app.js b/html/admin/js/app.js index 58eeb3a..1a0e819 100644 --- a/html/admin/js/app.js +++ b/html/admin/js/app.js @@ -1,6 +1,7 @@ console.log("app.sh startup") -var app = angular.module("rvpnApp", ["ngRoute"]); +var app = angular.module("rvpnApp", ["ngRoute", "angular-duration-format"]); + app.config(function($routeProvider, $locationProvider) { $routeProvider .when("/admin/index.html", { @@ -18,27 +19,85 @@ app.config(function($routeProvider, $locationProvider) { $locationProvider.html5Mode(true); }); +app.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)); + return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number]; + } +}); + +app.filter('hfcduration', function() { + return function(duration, precision) { + remain = duration + duration_day = 24*60*60 + duration_hour = 60*60 + duration_minute = 60 + duration_str = "" + + days = Math.floor(remain / duration_day) + if (days > 0) { + remain = remain - (days * duration_day) + duration_str = duration_str + days + 'd' + } + + hours = Math.floor(remain / duration_hour) + if (hours > 0) { + remain = remain - (hours * duration_hour) + duration_str = duration_str + hours + 'h' + } + + mins = Math.floor(remain / duration_minute) + if (mins > 0) { + remain = remain - (mins * duration_minute) + duration_str = duration_str + mins + 'm' + } + + secs = Math.floor(remain) + duration_str = duration_str + secs + 's' + + return (duration_str); + } +}); + app.controller('serverController', function ($scope, $http) { $scope.servers = []; + $scope.servers_search = ""; + $scope.servers_trigger_details = []; + var api = '/api/com.daplie.rvpn/servers' + + $scope.updateView = function() { + $http.get(api).then(function(response) { + //console.log(response); + data = response.data; + if (data.error == 'ok' ){ + $scope.servers = data.result.servers; + } + }); + } - $http.get(api).then(function(response) { - updateView(response.data); - }); - - updateView = function(data) { - console.log(data); - if (data.error == 'ok' ){ - console.log("ok") - $scope.servers = data.result.servers; - console.log(data.result) - + $scope.triggerDetail = function(id) { + console.log("triggerDetail ", id, $scope.servers_trigger_details[id]) + if ($scope.servers_trigger_details[id] == true) { + $scope.servers_trigger_details[id] = false; + } else { + $scope.servers_trigger_details[id] = true } }; + $scope.checkDetail = function(id) { + console.log("checkDetail ", id, $scope.servers_trigger_details[id]) + if ($scope.servers_trigger_details[id] == true) { + return false; + } else { + return true + } + }; - - + $scope.updateView() }); \ No newline at end of file diff --git a/html/admin/js/vendor/filter.js b/html/admin/js/vendor/filter.js new file mode 100644 index 0000000..56b5b4c --- /dev/null +++ b/html/admin/js/vendor/filter.js @@ -0,0 +1,148 @@ +// ### filter.js >> + +angular + .module('angular-duration-format.filter', [ ]) + .filter('duration', function() { + var DURATION_FORMATS_SPLIT = /((?:[^ydhms']+)|(?:'(?:[^']|'')*')|(?:y+|d+|h+|m+|s+))(.*)/; + var DURATION_FORMATS = { + y: { // years + // "longer" years are not supported + value: 365 * 24 * 60 * 60 * 1000, + }, + yy: { + value: 'y', + pad: 2, + }, + d: { // days + value: 24 * 60 * 60 * 1000, + }, + dd: { + value: 'd', + pad: 2, + }, + h: { // hours + value: 60 * 60 * 1000, + }, + hh: { // padded hours + value: 'h', + pad: 2, + }, + m: { // minutes + value: 60 * 1000, + }, + mm: { // padded minutes + value: 'm', + pad: 2, + }, + s: { // seconds + value: 1000, + }, + ss: { // padded seconds + value: 's', + pad: 2, + }, + sss: { // milliseconds + value: 1, + }, + ssss: { // padded milliseconds + value: 'sss', + pad: 4, + }, + }; + + function _parseFormat(string) { + // @inspiration AngularJS date filter + var parts = []; + var format = string ? string.toString() : ''; + + while (format) { + var match = DURATION_FORMATS_SPLIT.exec(format); + + if (match) { + parts = parts.concat(match.slice(1)); + + format = parts.pop(); + } else { + parts.push(format); + + format = null; + } + } + + return parts; + } + + function _formatDuration(timestamp, format) { + var text = ''; + var values = { }; + + format.filter(function(format) { // filter only value parts of format + return DURATION_FORMATS.hasOwnProperty(format); + }).map(function(format) { // get formats with values only + var config = DURATION_FORMATS[format]; + + if (config.hasOwnProperty('pad')) { + return config.value; + } else { + return format; + } + }).filter(function(format, index, arr) { // remove duplicates + return (arr.indexOf(format) === index); + }).map(function(format) { // get format configurations with values + return angular.extend({ + name: format, + }, DURATION_FORMATS[format]); + }).sort(function(a, b) { // sort formats descending by value + return b.value - a.value; + }).forEach(function(format) { // create values for format parts + var value = values[format.name] = Math.floor(timestamp / format.value); + + timestamp = timestamp - (value * format.value); + }); + + format.forEach(function(part) { + var format = DURATION_FORMATS[part]; + + if (format) { + var value = values[format.value]; + + text += (format.hasOwnProperty('pad') ? _padNumber(value, Math.max(format.pad, value.toString().length)) : values[part]); + } else { + text += part.replace(/(^'|'$)/g, '').replace(/''/g, '\''); + } + }); + + return text; + } + + function _padNumber(number, len) { + return ((new Array(len + 1)).join('0') + number).slice(-len); + } + + return function(value, format) { + var parsedValue = parseFloat(value, 10); + var parsedFormat = _parseFormat(format); + + if (isNaN(parsedValue) || (parsedFormat.length === 0)) { + return value; + } else { + return _formatDuration(parsedValue, parsedFormat); + } + }; + }); + + +// ### << filter.js + + + +// ### main.js >> + +angular + .module('angular-duration-format', [ + 'angular-duration-format.filter', + ]); + + +// ### << main.js + diff --git a/html/admin/partials/servers.html b/html/admin/partials/servers.html index 3c323d0..4da684b 100644 --- a/html/admin/partials/servers.html +++ b/html/admin/partials/servers.html @@ -9,27 +9,52 @@
- +
+
+
- - - - - + + + + + + + + - + - - - + + + + +
IDNameAddressXfer (in/out)TimeIDNameAddressXfer (in/out)TimeIdle
{{ s.server_id }} {{ s.server_name }}{{ s.source_address }}{{ s.bytes_in }}/{{ s.bytes_out}}{{ s.duration }} + {{ s.source_address }} +
+
+    {{ d.domain_name }} +
+
+
+ {{ s.bytes_in | bytes }}/{{ s.bytes_out | bytes }} +
+
+ {{ d.bytes_in | bytes }}/{{ d.bytes_out | bytes }} +
+
+
{{ s.duration | hfcduration }}{{ s.idle | hfcduration }} + +
diff --git a/main.go b/main.go index 21ca4b9..3d1e4c5 100644 --- a/main.go +++ b/main.go @@ -95,6 +95,6 @@ func main() { go genericListeners.Run(ctx, argGenericBinding) //Run for 10 minutes and then shutdown cleanly - time.Sleep(600 * time.Second) + time.Sleep(6000 * time.Second) cancelContext() } diff --git a/rvpn/genericlistener/api_collect_server.go b/rvpn/genericlistener/api_collect_server.go index ad775b7..73e95a5 100644 --- a/rvpn/genericlistener/api_collect_server.go +++ b/rvpn/genericlistener/api_collect_server.go @@ -11,6 +11,7 @@ type ServerAPI struct { ServerID int64 `json:"server_id"` Domains []*DomainAPI `json:"domains"` Duration float64 `json:"duration"` + Idle float64 `json:"idle"` BytesIn int64 `json:"bytes_in"` BytesOut int64 `json:"bytes_out"` Source string `json:"source_address"` @@ -23,6 +24,7 @@ func NewServerAPI(c *Connection) (s *ServerAPI) { s.ServerID = c.ConnectionID() s.Domains = make([]*DomainAPI, 0) s.Duration = time.Since(c.ConnectTime()).Seconds() + s.Idle = time.Since(c.LastUpdate()).Seconds() s.BytesIn = c.BytesIn() s.BytesOut = c.BytesOut() s.Source = c.source diff --git a/rvpn/genericlistener/api_collect_servers.go b/rvpn/genericlistener/api_collect_servers.go index 63c9746..a6170e4 100644 --- a/rvpn/genericlistener/api_collect_servers.go +++ b/rvpn/genericlistener/api_collect_servers.go @@ -11,6 +11,7 @@ type ServersAPI struct { ServerID int64 `json:"server_id"` Domains []*DomainAPI `json:"domains"` Duration float64 `json:"duration"` + Idle float64 `json:"idle"` BytesIn int64 `json:"bytes_in"` BytesOut int64 `json:"bytes_out"` Source string `json:"source_address"` @@ -23,6 +24,7 @@ func NewServersAPI(c *Connection) (s *ServersAPI) { s.ServerID = c.ConnectionID() s.Domains = make([]*DomainAPI, 0) s.Duration = time.Since(c.ConnectTime()).Seconds() + s.Idle = time.Since(c.LastUpdate()).Seconds() s.BytesIn = c.BytesIn() s.BytesOut = c.BytesOut() s.Source = c.Source() diff --git a/rvpn/genericlistener/connection.go b/rvpn/genericlistener/connection.go index c7cd016..68bd62c 100755 --- a/rvpn/genericlistener/connection.go +++ b/rvpn/genericlistener/connection.go @@ -178,6 +178,11 @@ func (c *Connection) Update() { c.lastUpdate = time.Now() } +//LastUpdate -- retrieve last update +func (c *Connection) LastUpdate() time.Time { + return c.lastUpdate +} + //ConnectionID - Get func (c *Connection) ConnectionID() int64 { return c.connectionID