From 19e49f4b867847bf1b8a0cd5f1233aa2bd456b57 Mon Sep 17 00:00:00 2001 From: Alessio Sergi Date: Sun, 19 Apr 2015 23:53:19 +0200 Subject: [PATCH 01/15] glances_client.py: fix function call --- glances/core/glances_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glances/core/glances_client.py b/glances/core/glances_client.py index fe86433f..5ade183e 100644 --- a/glances/core/glances_client.py +++ b/glances/core/glances_client.py @@ -232,7 +232,7 @@ class GlancesClient(object): # Export stats using export modules self.stats.export(self.stats) finally: - self.end + self.end() return self.client_mode From e446d380a1c0b84577f5a2d20f8e9ee6dc371274 Mon Sep 17 00:00:00 2001 From: Alessio Sergi Date: Tue, 21 Apr 2015 11:44:17 +0200 Subject: [PATCH 02/15] Process list: TIME+: display hours too --- glances/plugins/glances_processlist.py | 28 ++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index 0491a1a5..ac022e2c 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -30,6 +30,20 @@ from glances.core.glances_processes import glances_processes from glances.plugins.glances_plugin import GlancesPlugin +def convert_timedelta(delta): + """Convert timedelta to human-readable time.""" + # Python 2.7+: + # total_seconds = delta.total_seconds() + # hours = total_seconds // 3600 + days, total_seconds = delta.days, delta.seconds + hours = days * 24 + total_seconds // 3600 + minutes = (total_seconds % 3600) // 60 + seconds = str(total_seconds % 60).zfill(2) + microseconds = str(delta.microseconds)[:2].zfill(2) + + return hours, minutes, seconds, microseconds + + class Plugin(GlancesPlugin): """Glances' processes plugin. @@ -222,18 +236,20 @@ class Plugin(GlancesPlugin): # TIME+ if self.tag_proc_time: try: - dtime = timedelta(seconds=sum(p['cpu_times'])) + delta = timedelta(seconds=sum(p['cpu_times'])) except Exception: # Catched on some Amazon EC2 server # See https://github.com/nicolargo/glances/issues/87 self.tag_proc_time = False else: - msg = '{0}:{1}.{2}'.format(str(dtime.seconds // 60 % 60), - str(dtime.seconds % 60).zfill(2), - str(dtime.microseconds)[:2].zfill(2)) + hours, minutes, seconds, microseconds = convert_timedelta(delta) + if hours: + msg = '{0}h{1}:{2}'.format(hours, minutes, seconds) + else: + msg = '{0}:{1}.{2}'.format(minutes, seconds, microseconds) else: msg = ' ' - msg = '{0:>9}'.format(msg) + msg = '{0:>10}'.format(msg) ret.append(self.curse_add_line(msg, optional=True)) # IO read/write if 'io_counters' in p: @@ -384,7 +400,7 @@ class Plugin(GlancesPlugin): ret.append(self.curse_add_line(msg)) msg = '{0:>2}'.format(_("S")) ret.append(self.curse_add_line(msg)) - msg = '{0:>9}'.format(_("TIME+")) + msg = '{0:>10}'.format(_("TIME+")) ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'cpu_times' else 'DEFAULT', optional=True)) msg = '{0:>6}'.format(_("IOR/s")) ret.append(self.curse_add_line(msg, sort_style if process_sort_key == 'io_counters' else 'DEFAULT', optional=True, additional=True)) From d588cb041cf6bfeb6b265095dbec3b02ef98f509 Mon Sep 17 00:00:00 2001 From: nicolargo Date: Tue, 21 Apr 2015 22:30:20 +0200 Subject: [PATCH 03/15] A first comment change to be compliant with apidoc (see http://apidocjs.com/#examples) --- glances/outputs/glances_bottle.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/glances/outputs/glances_bottle.py b/glances/outputs/glances_bottle.py index 0f9de73b..f97a85f1 100644 --- a/glances/outputs/glances_bottle.py +++ b/glances/outputs/glances_bottle.py @@ -124,9 +124,28 @@ class GlancesBottle(object): def _api_plugins(self): """ - Glances API RESTFul implementation - Return the plugin list - or 404 error + @api {get} /api/2/pluginslist Get plugins list + @apiVersion 2.0 + @apiName pluginslist + @apiGroup plugin + + @apiSuccess {String[]} Plugins list. + + @apiSuccessExample Success-Response: + HTTP/1.1 200 OK + [ + "load", + "help", + "ip", + "memswap", + "processlist", + ... + ] + + @apiError Cannot get plugin list. + + @apiErrorExample Error-Response: + HTTP/1.1 404 Not Found """ response.content_type = 'application/json' From 8d1a741e261de8b1000627234b0b8a3597f8fde9 Mon Sep 17 00:00:00 2001 From: Alessio Sergi Date: Wed, 22 Apr 2015 11:44:44 +0200 Subject: [PATCH 04/15] Fix regression in the default configuration settings --- glances/core/glances_config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/glances/core/glances_config.py b/glances/core/glances_config.py index 9ad475a5..828fb7d1 100644 --- a/glances/core/glances_config.py +++ b/glances/core/glances_config.py @@ -205,12 +205,12 @@ class Config(object): # Process list if not self.parser.has_section('processlist'): self.parser.add_section('processlist') - self.parser.set('cpu', 'careful', '50') - self.parser.set('cpu', 'warning', '70') - self.parser.set('cpu', 'critical', '90') - self.parser.set('mem', 'careful', '50') - self.parser.set('mem', 'warning', '70') - self.parser.set('mem', 'critical', '90') + self.parser.set('processlist', 'cpu_careful', '50') + self.parser.set('processlist', 'cpu_warning', '70') + self.parser.set('processlist', 'cpu_critical', '90') + self.parser.set('processlist', 'mem_careful', '50') + self.parser.set('processlist', 'mem_warning', '70') + self.parser.set('processlist', 'mem_critical', '90') @property def loaded_config_file(self): From deeacc6791423ee1ac7d24d0cc6eb532a768c44a Mon Sep 17 00:00:00 2001 From: Alessio Sergi Date: Wed, 22 Apr 2015 15:22:00 +0200 Subject: [PATCH 05/15] glances_processlist.py: minor fixes - Attribute 'tag_proc_time' defined outside __init__ - Catch the correct exception (OverflowError) - Display '?' if no CPU times are available --- glances/plugins/glances_processlist.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index ac022e2c..3f49f206 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -58,6 +58,9 @@ class Plugin(GlancesPlugin): # We want to display the stat in the curse interface self.display_curse = True + # Trying to display proc time + self.tag_proc_time = True + # Note: 'glances_processes' is already init in the glances_processes.py script def get_key(self): @@ -237,7 +240,7 @@ class Plugin(GlancesPlugin): if self.tag_proc_time: try: delta = timedelta(seconds=sum(p['cpu_times'])) - except Exception: + except OverflowError: # Catched on some Amazon EC2 server # See https://github.com/nicolargo/glances/issues/87 self.tag_proc_time = False @@ -248,7 +251,7 @@ class Plugin(GlancesPlugin): else: msg = '{0}:{1}.{2}'.format(minutes, seconds, microseconds) else: - msg = ' ' + msg = '?' msg = '{0:>10}'.format(msg) ret.append(self.curse_add_line(msg, optional=True)) # IO read/write @@ -409,9 +412,6 @@ class Plugin(GlancesPlugin): msg = ' {0:8}'.format(_("Command")) ret.append(self.curse_add_line(msg)) - # Trying to display proc time - self.tag_proc_time = True - if glances_processes.is_tree_enabled(): ret.extend(self.get_process_tree_curses_data( self.sort_stats(process_sort_key), args, first_level=True, From a88fb52b8d519bd4bb4d2cdcb02bbe379a36693a Mon Sep 17 00:00:00 2001 From: Alessio Sergi Date: Wed, 22 Apr 2015 16:07:15 +0200 Subject: [PATCH 06/15] Process list: TIME+: highlight hours (for long running processes >= 1h) --- glances/outputs/glances_curses.py | 3 +++ glances/plugins/glances_plugin.py | 1 + glances/plugins/glances_processlist.py | 9 +++++---- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 3d1a4fb0..af6c5a89 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -128,6 +128,7 @@ class _GlancesCurses(object): self.no_color = curses.color_pair(1) self.default_color = curses.color_pair(3) | A_BOLD self.nice_color = curses.color_pair(9) | A_BOLD + self.cpu_time_color = curses.color_pair(9) | A_BOLD self.ifCAREFUL_color = curses.color_pair(4) | A_BOLD self.ifWARNING_color = curses.color_pair(5) | A_BOLD self.ifCRITICAL_color = curses.color_pair(2) | A_BOLD @@ -141,6 +142,7 @@ class _GlancesCurses(object): self.no_color = curses.A_NORMAL self.default_color = curses.A_NORMAL self.nice_color = A_BOLD + self.cpu_time_color = A_BOLD self.ifCAREFUL_color = curses.A_UNDERLINE self.ifWARNING_color = A_BOLD self.ifCRITICAL_color = curses.A_REVERSE @@ -162,6 +164,7 @@ class _GlancesCurses(object): 'PROCESS': self.default_color2, 'STATUS': self.default_color2, 'NICE': self.nice_color, + 'CPU_TIME': self.cpu_time_color, 'CAREFUL': self.ifCAREFUL_color2, 'WARNING': self.ifWARNING_color2, 'CRITICAL': self.ifCRITICAL_color2, diff --git a/glances/plugins/glances_plugin.py b/glances/plugins/glances_plugin.py index ffd4b85b..73c68c75 100644 --- a/glances/plugins/glances_plugin.py +++ b/glances/plugins/glances_plugin.py @@ -553,6 +553,7 @@ class GlancesPlugin(object): PROCESS: for process name STATUS: for process status NICE: for process niceness + CPU_TIME: for process cpu time OK: Value is OK and non logged OK_LOG: Value is OK and logged CAREFUL: Value is CAREFUL and non logged diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index 3f49f206..dbdf4ced 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -247,12 +247,13 @@ class Plugin(GlancesPlugin): else: hours, minutes, seconds, microseconds = convert_timedelta(delta) if hours: - msg = '{0}h{1}:{2}'.format(hours, minutes, seconds) + msg = '{0:>4}h'.format(hours) + ret.append(self.curse_add_line(msg, decoration='CPU_TIME', optional=True)) + msg = '{0}:{1}'.format(minutes, seconds) else: - msg = '{0}:{1}.{2}'.format(minutes, seconds, microseconds) + msg = '{0:>4}:{1}.{2}'.format(minutes, seconds, microseconds) else: - msg = '?' - msg = '{0:>10}'.format(msg) + msg = '{0:>10}'.format('?') ret.append(self.curse_add_line(msg, optional=True)) # IO read/write if 'io_counters' in p: From afa43acdd75c8ac68be9722222cb4669de5d5fa8 Mon Sep 17 00:00:00 2001 From: Alessio Sergi Date: Sat, 25 Apr 2015 14:25:17 +0200 Subject: [PATCH 07/15] Process list: TIME+: display minutes in two digits (hh:mm:ss format only) Display a leading zero if the number is only one digit long (00-59). Display minutes in one digit otherwise (mm:ss.ms format, 0-59). --- glances/plugins/glances_processlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glances/plugins/glances_processlist.py b/glances/plugins/glances_processlist.py index dbdf4ced..53bd10db 100644 --- a/glances/plugins/glances_processlist.py +++ b/glances/plugins/glances_processlist.py @@ -249,7 +249,7 @@ class Plugin(GlancesPlugin): if hours: msg = '{0:>4}h'.format(hours) ret.append(self.curse_add_line(msg, decoration='CPU_TIME', optional=True)) - msg = '{0}:{1}'.format(minutes, seconds) + msg = '{0}:{1}'.format(str(minutes).zfill(2), seconds) else: msg = '{0:>4}:{1}.{2}'.format(minutes, seconds, microseconds) else: From 6c9eedbef94cd0801541aa60a6c55337a8538e9f Mon Sep 17 00:00:00 2001 From: Alessio Sergi Date: Mon, 27 Apr 2015 12:27:17 +0200 Subject: [PATCH 08/15] Bump minimum required zeroconf version to 0.17 - Fix regression introduced by issue #528 fix (pointless-statement). - 'netifaces' is a dependency of 'zeroconf==0.17', so the check is redundant. --- README.rst | 3 ++- glances/core/glances_autodiscover.py | 40 +++++++++++----------------- setup.py | 3 ++- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/README.rst b/README.rst index 78f49d73..e45a9b99 100644 --- a/README.rst +++ b/README.rst @@ -39,7 +39,8 @@ Optional dependencies: - ``batinfo`` (for battery monitoring support) [Linux-only] - ``pymdstat`` (for RAID support) [Linux-only] - ``pysnmp`` (for SNMP support) -- ``zeroconf`` and ``netifaces`` (for the auto discoverer mode) +- ``zeroconf`` (for the autodiscover mode) +- ``netifaces`` (for the IP plugin) - ``influxdb`` (for the InfluxDB export module) - ``statsd`` (for the StatsD export module) - ``pystache`` (for the action script feature) diff --git a/glances/core/glances_autodiscover.py b/glances/core/glances_autodiscover.py index bc783932..2e5de4db 100644 --- a/glances/core/glances_autodiscover.py +++ b/glances/core/glances_autodiscover.py @@ -22,11 +22,7 @@ # Import system libs import socket import sys -try: - import netifaces - netifaces_tag = True -except ImportError: - netifaces_tag = False + try: from zeroconf import ( __version__ as __zeroconf_version, @@ -42,13 +38,13 @@ except ImportError: from glances.core.glances_globals import appname from glances.core.glances_logging import logger -# Zeroconf 0.16 or higher is needed +# Zeroconf 0.17 or higher is needed if zeroconf_tag: - zeroconf_min_version = (0, 16, 0) + zeroconf_min_version = (0, 17, 0) zeroconf_version = tuple([int(num) for num in __zeroconf_version.split('.')]) logger.debug("Zeroconf version {0} detected.".format(__zeroconf_version)) if zeroconf_version < zeroconf_min_version: - logger.critical("Please install zeroconf 0.16 or higher.") + logger.critical("Please install zeroconf 0.17 or higher.") sys.exit(1) # Global var @@ -197,20 +193,15 @@ class GlancesAutoDiscoverClient(object): except socket.error as e: logger.error("Cannot start zeroconf: {0}".format(e)) - if netifaces_tag: + try: # -B @ overwrite the dynamic IPv4 choice if zeroconf_bind_address == '0.0.0.0': zeroconf_bind_address = self.find_active_ip_address() - else: - logger.error("Couldn't find the active IP address: netifaces library not found.") + except KeyError: + # Issue #528 (no network interface available) + pass - # Correct issue #528 (no network interface available) - if zeroconf_bind_address is None: - zeroconf_bind_address == '0.0.0.0' - - logger.info("Announce the Glances server on the LAN (using {0} IP address)".format(zeroconf_bind_address)) print("Announce the Glances server on the LAN (using {0} IP address)".format(zeroconf_bind_address)) - self.info = ServiceInfo( zeroconf_type, '{0}:{1}.{2}'.format(hostname, args.port, zeroconf_type), address=socket.inet_aton(zeroconf_bind_address), port=args.port, @@ -219,15 +210,14 @@ class GlancesAutoDiscoverClient(object): else: logger.error("Cannot announce Glances server on the network: zeroconf library not found.") - def find_active_ip_address(self): + @staticmethod + def find_active_ip_address(): """Try to find the active IP addresses.""" - try: - # Interface of the default gateway - gateway_itf = netifaces.gateways()['default'][netifaces.AF_INET][1] - # IP address for the interface - return netifaces.ifaddresses(gateway_itf)[netifaces.AF_INET][0]['addr'] - except Exception: - return None + import netifaces + # Interface of the default gateway + gateway_itf = netifaces.gateways()['default'][netifaces.AF_INET][1] + # IP address for the interface + return netifaces.ifaddresses(gateway_itf)[netifaces.AF_INET][0]['addr'] def close(self): if zeroconf_tag: diff --git a/setup.py b/setup.py index d41078f9..64486625 100755 --- a/setup.py +++ b/setup.py @@ -53,7 +53,8 @@ setup( 'BATINFO': ['batinfo'], 'SNMP': ['pysnmp'], 'CHART': ['matplotlib'], - 'BROWSER': ['zeroconf>=0.16', 'netifaces'], + 'BROWSER': ['zeroconf>=0.17'], + 'IP': ['netifaces'], 'RAID': ['pymdstat'], 'DOCKER': ['docker-py'], 'EXPORT': ['influxdb>=1.0.0', 'statsd', 'pika'], From 1f5b07a522f08d7f358cc444c21d949b54b4aa34 Mon Sep 17 00:00:00 2001 From: Nicolas Hart Date: Tue, 28 Apr 2015 16:04:10 +0200 Subject: [PATCH 09/15] move vendors into a dedicated directory --- glances/outputs/static/html/index.html | 10 +++++----- .../static/js/{ => vendors}/angular-route.min.js | 0 .../static/js/{ => vendors}/angular-route.min.js.map | 0 glances/outputs/static/js/{ => vendors}/angular.min.js | 0 .../outputs/static/js/{ => vendors}/angular.min.js.map | 0 5 files changed, 5 insertions(+), 5 deletions(-) rename glances/outputs/static/js/{ => vendors}/angular-route.min.js (100%) rename glances/outputs/static/js/{ => vendors}/angular-route.min.js.map (100%) rename glances/outputs/static/js/{ => vendors}/angular.min.js (100%) rename glances/outputs/static/js/{ => vendors}/angular.min.js.map (100%) diff --git a/glances/outputs/static/html/index.html b/glances/outputs/static/html/index.html index c45bd3b4..99e766b9 100644 --- a/glances/outputs/static/html/index.html +++ b/glances/outputs/static/html/index.html @@ -5,17 +5,17 @@ Glances - + - - - + + + - + diff --git a/glances/outputs/static/js/angular-route.min.js b/glances/outputs/static/js/vendors/angular-route.min.js similarity index 100% rename from glances/outputs/static/js/angular-route.min.js rename to glances/outputs/static/js/vendors/angular-route.min.js diff --git a/glances/outputs/static/js/angular-route.min.js.map b/glances/outputs/static/js/vendors/angular-route.min.js.map similarity index 100% rename from glances/outputs/static/js/angular-route.min.js.map rename to glances/outputs/static/js/vendors/angular-route.min.js.map diff --git a/glances/outputs/static/js/angular.min.js b/glances/outputs/static/js/vendors/angular.min.js similarity index 100% rename from glances/outputs/static/js/angular.min.js rename to glances/outputs/static/js/vendors/angular.min.js diff --git a/glances/outputs/static/js/angular.min.js.map b/glances/outputs/static/js/vendors/angular.min.js.map similarity index 100% rename from glances/outputs/static/js/angular.min.js.map rename to glances/outputs/static/js/vendors/angular.min.js.map From 625384af44c82e16cbad07b262f22cb6a4ed4751 Mon Sep 17 00:00:00 2001 From: Nicolas Hart Date: Tue, 28 Apr 2015 18:54:03 +0200 Subject: [PATCH 10/15] move all filters in a dedicated file --- glances/outputs/static/html/index.html | 1 + glances/outputs/static/js/app.js | 13 ++- glances/outputs/static/js/filters.js | 44 ++++++++ glances/outputs/static/js/stats_controller.js | 104 ++++-------------- 4 files changed, 80 insertions(+), 82 deletions(-) create mode 100644 glances/outputs/static/js/filters.js diff --git a/glances/outputs/static/html/index.html b/glances/outputs/static/html/index.html index 99e766b9..308be4a6 100644 --- a/glances/outputs/static/html/index.html +++ b/glances/outputs/static/html/index.html @@ -14,6 +14,7 @@ + diff --git a/glances/outputs/static/js/app.js b/glances/outputs/static/js/app.js index 3ade4fdc..4186b40b 100644 --- a/glances/outputs/static/js/app.js +++ b/glances/outputs/static/js/app.js @@ -1,2 +1,13 @@ -var glancesApp = angular.module('glancesApp', ['ngRoute']); +var glancesApp = angular.module('glancesApp', ['ngRoute']) +.config(function($routeProvider, $locationProvider) { + $routeProvider.when('/', { + templateUrl : 'stats.html', + controller : 'statsController' + }).when('/:refresh_time', { + templateUrl : 'stats.html', + controller : 'statsController' + }); + + $locationProvider.html5Mode(true); +}); diff --git a/glances/outputs/static/js/filters.js b/glances/outputs/static/js/filters.js new file mode 100644 index 00000000..1839d3dd --- /dev/null +++ b/glances/outputs/static/js/filters.js @@ -0,0 +1,44 @@ +glancesApp.filter('min_size', function() { + return function(input) { + var max = 8; + if (input.length > max) { + return "_" + input.substring(input.length - max) + } + return input + }; +}); +glancesApp.filter('exclamation', function() { + return function(input) { + if (input == undefined || input =='') { + return '?' + } + return input + }; +}); + +/** + * Fork from https://gist.github.com/thomseddon/3511330 + *   => \u00A0 + * WARNING : kilobyte (kB) != kibibyte (KiB) (more info here : http://en.wikipedia.org/wiki/Byte ) + **/ +glancesApp.filter('bytes', function() { + return function (bytes, precision) { + if (isNaN(parseFloat(bytes)) || !isFinite(bytes) || bytes == 0){ + return '0B'; + } + var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'], + number = Math.floor(Math.log(bytes) / Math.log(1000)); + return (bytes / Math.pow(1000, Math.floor(number))).toFixed(precision) + units[number]; + } +}); + +glancesApp.filter('bits', function() { + return function (bits, precision) { + if (isNaN(parseFloat(bits)) || !isFinite(bits) || bits == 0){ + return '0b'; + } + var units = ['b', 'kb', 'Mb', 'Gb', 'Tb', 'Pb'], + number = Math.floor(Math.log(bits) / Math.log(1000)); + return (bits / Math.pow(1000, Math.floor(number))).toFixed(precision) + units[number]; + } +}); diff --git a/glances/outputs/static/js/stats_controller.js b/glances/outputs/static/js/stats_controller.js index 1e477639..dd160457 100644 --- a/glances/outputs/static/js/stats_controller.js +++ b/glances/outputs/static/js/stats_controller.js @@ -1,61 +1,3 @@ -glancesApp.config([ '$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) { - $routeProvider.when('/', { - templateUrl : 'stats.html', - controller : 'statsController' - }).when('/:refresh_time', { - templateUrl : 'stats.html', - controller : 'statsController' - }); - - $locationProvider.html5Mode(true); -} ]); - -glancesApp.filter('min_size', function() { - return function(input) { - var max = 8; - if (input.length > max) { - return "_" + input.substring(input.length - max) - } - return input - }; -}); -glancesApp.filter('exclamation', function() { - return function(input) { - if (input == undefined || input =='') { - return '?' - } - return input - }; -}); - - -/** - * Fork from https://gist.github.com/thomseddon/3511330 - *   => \u00A0 - * WARNING : kilobyte (kB) != kibibyte (KiB) (more info here : http://en.wikipedia.org/wiki/Byte ) - **/ -glancesApp.filter('bytes', function() { - return function (bytes, precision) { - if (isNaN(parseFloat(bytes)) || !isFinite(bytes) || bytes == 0){ - return '0B'; - } - var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'], - number = Math.floor(Math.log(bytes) / Math.log(1000)); - return (bytes / Math.pow(1000, Math.floor(number))).toFixed(precision) + units[number]; - } -}); -glancesApp.filter('bits', function() { - return function (bits, precision) { - if (isNaN(parseFloat(bits)) || !isFinite(bits) || bits == 0){ - return '0b'; - } - var units = ['b', 'kb', 'Mb', 'Gb', 'Tb', 'Pb'], - number = Math.floor(Math.log(bits) / Math.log(1000)); - return (bits / Math.pow(1000, Math.floor(number))).toFixed(precision) + units[number]; - } -}); - - glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', '$routeParams', function($scope, $http, $interval, $q, $routeParams) { $scope.limitSuffix = ['critical', 'careful', 'warning'] @@ -64,7 +6,7 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', $scope.sortColumn = '' $scope.sortOrderAsc = false $scope.help_screen = false - $scope.lastSortColumn = '#column_' + $scope.sortColumn + $scope.lastSortColumn = '#column_' + $scope.sortColumn $scope.show = { 'diskio' : true, 'network' : true, @@ -93,18 +35,18 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', } } } - - + + $scope.init_limits = function() { $scope.plugins_limits(); } - + $scope.init_help = function() { $http.get('/api/2/help').success(function(response, status, headers, config) { $scope.help = response }); } - + $scope.show_hide = function(bloc) { if(bloc == 'help') { $scope.help_screen = !$scope.help_screen @@ -112,7 +54,7 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', $scope.show[bloc] = !$scope.show[bloc] } } - + $scope.sort_by = function(column) { if (column == undefined) { // sort automatically @@ -123,7 +65,7 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', column = 'cmdline' } angular.element(document.querySelector($scope.lastSortColumn)).removeClass('sort sort_asc sort_desc') - + if ($scope.sortColumn == column) { $scope.sortOrderAsc = !$scope.sortOrderAsc if ($scope.sortOrderAsc) { @@ -134,11 +76,11 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', } else { $scope.sortColumn = column $scope.sortOrderAsc = false - $scope.lastSortColumn = '#column_' + $scope.sortColumn + $scope.lastSortColumn = '#column_' + $scope.sortColumn angular.element(document.querySelector($scope.lastSortColumn)).addClass('sort sort_desc') } } - + $scope.plugins_limits = function() { $http.get('/api/2/all/limits').success(function(response, status, headers, config) { $scope.limits = response @@ -146,9 +88,9 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', console.log('error : ' + response+ status + headers + config); }); } - + var canceler = undefined; - + /** * Refresh all the data of the view */ @@ -156,7 +98,7 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', canceler = $q.defer(); $http.get('/api/2/all', {timeout: canceler.promise}).success(function(response, status, headers, config) { //alert('success'); - + function timemillis(array) { var sum = 0.0 for (var i = 0; i < array.length; i++) { @@ -188,7 +130,7 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', var month = leftpad(d.getMonth() + 1) // JANUARY = 0 var day = leftpad(d.getDate()) return year + "-" + month + "-" + day + " " + datetimeformat(input) - + } function datetimeformat(input) { var millis = input * 1000.0; @@ -198,7 +140,7 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', var seconds = leftpad(d.getUTCSeconds()) return hour + ":" + minutes + ":" + seconds } - + for (var i = 0; i < response['processlist'].length; i++) { var process = response['processlist'][i] process.memvirt = process.memory_info[1] @@ -220,13 +162,13 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', canceler.resolve() }); } - + $scope.getClass = function(pluginName, limitNamePrefix, value, num) { if ($scope.pluginLimits != undefined && $scope.pluginLimits[pluginName] != undefined) { for (var i = 0; i < $scope.limitSuffix.length; i++) { var limitName = limitNamePrefix + $scope.limitSuffix[i] var limit = $scope.pluginLimits[pluginName][limitName] - + if (value >= limit) { //console.log("value = " + value + " - limit = " + limit) var pos = limitName.lastIndexOf("_") @@ -244,11 +186,11 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', } return "ok"; } - + $scope.init_refresh_time(); $scope.init_limits(); $scope.init_help(); - + var stop; $scope.configure_refresh = function () { if (!angular.isDefined(stop)) { @@ -258,7 +200,7 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', }, $scope.refreshTime * 1000); // in milliseconds } } - + $scope.$watch( function() { return $scope.refreshTime; }, function(newValue, oldValue) { @@ -273,7 +215,7 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', stop = undefined; } }; - + $scope.$on('$destroy', function() { // Make sure that the interval is destroyed too $scope.stop_refresh(); @@ -281,13 +223,13 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', $scope.onKeyDown = function($event) { console.log($event) - if ($event.keyCode == keycodes.a) { // a Sort processes automatically + if ($event.keyCode == keycodes.a) { // a Sort processes automatically $scope.sort_by() } else if ($event.keyCode == keycodes.c) {//c Sort processes by CPU% $scope.sort_by('cpu_percent') - } else if ($event.keyCode == keycodes.m) {//m Sort processes by MEM% + } else if ($event.keyCode == keycodes.m) {//m Sort processes by MEM% $scope.sort_by('memory_percent') - } else if ($event.keyCode == keycodes.p) {//p Sort processes by name + } else if ($event.keyCode == keycodes.p) {//p Sort processes by name $scope.sort_by('name') } else if ($event.keyCode == keycodes.i) {//i Sort processes by I/O rate $scope.sort_by('io_read') From c17f252a0d7568aba5cfc887b7aad749addaedc9 Mon Sep 17 00:00:00 2001 From: Nicolas Hart Date: Tue, 28 Apr 2015 23:24:02 +0200 Subject: [PATCH 11/15] remove load of unexisting help controller --- glances/outputs/static/html/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/glances/outputs/static/html/index.html b/glances/outputs/static/html/index.html index 308be4a6..19cdb16f 100644 --- a/glances/outputs/static/html/index.html +++ b/glances/outputs/static/html/index.html @@ -18,7 +18,6 @@ - From 494d2634c1ae2e1293b9539790efcc4768978a6c Mon Sep 17 00:00:00 2001 From: Nicolas Hart Date: Tue, 28 Apr 2015 23:25:05 +0200 Subject: [PATCH 12/15] fix issues on docker web ui --- glances/outputs/static/html/plugins/docker.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/glances/outputs/static/html/plugins/docker.html b/glances/outputs/static/html/plugins/docker.html index 2b4e4684..d30fa2d0 100644 --- a/glances/outputs/static/html/plugins/docker.html +++ b/glances/outputs/static/html/plugins/docker.html @@ -1,4 +1,4 @@ -CONTAINERS {{ result['docker']['containers'].length }} (served by 1.2.0) +CONTAINERS {{ result['docker']['containers'].length }} (served by Docker {{ result['docker']['version']['Version'] }})
@@ -13,7 +13,7 @@
{{ container.Id | limitTo:12 }}
{{ container.Names[0] }}
{{ container.Status }}
-
{{ container.cpu.total }}
+
{{ container.cpu.total | number:1 }}
{{ container.memory.usage | bytes }}
{{ container.Command }}
From 248918012a1c850a994be41bc318c65be1c89f6f Mon Sep 17 00:00:00 2001 From: Nicolas Hart Date: Fri, 1 May 2015 14:22:04 +0200 Subject: [PATCH 13/15] fix processlist sort --- glances/outputs/static/css/style.css | 14 +-- .../static/html/plugins/processcount.html | 11 +- .../static/html/plugins/processlist.html | 26 ++-- glances/outputs/static/js/stats_controller.js | 117 +++++++++++------- glances/outputs/static/js/variables.js | 9 +- 5 files changed, 98 insertions(+), 79 deletions(-) diff --git a/glances/outputs/static/css/style.css b/glances/outputs/static/css/style.css index 2ff226f3..448eb031 100644 --- a/glances/outputs/static/css/style.css +++ b/glances/outputs/static/css/style.css @@ -26,20 +26,15 @@ body { padding-bottom: 20px; } -.underline{ +.underline { text-decoration: underline } -.bold{ +.bold { font-weight: bold; } -.sort{ +.sort { font-weight: bold; -} -.sort_asc:after { - content: '\25B2' -} -.sort_desc:after { - content: '\25BC' + color: white; } .text-right { text-align: right; @@ -100,7 +95,6 @@ body { padding: 0px 5px 0px 5px; white-space: nowrap; } - /* Loading page */ #loading-page .glances-logo { diff --git a/glances/outputs/static/html/plugins/processcount.html b/glances/outputs/static/html/plugins/processcount.html index af39392d..0f3a8789 100644 --- a/glances/outputs/static/html/plugins/processcount.html +++ b/glances/outputs/static/html/plugins/processcount.html @@ -1,5 +1,6 @@ -TASKS {{result["processcount"].total}} ({{result["processcount"].thread}} thr), {{result["processcount"].running}} run, - {{result["processcount"].sleeping}} slp, {{result["processcount"].stopped}} oth - sorted automatically -sorted - by {{sortColumn}}, flat view +TASKS +{{result["processcount"].total}} ({{result["processcount"].thread}} thr), +{{result["processcount"].running}} run, +{{result["processcount"].sleeping}} slp, +{{result["processcount"].stopped}} oth + sorted {{ sorter.auto ? 'automatically' : '' }} by {{ sorter.column }}, flat view diff --git a/glances/outputs/static/html/plugins/processlist.html b/glances/outputs/static/html/plugins/processlist.html index a7d8fcdd..3ef93a4e 100644 --- a/glances/outputs/static/html/plugins/processlist.html +++ b/glances/outputs/static/html/plugins/processlist.html @@ -1,19 +1,19 @@
-
CPU%
-
MEM%
- - -
PID
-
USER
-
NI
-
S
- - - -
Command
+
CPU%
+
MEM%
+ + +
PID
+
USER
+
NI
+
S
+ + + +
Command
-
+
{{process.cpu_percent | number:1}}
{{process.memory_percent | number:1}}
diff --git a/glances/outputs/static/js/stats_controller.js b/glances/outputs/static/js/stats_controller.js index dd160457..a65e690d 100644 --- a/glances/outputs/static/js/stats_controller.js +++ b/glances/outputs/static/js/stats_controller.js @@ -1,12 +1,13 @@ -glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', '$routeParams', function($scope, $http, $interval, $q, $routeParams) { +glancesApp.controller('statsController', function($scope, $http, $interval, $q, $routeParams) { - $scope.limitSuffix = ['critical', 'careful', 'warning'] - $scope.refreshTime = 3 - $scope.pluginLimits = [] - $scope.sortColumn = '' - $scope.sortOrderAsc = false - $scope.help_screen = false - $scope.lastSortColumn = '#column_' + $scope.sortColumn + $scope.limitSuffix = ['critical', 'careful', 'warning']; + $scope.refreshTime = 3; + $scope.pluginLimits = []; + $scope.sorter = { + column: "cpu_percent", + auto: true + }; + $scope.help_screen = false; $scope.show = { 'diskio' : true, 'network' : true, @@ -25,7 +26,7 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', 'network_io_cumulative':false, 'filesystem_freespace':false, 'network_by_bytes':true - } + }; $scope.init_refresh_time = function() { if ($routeParams != undefined && $routeParams.refresh_time != undefined) { @@ -36,7 +37,6 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', } } - $scope.init_limits = function() { $scope.plugins_limits(); } @@ -55,32 +55,6 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', } } - $scope.sort_by = function(column) { - if (column == undefined) { - // sort automatically - $scope.sortColumn = undefined - return - } - if (column == 'name' && !$scope.show.short_process_name) { - column = 'cmdline' - } - angular.element(document.querySelector($scope.lastSortColumn)).removeClass('sort sort_asc sort_desc') - - if ($scope.sortColumn == column) { - $scope.sortOrderAsc = !$scope.sortOrderAsc - if ($scope.sortOrderAsc) { - angular.element(document.querySelector($scope.lastSortColumn)).addClass('sort sort_asc') - } else { - angular.element(document.querySelector($scope.lastSortColumn)).addClass('sort sort_desc') - } - } else { - $scope.sortColumn = column - $scope.sortOrderAsc = false - $scope.lastSortColumn = '#column_' + $scope.sortColumn - angular.element(document.querySelector($scope.lastSortColumn)).addClass('sort sort_desc') - } - } - $scope.plugins_limits = function() { $http.get('/api/2/all/limits').success(function(response, status, headers, config) { $scope.limits = response @@ -97,7 +71,6 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', $scope.refreshData = function() { canceler = $q.defer(); $http.get('/api/2/all', {timeout: canceler.promise}).success(function(response, status, headers, config) { - //alert('success'); function timemillis(array) { var sum = 0.0 @@ -170,10 +143,8 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', var limit = $scope.pluginLimits[pluginName][limitName] if (value >= limit) { - //console.log("value = " + value + " - limit = " + limit) var pos = limitName.lastIndexOf("_") var className = limitName.substring(pos + 1) - //console.log("className = " + className) if (num == 1) { return className + '_log' } @@ -222,19 +193,41 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', }); $scope.onKeyDown = function($event) { - console.log($event) if ($event.keyCode == keycodes.a) { // a Sort processes automatically - $scope.sort_by() + $scope.sorter = { + column: "cpu_percent", + auto: true + }; } else if ($event.keyCode == keycodes.c) {//c Sort processes by CPU% - $scope.sort_by('cpu_percent') + $scope.sorter = { + column: "cpu_percent", + auto: false + }; } else if ($event.keyCode == keycodes.m) {//m Sort processes by MEM% - $scope.sort_by('memory_percent') + $scope.sorter = { + column: "memory_percent", + auto: false + }; } else if ($event.keyCode == keycodes.p) {//p Sort processes by name - $scope.sort_by('name') + $scope.sorter = { + column: "name", + auto: false + }; } else if ($event.keyCode == keycodes.i) {//i Sort processes by I/O rate - $scope.sort_by('io_read') + $scope.sorter = { + column: ['io_read', 'io_write'], + auto: false + }; } else if ($event.keyCode == keycodes.t) {//t Sort processes by CPU times - $scope.sort_by('timemillis') + $scope.sorter = { + column: "timemillis", + auto: false + }; + } else if ($event.keyCode == keycodes.u) {//t Sort processes by user + $scope.sorter = { + column: "username", + auto: false + }; } else if ($event.keyCode == keycodes.d) {//d Show/hide disk I/O stats $scope.show_hide('diskio') } else if ($event.keyCode == keycodes.f) {//f Show/hide filesystem stats @@ -279,4 +272,34 @@ glancesApp.controller('statsController', [ '$scope', '$http', '$interval', '$q', // not available } } -} ]); +}) + +.directive("sortableTh", function() { + return { + restrict: 'A', + scope: { + sorter: '=' + }, + link: function (scope, element, attrs) { + + scope.$watch(function() { + return scope.sorter.column; + }, function(newValue, oldValue) { + + if (attrs.column === newValue) { + element.addClass('sort'); + } else { + element.removeClass('sort'); + } + + }); + + element.on('click', function() { + + scope.sorter.column = attrs.column; + + scope.$apply(); + }); + } + }; +}); diff --git a/glances/outputs/static/js/variables.js b/glances/outputs/static/js/variables.js index f164f485..6537b500 100644 --- a/glances/outputs/static/js/variables.js +++ b/glances/outputs/static/js/variables.js @@ -5,22 +5,23 @@ var keycodes = { 'p' : '80', 'i' : '73', 't' : '84', + 'u' : '85', 'd' : '68', 'f' : '70', 'n' : '78', 's' : '83', - 'TWO' : '50', + 'TWO': '50', 'z' : '90', 'e' : '69', - 'SLASH' : '191', + 'SLASH': '191', 'D' : '68', 'b' : '66', 'l' : '76', 'w' : '87', 'x' : '88', - 'ONE' : '49', + 'ONE': '49', 'h' : '72', - 'T' : '84', + 'T' : '84', 'u' : '85', 'F' : '70', 'g' : '71', From 432c669466e4dd5aab47a394db45dea226088961 Mon Sep 17 00:00:00 2001 From: Nicolas Hart Date: Fri, 1 May 2015 17:28:35 +0200 Subject: [PATCH 14/15] handle io sort in process list --- glances/outputs/static/html/index.html | 2 + .../static/html/plugins/processcount.html | 2 +- .../static/html/plugins/processlist.html | 2 +- glances/outputs/static/js/stats_controller.js | 68 +++++---- .../outputs/static/js/vendors/lodash.min.js | 137 ++++++++++++++++++ 5 files changed, 177 insertions(+), 34 deletions(-) create mode 100644 glances/outputs/static/js/vendors/lodash.min.js diff --git a/glances/outputs/static/html/index.html b/glances/outputs/static/html/index.html index 19cdb16f..f9b20a70 100644 --- a/glances/outputs/static/html/index.html +++ b/glances/outputs/static/html/index.html @@ -13,9 +13,11 @@ + + diff --git a/glances/outputs/static/html/plugins/processcount.html b/glances/outputs/static/html/plugins/processcount.html index 0f3a8789..a787edd3 100644 --- a/glances/outputs/static/html/plugins/processcount.html +++ b/glances/outputs/static/html/plugins/processcount.html @@ -3,4 +3,4 @@ {{result["processcount"].running}} run, {{result["processcount"].sleeping}} slp, {{result["processcount"].stopped}} oth - sorted {{ sorter.auto ? 'automatically' : '' }} by {{ sorter.column }}, flat view + sorted {{ sorter.auto ? 'automatically' : '' }} by {{ sorter.getColumnLabel(sorter.column) }}, flat view diff --git a/glances/outputs/static/html/plugins/processlist.html b/glances/outputs/static/html/plugins/processlist.html index 3ef93a4e..15535ca7 100644 --- a/glances/outputs/static/html/plugins/processlist.html +++ b/glances/outputs/static/html/plugins/processlist.html @@ -13,7 +13,7 @@
Command
-
+
{{process.cpu_percent | number:1}}
{{process.memory_percent | number:1}}
diff --git a/glances/outputs/static/js/stats_controller.js b/glances/outputs/static/js/stats_controller.js index a65e690d..f894a79c 100644 --- a/glances/outputs/static/js/stats_controller.js +++ b/glances/outputs/static/js/stats_controller.js @@ -5,7 +5,17 @@ glancesApp.controller('statsController', function($scope, $http, $interval, $q, $scope.pluginLimits = []; $scope.sorter = { column: "cpu_percent", - auto: true + auto: true, + isReverseColumn: function(column) { + return !(column == 'username' || column == 'name'); + }, + getColumnLabel: function(column) { + if (_.isEqual(column, ['io_read', 'io_write'])) { + return 'io_counters'; + } else { + return column; + } + } }; $scope.help_screen = false; $scope.show = { @@ -194,40 +204,26 @@ glancesApp.controller('statsController', function($scope, $http, $interval, $q, $scope.onKeyDown = function($event) { if ($event.keyCode == keycodes.a) { // a Sort processes automatically - $scope.sorter = { - column: "cpu_percent", - auto: true - }; + $scope.sorter.column = "cpu_percent"; + $scope.sorter.auto = true; } else if ($event.keyCode == keycodes.c) {//c Sort processes by CPU% - $scope.sorter = { - column: "cpu_percent", - auto: false - }; + $scope.sorter.column = "cpu_percent"; + $scope.sorter.auto = false; } else if ($event.keyCode == keycodes.m) {//m Sort processes by MEM% - $scope.sorter = { - column: "memory_percent", - auto: false - }; + $scope.sorter.column = "memory_percent"; + $scope.sorter.auto = false; } else if ($event.keyCode == keycodes.p) {//p Sort processes by name - $scope.sorter = { - column: "name", - auto: false - }; + $scope.sorter.column = "name"; + $scope.sorter.auto = false; } else if ($event.keyCode == keycodes.i) {//i Sort processes by I/O rate - $scope.sorter = { - column: ['io_read', 'io_write'], - auto: false - }; + $scope.sorter.column = ['io_read', 'io_write']; + $scope.sorter.auto = false; } else if ($event.keyCode == keycodes.t) {//t Sort processes by CPU times - $scope.sorter = { - column: "timemillis", - auto: false - }; + $scope.sorter.column = "timemillis"; + $scope.sorter.auto = false; } else if ($event.keyCode == keycodes.u) {//t Sort processes by user - $scope.sorter = { - column: "username", - auto: false - }; + $scope.sorter.column = "username"; + $scope.sorter.auto = false; } else if ($event.keyCode == keycodes.d) {//d Show/hide disk I/O stats $scope.show_hide('diskio') } else if ($event.keyCode == keycodes.f) {//f Show/hide filesystem stats @@ -286,10 +282,18 @@ glancesApp.controller('statsController', function($scope, $http, $interval, $q, return scope.sorter.column; }, function(newValue, oldValue) { - if (attrs.column === newValue) { - element.addClass('sort'); + if (angular.isArray(newValue)) { + if (newValue.indexOf(attrs.column) !== -1) { + element.addClass('sort'); + } else { + element.removeClass('sort'); + } } else { - element.removeClass('sort'); + if (attrs.column === newValue) { + element.addClass('sort'); + } else { + element.removeClass('sort'); + } } }); diff --git a/glances/outputs/static/js/vendors/lodash.min.js b/glances/outputs/static/js/vendors/lodash.min.js new file mode 100644 index 00000000..0d0fd334 --- /dev/null +++ b/glances/outputs/static/js/vendors/lodash.min.js @@ -0,0 +1,137 @@ +/** + * @license + * lodash 3.8.0 (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE + * Build: `lodash modern -o ./lodash.js` + */ +;(function(){function n(n,t){if(n!==t){var r=n===n,e=t===t;if(n>t||!r||n===w&&e)return 1;if(n=n&&9<=n&&13>=n||32==n||160==n||5760==n||6158==n||8192<=n&&(8202>=n||8232==n||8233==n||8239==n||8287==n||12288==n||65279==n); + +}function v(n,t){for(var r=-1,e=n.length,u=-1,o=[];++ri(t,a,0)&&u.push(a);return u}function at(n,t){var r=true;return zu(n,function(n,e,u){return r=!!t(n,e,u)}),r}function ct(n,t){var r=[];return zu(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function lt(n,t,r,e){var u;return r(n,function(n,r,o){return t(n,r,o)?(u=e?r:n,false):void 0}),u}function st(n,t,r){ +for(var e=-1,u=n.length,o=-1,i=[];++et&&(t=-t>u?0:u+t),r=r===w||r>u?u:+r||0,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0, +r=Ue(u);++eu(a,s,0)&&((t||f)&&a.push(s),c.push(l))}return c}function Ut(n,t){for(var r=-1,e=t.length,u=Ue(e);++r>>1,i=n[o];(r?i<=t:iu?null:o,u=1);++earguments.length;return typeof e=="function"&&o===w&&To(r)?n(r,e,u,i):Et(r,dr(e,o,4),u,i,t)}}function cr(n,t,r,e,u,o,i,f,a,c){function l(){for(var b=arguments.length,j=b,k=Ue(b);j--;)k[j]=arguments[j];if(e&&(k=Mt(k,e,u)),o&&(k=Dt(k,o,i)),_||y){var j=l.placeholder,O=v(k,j),b=b-O.length;if(bu)||i===e&&i===o)&&(u=i,o=n)}),o}function dr(n,t,r){var e=$n.callback||Ee,e=e===Ee?ut:e;return r?e(n,t,r):e}function mr(n,t,e){var u=$n.indexOf||Dr,u=u===Dr?r:u;return n?u(n,t,e):u}function wr(n){var t=n.length,r=new n.constructor(t); + +return t&&"string"==typeof n[0]&&Ge.call(n,"index")&&(r.index=n.index,r.input=n.input),r}function br(n){return n=n.constructor,typeof n=="function"&&n instanceof n||(n=Be),new n}function xr(n,t,r){var e=n.constructor;switch(t){case J:return Bt(n);case D:case P:return new e(+n);case X:case H:case Q:case nn:case tn:case rn:case en:case un:case on:return t=n.buffer,new e(r?Bt(t):t,n.byteOffset,n.length);case V:case G:return new e(n);case Z:var u=new e(n.source,kn.exec(n));u.lastIndex=n.lastIndex}return u; + +}function Ar(n,t,r){return null==n||Er(t,n)||(t=$r(t),n=1==t.length?n:gt(n,It(t,0,-1)),t=Pr(t)),t=null==n?n:n[t],null==t?w:t.apply(n,r)}function jr(n){return null!=n&&Rr(Yu(n))}function kr(n,t){return n=+n,t=null==t?Tu:t,-1t?0:t)):[]}function Br(n,t,r){var e=n?n.length:0;return e?((r?Or(n,t,r):null==t)&&(t=1),t=e-(+t||0),It(n,0,0>t?0:t)):[]}function Mr(n){return n?n[0]:w}function Dr(n,t,e){var u=n?n.length:0;if(!u)return-1;if(typeof e=="number")e=0>e?bu(u+e,0):e;else if(e)return e=$t(n,t),n=n[e],(t===t?t===n:n!==n)?e:-1;return r(n,t,e||0)}function Pr(n){var t=n?n.length:0;return t?n[t-1]:w}function qr(n){return zr(n,1)}function Kr(n,t,e,u){ +if(!n||!n.length)return[];null!=t&&typeof t!="boolean"&&(u=e,e=Or(n,t,u)?null:t,t=false);var o=dr();if((o!==ut||null!=e)&&(e=o(e,u,3)),t&&mr()==r){t=e;var i;e=-1,u=n.length;for(var o=-1,f=[];++er?bu(u+r,0):r||0,typeof n=="string"||!To(n)&&ve(n)?rt?0:+t||0,n.length),n)}function re(n){n=Nr(n);for(var t=-1,r=n.length,e=Ue(r);++t=n&&(t=null),r}}function oe(n,t,r){function e(){var r=t-(wo()-c);0>=r||r>t?(f&&eu(f),r=p,f=s=p=w,r&&(h=wo(),a=n.apply(l,i),s||f||(i=l=null))):s=su(e,r)}function u(){s&&eu(s),f=s=p=w,(v||_!==t)&&(h=wo(),a=n.apply(l,i),s||f||(i=l=null))}function o(){if(i=arguments,c=wo(),l=this,p=v&&(s||!g), +!1===_)var r=g&&!s;else{f||g||(h=c);var o=_-(c-h),y=0>=o||o>_;y?(f&&(f=eu(f)),h=c,a=n.apply(l,i)):f||(f=su(u,o))}return y&&s?s=eu(s):s||t===_||(s=su(e,t)),r&&(y=true,a=n.apply(l,i)),!y||s||f||(i=l=null),a}var i,f,a,c,l,s,p,h=0,_=false,v=true;if(typeof n!="function")throw new Pe(L);if(t=0>t?0:+t||0,true===r)var g=true,v=false;else se(r)&&(g=r.leading,_="maxWait"in r&&bu(+r.maxWait||0,t),v="trailing"in r?r.trailing:v);return o.cancel=function(){s&&eu(s),f&&eu(f),f=s=p=w},o}function ie(n,t){function r(){var e=arguments,u=r.cache,o=t?t.apply(this,e):e[0]; + +return u.has(o)?u.get(o):(e=n.apply(this,e),u.set(o,e),e)}if(typeof n!="function"||t&&typeof t!="function")throw new Pe(L);return r.cache=new ie.Cache,r}function fe(n,t){if(typeof n!="function")throw new Pe(L);return t=bu(t===w?n.length-1:+t||0,0),function(){for(var r=arguments,e=-1,u=bu(r.length-t,0),o=Ue(u);++et||!n||!mu(t))return r;do t%2&&(r+=n),t=uu(t/2),n+=n;while(t);return r}function ke(n,t,r){var e=n;return(n=u(n))?(r?Or(e,t,r):null==t)?n.slice(g(n),y(n)+1):(t+="",n.slice(i(n,t),f(n,t)+1)):n}function Oe(n,t,r){return r&&Or(n,t,r)&&(t=null),n=u(n),n.match(t||Wn)||[]}function Ee(n,t,r){return r&&Or(n,t,r)&&(t=null),h(n)?Ce(n):ut(n,t)}function Ie(n){ +return function(){return n}}function Re(n){return n}function Ce(n){return wt(ot(n,true))}function We(n,t,r){if(null==r){var e=se(t),u=e&&Ko(t);((u=u&&u.length&&vt(t,u))?u.length:e)||(u=false,r=t,t=n,n=this)}u||(u=vt(t,Ko(t)));var o=true,e=-1,i=No(n),f=u.length;false===r?o=false:se(r)&&"chain"in r&&(o=r.chain);for(;++e>>1,Su=vu?vu.BYTES_PER_ELEMENT:0,Tu=Le.pow(2,53)-1,Uu=_u&&new _u,Nu={},Fu=$n.support={}; + +!function(n){function t(){this.x=n}var r=arguments,e=[];t.prototype={valueOf:n,y:n};for(var u in new t)e.push(u);Fu.funcDecomp=/\bthis\b/.test(function(){return this}),Fu.funcNames=typeof $e.name=="string";try{Fu.dom=11===Ye.createDocumentFragment().nodeType}catch(o){Fu.dom=false}try{Fu.nonEnumArgs=!cu.call(r,1)}catch(i){Fu.nonEnumArgs=true}}(1,0),$n.templateSettings={escape:_n,evaluate:vn,interpolate:gn,variable:"",imports:{_:$n}};var $u=gu||function(n,t){return null==t?n:et(t,Zu(t),et(t,Ko(t),n))},Lu=function(){ +function n(){}return function(t){if(se(t)){n.prototype=t;var r=new n;n.prototype=null}return r||_.Object()}}(),zu=Kt(ht),Bu=Kt(_t,true),Mu=Vt(),Du=Vt(true),Pu=Uu?function(n,t){return Uu.set(n,t),n}:Re;tu||(Bt=nu&&hu?function(n){var t=n.byteLength,r=vu?uu(t/Su):0,e=r*Su,u=new nu(t);if(r){var o=new vu(u,0,r);o.set(new vu(n,0,r))}return t!=e&&(o=new hu(u,e),o.set(new hu(n,e))),u}:Ie(null));var qu=du&&lu?function(n){return new Dn(n)}:Ie(null),Ku=Uu?function(n){return Uu.get(n)}:Se,Vu=function(){return Fu.funcNames?"constant"==Ie.name?At("name"):function(n){ +for(var t=n.name,r=Nu[t],e=r?r.length:0;e--;){var u=r[e],o=u.func;if(null==o||o==n)return u.name}return t}:Ie("")}(),Yu=At("length"),Zu=ou?function(n){return ou(Fr(n))}:Ie([]),Gu=function(){var n=0,t=0;return function(r,e){var u=wo(),o=U-(u-t);if(t=u,0=T)return r}else n=0;return Pu(r,e)}}(),Ju=fe(function(n,t){return jr(n)?ft(n,st(t,false,true)):[]}),Xu=Qt(),Hu=Qt(true),Qu=fe(function(t,r){r=st(r);var e=rt(t,r);return kt(t,r.sort(n)),e}),no=pr(),to=pr(true),ro=fe(function(n){return Tt(st(n,false,true)); + +}),eo=fe(function(n,t){return jr(n)?ft(n,t):[]}),uo=fe(Vr),oo=fe(function(n){var t=n.length,r=n[t-2],e=n[t-1];return 2e&&(e=u)}return e}),fi=Xt(function(n){for(var t=-1,r=n.length,e=Iu;++t--n?t.apply(this,arguments):void 0}},$n.ary=function(n,t,r){return r&&Or(n,t,r)&&(t=null), +t=n&&null==t?n.length:bu(+t||0,0),hr(n,R,null,null,null,null,t)},$n.assign=$o,$n.at=io,$n.before=ue,$n.bind=bo,$n.bindAll=xo,$n.bindKey=Ao,$n.callback=Ee,$n.chain=Gr,$n.chunk=function(n,t,r){t=(r?Or(n,t,r):null==t)?1:bu(+t||1,1),r=0;for(var e=n?n.length:0,u=-1,o=Ue(ru(e/t));rr&&(r=-r>u?0:u+r),e=e===w||e>u?u:+e||0,0>e&&(e+=u),u=r>e?0:e>>>0,r>>>=0;re)return f;var i=n[0],c=-1,l=i?i.length:0,s=u[0];n:for(;++c(s?Pn(s,a):o(f,a,0))){for(t=e;--t;){var p=u[t];if(0>(p?Pn(p,a):o(n[t],a,0)))continue n}s&&s.push(a),f.push(a)}return f},$n.invert=function(n,t,r){r&&Or(n,t,r)&&(t=null),r=-1;for(var e=Ko(n),u=e.length,o={};++rt?0:t)):[]},$n.takeRight=function(n,t,r){var e=n?n.length:0;return e?((r?Or(n,t,r):null==t)&&(t=1),t=e-(+t||0),It(n,0>t?0:t)):[]},$n.takeRightWhile=function(n,t,r){return n&&n.length?Nt(n,dr(t,r,3),false,true):[]},$n.takeWhile=function(n,t,r){return n&&n.length?Nt(n,dr(t,r,3)):[]},$n.tap=function(n,t,r){return t.call(r,n),n},$n.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new Pe(L);return false===r?e=false:se(r)&&(e="leading"in r?!!r.leading:e, +u="trailing"in r?!!r.trailing:u),Fn.leading=e,Fn.maxWait=+t,Fn.trailing=u,oe(n,t,Fn)},$n.thru=Jr,$n.times=function(n,t,r){if(n=uu(n),1>n||!mu(n))return[];var e=-1,u=Ue(xu(n,Ru));for(t=zt(t,r,1);++er?0:+r||0,e),r-=t.length,0<=r&&n.indexOf(t,r)==r},$n.escape=function(n){return(n=u(n))&&hn.test(n)?n.replace(sn,l):n},$n.escapeRegExp=xe,$n.every=Xr,$n.find=ao,$n.findIndex=Xu,$n.findKey=zo,$n.findLast=co,$n.findLastIndex=Hu,$n.findLastKey=Bo,$n.findWhere=function(n,t){return ao(n,wt(t))},$n.first=Mr,$n.get=function(n,t,r){return n=null==n?w:gt(n,$r(t),t+""),n===w?r:n},$n.has=function(n,t){if(null==n)return false;var r=Ge.call(n,t); + +return r||Er(t)||(t=$r(t),n=1==t.length?n:gt(n,It(t,0,-1)),t=Pr(t),r=null!=n&&Ge.call(n,t)),r},$n.identity=Re,$n.includes=Qr,$n.indexOf=Dr,$n.inRange=function(n,t,r){return t=+t||0,"undefined"===typeof r?(r=t,t=0):r=+r||0,n>=xu(t,r)&&nr?bu(e+r,0):xu(r||0,e-1))+1;else if(r)return u=$t(n,t,true)-1,n=n[u],(t===t?t===n:n!==n)?u:-1;if(t!==t)return p(n,u,true);for(;u--;)if(n[u]===t)return u;return-1},$n.max=ii,$n.min=fi,$n.noConflict=function(){ +return _._=He,this},$n.noop=Se,$n.now=wo,$n.pad=function(n,t,r){n=u(n),t=+t;var e=n.length;return er?0:+r||0,n.length),n.lastIndexOf(t,r)==r},$n.sum=function(n,t,r){r&&Or(n,t,r)&&(t=null); + +var e=dr(),u=null==t;if(e===ut&&u||(u=false,t=e(t,r,3)),u){for(n=To(n)?n:Nr(n),t=n.length,r=0;t--;)r+=+n[t]||0;n=r}else n=St(n,t);return n},$n.template=function(n,t,r){var e=$n.templateSettings;r&&Or(n,t,r)&&(t=r=null),n=u(n),t=tt($u({},r||t),e,nt),r=tt($u({},t.imports),e.imports,nt);var o,i,f=Ko(r),a=Ut(r,f),c=0;r=t.interpolate||Rn;var l="__p+='";r=Me((t.escape||Rn).source+"|"+r.source+"|"+(r===gn?jn:Rn).source+"|"+(t.evaluate||Rn).source+"|$","g");var p="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":""; + +if(n.replace(r,function(t,r,e,u,f,a){return e||(e=u),l+=n.slice(c,a).replace(Cn,s),r&&(o=true,l+="'+__e("+r+")+'"),f&&(i=true,l+="';"+f+";\n__p+='"),e&&(l+="'+((__t=("+e+"))==null?'':__t)+'"),c=a+t.length,t}),l+="';",(t=t.variable)||(l="with(obj){"+l+"}"),l=(i?l.replace(fn,""):l).replace(an,"$1").replace(cn,"$1;"),l="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(o?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}", +t=ei(function(){return $e(f,p+"return "+l).apply(w,a)}),t.source=l,le(t))throw t;return t},$n.trim=ke,$n.trimLeft=function(n,t,r){var e=n;return(n=u(n))?n.slice((r?Or(e,t,r):null==t)?g(n):i(n,t+"")):n},$n.trimRight=function(n,t,r){var e=n;return(n=u(n))?(r?Or(e,t,r):null==t)?n.slice(0,y(n)+1):n.slice(0,f(n,t+"")+1):n},$n.trunc=function(n,t,r){r&&Or(n,t,r)&&(t=null);var e=W;if(r=S,null!=t)if(se(t)){var o="separator"in t?t.separator:o,e="length"in t?+t.length||0:e;r="omission"in t?u(t.omission):r}else e=+t||0; + +if(n=u(n),e>=n.length)return n;if(e-=r.length,1>e)return r;if(t=n.slice(0,e),null==o)return t+r;if(_e(o)){if(n.slice(e).search(o)){var i,f=n.slice(0,e);for(o.global||(o=Me(o.source,(kn.exec(o)||"")+"g")),o.lastIndex=0;n=o.exec(f);)i=n.index;t=t.slice(0,null==i?e:i)}}else n.indexOf(o,e)!=e&&(o=t.lastIndexOf(o),-1u.__dir__?"Right":"") +}),u},Bn.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()},Bn.prototype[n+"RightWhile"]=function(n,t){return this.reverse()[r](n,t).reverse()}}),Kn(["first","last"],function(n,t){var r="take"+(t?"Right":"");Bn.prototype[n]=function(){return this[r](1).value()[0]}}),Kn(["initial","rest"],function(n,t){var r="drop"+(t?"":"Right");Bn.prototype[n]=function(){return this[r](1)}}),Kn(["pluck","where"],function(n,t){var r=t?"filter":"map",e=t?wt:Te;Bn.prototype[n]=function(n){return this[r](e(n)); + +}}),Bn.prototype.compact=function(){return this.filter(Re)},Bn.prototype.reject=function(n,t){return n=dr(n,t,1),this.filter(function(t){return!n(t)})},Bn.prototype.slice=function(n,t){n=null==n?0:+n||0;var r=this;return 0>n?r=this.takeRight(-n):n&&(r=this.drop(n)),t!==w&&(t=+t||0,r=0>t?r.dropRight(-t):r.take(t-n)),r},Bn.prototype.toArray=function(){return this.drop(0)},ht(Bn.prototype,function(n,t){var r=$n[t];if(r){var e=/^(?:filter|map|reject)|While$/.test(t),u=/^(?:first|last)$/.test(t);$n.prototype[t]=function(){ +function t(n){return n=[n],fu.apply(n,o),r.apply($n,n)}var o=arguments,i=this.__chain__,f=this.__wrapped__,a=!!this.__actions__.length,c=f instanceof Bn,l=o[0],s=c||To(f);return s&&e&&typeof l=="function"&&1!=l.length&&(c=s=false),c=c&&!a,u&&!i?c?n.call(f):r.call($n,this.value()):s?(f=n.apply(c?f:new Bn(this),o),u||!a&&!f.__actions__||(f.__actions__||(f.__actions__=[])).push({func:Jr,args:[t],thisArg:$n}),new zn(f,i)):this.thru(t)}}}),Kn("concat join pop push replace shift sort splice split unshift".split(" "),function(n){ +var t=(/^(?:replace|split)$/.test(n)?Ve:qe)[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:join|pop|replace|shift)$/.test(n);$n.prototype[n]=function(){var n=arguments;return e&&!this.__chain__?t.apply(this.value(),n):this[r](function(r){return t.apply(r,n)})}}),ht(Bn.prototype,function(n,t){var r=$n[t];if(r){var e=r.name;(Nu[e]||(Nu[e]=[])).push({name:t,func:r})}}),Nu[cr(null,A).name]=[{name:"wrapper",func:null}],Bn.prototype.clone=function(){var n=this.__actions__,t=this.__iteratees__,r=this.__views__,e=new Bn(this.__wrapped__); + +return e.__actions__=n?qn(n):null,e.__dir__=this.__dir__,e.__filtered__=this.__filtered__,e.__iteratees__=t?qn(t):null,e.__takeCount__=this.__takeCount__,e.__views__=r?qn(r):null,e},Bn.prototype.reverse=function(){if(this.__filtered__){var n=new Bn(this);n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},Bn.prototype.value=function(){var n=this.__wrapped__.value();if(!To(n))return Ft(n,this.__actions__);var t,r=this.__dir__,e=0>r;t=n.length;for(var u=this.__views__,o=0,i=-1,f=u?u.length:0;++ip.index:u=_:!h(s))))continue n}else if(p=h(s), +_==$)s=p;else if(!p){if(_==F)continue n;break n}}c[a++]=s}return c},$n.prototype.chain=function(){return Gr(this)},$n.prototype.commit=function(){return new zn(this.value(),this.__chain__)},$n.prototype.plant=function(n){for(var t,r=this;r instanceof Ln;){var e=Lr(r);t?u.__wrapped__=e:t=e;var u=e,r=r.__wrapped__}return u.__wrapped__=n,t},$n.prototype.reverse=function(){var n=this.__wrapped__;return n instanceof Bn?(this.__actions__.length&&(n=new Bn(this)),new zn(n.reverse(),this.__chain__)):this.thru(function(n){ +return n.reverse()})},$n.prototype.toString=function(){return this.value()+""},$n.prototype.run=$n.prototype.toJSON=$n.prototype.valueOf=$n.prototype.value=function(){return Ft(this.__wrapped__,this.__actions__)},$n.prototype.collect=$n.prototype.map,$n.prototype.head=$n.prototype.first,$n.prototype.select=$n.prototype.filter,$n.prototype.tail=$n.prototype.rest,$n}var w,b="3.8.0",x=1,A=2,j=4,k=8,O=16,E=32,I=64,R=128,C=256,W=30,S="...",T=150,U=16,N=0,F=1,$=2,L="Expected a function",z="__lodash_placeholder__",B="[object Arguments]",M="[object Array]",D="[object Boolean]",P="[object Date]",q="[object Error]",K="[object Function]",V="[object Number]",Y="[object Object]",Z="[object RegExp]",G="[object String]",J="[object ArrayBuffer]",X="[object Float32Array]",H="[object Float64Array]",Q="[object Int8Array]",nn="[object Int16Array]",tn="[object Int32Array]",rn="[object Uint8Array]",en="[object Uint8ClampedArray]",un="[object Uint16Array]",on="[object Uint32Array]",fn=/\b__p\+='';/g,an=/\b(__p\+=)''\+/g,cn=/(__e\(.*?\)|\b__t\))\+'';/g,ln=/&(?:amp|lt|gt|quot|#39|#96);/g,sn=/[&<>"'`]/g,pn=RegExp(ln.source),hn=RegExp(sn.source),_n=/<%-([\s\S]+?)%>/g,vn=/<%([\s\S]+?)%>/g,gn=/<%=([\s\S]+?)%>/g,yn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,dn=/^\w*$/,mn=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,wn=/[.*+?^${}()|[\]\/\\]/g,bn=RegExp(wn.source),xn=/[\u0300-\u036f\ufe20-\ufe23]/g,An=/\\(\\)?/g,jn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,kn=/\w*$/,On=/^0[xX]/,En=/^\[object .+?Constructor\]$/,In=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g,Rn=/($^)/,Cn=/['\n\r\u2028\u2029\\]/g,Wn=RegExp("[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?=[A-Z\\xc0-\\xd6\\xd8-\\xde][a-z\\xdf-\\xf6\\xf8-\\xff]+)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+|[A-Z\\xc0-\\xd6\\xd8-\\xde]+|[0-9]+","g"),Sn=" \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",Tn="Array ArrayBuffer Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Math Number Object RegExp Set String _ clearTimeout document isFinite parseInt setTimeout TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap window".split(" "),Un={}; + +Un[X]=Un[H]=Un[Q]=Un[nn]=Un[tn]=Un[rn]=Un[en]=Un[un]=Un[on]=true,Un[B]=Un[M]=Un[J]=Un[D]=Un[P]=Un[q]=Un[K]=Un["[object Map]"]=Un[V]=Un[Y]=Un[Z]=Un["[object Set]"]=Un[G]=Un["[object WeakMap]"]=false;var Nn={};Nn[B]=Nn[M]=Nn[J]=Nn[D]=Nn[P]=Nn[X]=Nn[H]=Nn[Q]=Nn[nn]=Nn[tn]=Nn[V]=Nn[Y]=Nn[Z]=Nn[G]=Nn[rn]=Nn[en]=Nn[un]=Nn[on]=true,Nn[q]=Nn[K]=Nn["[object Map]"]=Nn["[object Set]"]=Nn["[object WeakMap]"]=false;var Fn={leading:false,maxWait:0,trailing:false},$n={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A", +"\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u", +"\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss"},Ln={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},zn={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"},Bn={"function":true,object:true},Mn={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Dn=Bn[typeof exports]&&exports&&!exports.nodeType&&exports,Pn=Bn[typeof module]&&module&&!module.nodeType&&module,qn=Bn[typeof self]&&self&&self.Object&&self,Kn=Bn[typeof window]&&window&&window.Object&&window,Vn=Pn&&Pn.exports===Dn&&Dn,Yn=Dn&&Pn&&typeof global=="object"&&global&&global.Object&&global||Kn!==(this&&this.window)&&Kn||qn||this,Zn=m(); + +typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Yn._=Zn, define(function(){return Zn})):Dn&&Pn?Vn?(Pn.exports=Zn)._=Zn:Dn._=Zn:Yn._=Zn}).call(this); \ No newline at end of file From 7332bd5104d3124539d5f53968dfef8839e7fd8f Mon Sep 17 00:00:00 2001 From: Nicolas Hart Date: Fri, 1 May 2015 17:48:57 +0200 Subject: [PATCH 15/15] add monitor plugin --- glances/outputs/static/css/style.css | 6 ++ .../html/components/monitor_process.html | 4 + .../outputs/static/html/plugins/monitor.html | 3 + glances/outputs/static/html/stats.html | 6 +- glances/outputs/static/js/directives.js | 78 +++++++++++++++++++ glances/outputs/static/js/stats_controller.js | 38 --------- 6 files changed, 94 insertions(+), 41 deletions(-) create mode 100644 glances/outputs/static/html/components/monitor_process.html create mode 100644 glances/outputs/static/html/plugins/monitor.html create mode 100644 glances/outputs/static/js/directives.js diff --git a/glances/outputs/static/css/style.css b/glances/outputs/static/css/style.css index 448eb031..4bb9e6f8 100644 --- a/glances/outputs/static/css/style.css +++ b/glances/outputs/static/css/style.css @@ -95,6 +95,12 @@ body { padding: 0px 5px 0px 5px; white-space: nowrap; } +gl-monitor-list { + display: block; +} +gl-monitor-list .table-cell { + text-align: left; +} /* Loading page */ #loading-page .glances-logo { diff --git a/glances/outputs/static/html/components/monitor_process.html b/glances/outputs/static/html/components/monitor_process.html new file mode 100644 index 00000000..ec883040 --- /dev/null +++ b/glances/outputs/static/html/components/monitor_process.html @@ -0,0 +1,4 @@ +
{{ process.description }}
+
{{ process.count > 1 ? process.count : '' }}
+
{{ process.count > 0 ? 'RUNNING' : 'NOT RUNNING' }}
+
{{ process.result }}
diff --git a/glances/outputs/static/html/plugins/monitor.html b/glances/outputs/static/html/plugins/monitor.html new file mode 100644 index 00000000..01ded842 --- /dev/null +++ b/glances/outputs/static/html/plugins/monitor.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/glances/outputs/static/html/stats.html b/glances/outputs/static/html/stats.html index e620342e..6d5f64b7 100644 --- a/glances/outputs/static/html/stats.html +++ b/glances/outputs/static/html/stats.html @@ -53,11 +53,11 @@
-
-
+
+ +
-
diff --git a/glances/outputs/static/js/directives.js b/glances/outputs/static/js/directives.js new file mode 100644 index 00000000..d0f48d35 --- /dev/null +++ b/glances/outputs/static/js/directives.js @@ -0,0 +1,78 @@ +glancesApp.directive("sortableTh", function() { + return { + restrict: 'A', + scope: { + sorter: '=' + }, + link: function (scope, element, attrs) { + + scope.$watch(function() { + return scope.sorter.column; + }, function(newValue, oldValue) { + + if (angular.isArray(newValue)) { + if (newValue.indexOf(attrs.column) !== -1) { + element.addClass('sort'); + } else { + element.removeClass('sort'); + } + } else { + if (attrs.column === newValue) { + element.addClass('sort'); + } else { + element.removeClass('sort'); + } + } + + }); + + element.on('click', function() { + + scope.sorter.column = attrs.column; + + scope.$apply(); + }); + } + }; +}); + +glancesApp.directive("glMonitorList", function() { + return { + restrict: 'AE', + scope: { + processes: '=' + }, + templateUrl: 'plugins/monitor.html', + controller: function() { + + } + } +}) + +glancesApp.directive("glMonitorProcess", function() { + return { + restrict: 'AE', + require: "^glMonitorList", + templateUrl: 'components/monitor_process.html', + scope: { + process: '=' + }, + link: function(scope, element, attrs) { + + count = scope.process.count; + countMin = scope.process.countmin; + countMax = scope.process.countmax; + + if (count > 0) { + if ((countMin == null || count >= countMin) && (countMax == null || count <= countMax)) { + scope.descriptionClass = 'ok'; + } else { + scope.descriptionClass = 'careful'; + } + } else { + scope.descriptionClass = countMin == null ? 'ok' : 'critical'; + } + + } + } +}); diff --git a/glances/outputs/static/js/stats_controller.js b/glances/outputs/static/js/stats_controller.js index f894a79c..569d265f 100644 --- a/glances/outputs/static/js/stats_controller.js +++ b/glances/outputs/static/js/stats_controller.js @@ -268,42 +268,4 @@ glancesApp.controller('statsController', function($scope, $http, $interval, $q, // not available } } -}) - -.directive("sortableTh", function() { - return { - restrict: 'A', - scope: { - sorter: '=' - }, - link: function (scope, element, attrs) { - - scope.$watch(function() { - return scope.sorter.column; - }, function(newValue, oldValue) { - - if (angular.isArray(newValue)) { - if (newValue.indexOf(attrs.column) !== -1) { - element.addClass('sort'); - } else { - element.removeClass('sort'); - } - } else { - if (attrs.column === newValue) { - element.addClass('sort'); - } else { - element.removeClass('sort'); - } - } - - }); - - element.on('click', function() { - - scope.sorter.column = attrs.column; - - scope.$apply(); - }); - } - }; });