diff --git a/glances/core/glances_autodiscover.py b/glances/core/glances_autodiscover.py index ec42eed99e1dbc225c603a85f45e39a97ad22556..830b13317a1a53a10c1f966a978d191d20cf396c 100644 --- a/glances/core/glances_autodiscover.py +++ b/glances/core/glances_autodiscover.py @@ -45,31 +45,30 @@ class AutoDiscovered(object): """Class to manage the auto discovered servers dict""" def __init__(self): - # server_dict is a dict of dict - # !!! server_dict SHOULD BE REFACTOR to list of dict to be JSON compliant - # key: ip:port - # value: {'cpu': 3, 'mem': 34 ...} - self._server_dict = {} + # server_dict is a list of dict (JSON compliant) + # [ {'key': 'zeroconf name', ip': '172.1.2.3', 'port': 61209, 'cpu': 3, 'mem': 34 ...} ... ] + self._server_list = [] def get_servers_list(self): - """Return the current server list (dict of dict)""" - return self._server_dict + """Return the current server list (list of dict)""" + return self._server_list def add_server(self, name, ip, port): - """Add a new server to the dict""" - try: - self._server_dict[name] = {'name': name.split(':')[0], 'ip': ip, 'port': port} - logger.debug("Servers list: %s" % self._server_dict) - except KeyError: - pass + """Add a new server to the list""" + new_server = {'key': name, 'name': name.split(':')[0], 'ip': ip, 'port': port} + self._server_list.append(new_server) + logger.debug("Servers list: %s" % self._server_list) def remove_server(self, name): """Remove a server from the dict""" - try: - del(self._server_dict[name]) - logger.debug("Servers list: %s" % self._server_dict) - except KeyError: - pass + for i in self._server_list: + if i['key'] == name: + try: + self._server_list.remove(i) + logger.debug("Remove server %s from the list" % name) + logger.debug("Updated servers list: %s" % self._server_list) + except ValueError: + logger.error("Can not remove server %s from the list" % name) class GlancesAutoDiscoverListener(object): @@ -81,7 +80,7 @@ class GlancesAutoDiscoverListener(object): self.servers = AutoDiscovered() def get_servers_list(self): - """Return the current server list (dict of dict)""" + """Return the current server list (list of dict)""" return self.servers.get_servers_list() def addService(self, zeroconf, srv_type, srv_name): diff --git a/glances/core/glances_client.py b/glances/core/glances_client.py index 1620e4af54699157c63c256c2efe3b7180dfa1ab..a3788b74e96062f7f00ec17f92cc0cfb5f55a5d6 100644 --- a/glances/core/glances_client.py +++ b/glances/core/glances_client.py @@ -37,7 +37,7 @@ except: # Import Glances libs from glances.core.glances_globals import version, logger from glances.core.glances_stats import GlancesStatsClient -from glances.outputs.glances_curses import GlancesCurses +from glances.outputs.glances_curses import GlancesCursesClient from glances.core.glances_autodiscover import GlancesAutoDiscoverServer @@ -164,7 +164,7 @@ class GlancesClient(object): self.stats.load_limits(self.config) # Init screen - self.screen = GlancesCurses(args=self.args) + self.screen = GlancesCursesClient(args=self.args) # Return result return ret diff --git a/glances/core/glances_client_browser.py b/glances/core/glances_client_browser.py index afe10bdfc825d1c6a63688f872764660291533ec..f7d92918484c92bb9856d168e0ee6ac17a85ac0d 100644 --- a/glances/core/glances_client_browser.py +++ b/glances/core/glances_client_browser.py @@ -23,14 +23,14 @@ import json import socket try: - from xmlrpc.client import Transport, ServerProxy, ProtocolError, Fault + from xmlrpc.client import ServerProxy, Fault except ImportError: # Python 2 - from xmlrpclib import Transport, ServerProxy, ProtocolError, Fault + from xmlrpclib import ServerProxy, Fault # Import Glances libs from glances.core.glances_globals import logger -from glances.outputs.glances_curses import GlancesCurses +from glances.outputs.glances_curses import GlancesCursesBrowser from glances.core.glances_autodiscover import GlancesAutoDiscoverServer from glances.core.glances_client import GlancesClientTransport @@ -49,7 +49,7 @@ class GlancesClientBrowser(object): self.autodiscover_server = GlancesAutoDiscoverServer() # Init screen - self.screen = GlancesCurses(args=self.args) + self.screen = GlancesCursesBrowser(args=self.args) def get_servers_list(self): """Return the current server list (dict of dict)""" @@ -66,15 +66,9 @@ class GlancesClientBrowser(object): # For each server in the list, grab elementary stats (CPU, LOAD, MEM) # logger.debug(self.get_servers_list()) try: - iteritems = self.get_servers_list().iteritems() - except AttributeError: - iteritems = self.get_servers_list().items() - # Dictionnary can change size during iteration... - try: - for k, v in iteritems: + for v in self.get_servers_list(): # !!! Perhaps, it will be better to store the ServerProxy instance in the get_servers_list - uri = 'http://%s:%s' % (self.get_servers_list() - [k]['ip'], self.get_servers_list()[k]['port']) + uri = 'http://%s:%s' % (v['ip'], v['port']) # Configure the server timeout to 3 seconds t = GlancesClientTransport() t.set_timeout(3) @@ -82,31 +76,25 @@ class GlancesClientBrowser(object): try: s = ServerProxy(uri, t) except Exception as e: - logger.warning( - _("Couldn't create socket {0}: {1}").format(uri, e)) + logger.warning("Couldn't create socket {0}: {1}".format(uri, e)) else: try: # LOAD - self.get_servers_list()[k]['load_min5'] = json.loads( - s.getLoad())['min5'] + v['load_min5'] = json.loads(s.getLoad())['min5'] # CPU% - self.get_servers_list()[k][ - 'cpu_percent'] = 100 - json.loads(s.getCpu())['idle'] + v['cpu_percent'] = 100 - json.loads(s.getCpu())['idle'] # MEM% - self.get_servers_list()[k]['mem_percent'] = json.loads( - s.getMem())['percent'] + v['mem_percent'] = json.loads(s.getMem())['percent'] # OS (human readable name) - self.get_servers_list()[k]['hr_name'] = json.loads( - s.getSystem())['hr_name'] + v['hr_name'] = json.loads(s.getSystem())['hr_name'] except (socket.error, Fault, KeyError) as e: - logger.warning( - _("Can not grab stats form {0}: {1}").format(uri, e)) + logger.warning("Can not grab stats form {0}: {1}".format(uri, e)) + # List can change size during iteration... except RuntimeError: - logger.debug( - _("Server list dictionnary change inside the loop (wait next update)")) + logger.debug("Server list dictionnary change inside the loop (wait next update)") # Update the screen - self.screen.update_browser(self.get_servers_list()) + self.screen.update(self.get_servers_list()) def end(self): """End of the client session.""" diff --git a/glances/outputs/glances_curses.py b/glances/outputs/glances_curses.py index 98d1a13b3b7dffc5b29fddace44f9633b05e1256..3e1d66be3004fed15f33f96a4d8f9800b4c27f10 100644 --- a/glances/outputs/glances_curses.py +++ b/glances/outputs/glances_curses.py @@ -43,9 +43,12 @@ else: curses = WCurseLight() -class GlancesCurses(object): +class _GlancesCurses(object): - """This class manages the curses display (and key pressed).""" + """ + This class manages the curses display (and key pressed). + Note: It is a private class, use GlancesCursesClient or GlancesCursesBrowser + """ def __init__(self, args=None): # Init args @@ -146,7 +149,7 @@ class GlancesCurses(object): self.filter_color = A_BOLD # Define the colors list (hash table) for stats - self.__colors_list = { + self.colors_list = { 'DEFAULT': self.no_color, 'UNDERLINE': curses.A_UNDERLINE, 'BOLD': A_BOLD, @@ -196,11 +199,8 @@ class GlancesCurses(object): logger.error( 'Stats history disabled because MatPlotLib is not installed') - # Init the cursor position for the client browser - self.cursor_init() - def set_cursor(self, value): - """Configure the cursor + """Configure the curse cursor apparence 0: invisible 1: visible 2: very visible @@ -211,7 +211,7 @@ class GlancesCurses(object): except Exception: pass - def __get_key(self, window): + def get_key(self, window): # Catch ESC key AND numlock key (issue #163) keycode = [0, 0] keycode[0] = window.getch() @@ -228,7 +228,7 @@ class GlancesCurses(object): def __catch_key(self): # Catch the pressed key - self.pressedkey = self.__get_key(self.term_window) + self.pressedkey = self.get_key(self.term_window) # Actions... if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'): @@ -330,48 +330,6 @@ class GlancesCurses(object): # Return the key code return self.pressedkey - def cursor_init(self): - """Init the cursor position to the top of the list""" - self.cursor_position = 0 - - def cursor_get(self): - """Return the cursor position""" - return self.cursor_position - - def cursor_up(self): - """Set the cursor to position N-1 in the list""" - if self.cursor_position > 0: - self.cursor_position -= 1 - - def cursor_down(self, servers_list): - """Set the cursor to position N-1 in the list""" - if self.cursor_position < len(servers_list) - 1: - self.cursor_position += 1 - - def __catch_key_browser(self, servers_list): - # Catch the browser pressed key - self.pressedkey = self.__get_key(self.term_window) - - # Actions... - if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'): - # 'ESC'|'q' > Quit - self.end() - logger.info("Stop Glances client browser") - sys.exit(0) - elif self.pressedkey == 10: - # 'ENTER' > Run Glances on the selected server - logger.debug("Line %s selected in the server list" % self.cursor_get()) - elif self.pressedkey == 259: - # 'UP' > Up in the server list - logger - self.cursor_up() - elif self.pressedkey == 258: - # 'DOWN' > Down in the server list - self.cursor_down(servers_list) - - # Return the key code - return self.pressedkey - def end(self): """Shutdown the curses window.""" if hasattr(curses, 'echo'): @@ -410,109 +368,6 @@ class GlancesCurses(object): """New column in the curses interface""" self.column = self.next_column - def display_browser(self, servers_list): - """Display the servers list - Return: - True if the stats have been displayed - False if the stats have not been displayed (no server available) - """ - # Init the internal line/column for Glances Curses - self.init_line_column() - - # Get the current screen size - screen_x = self.screen.getmaxyx()[1] - screen_y = self.screen.getmaxyx()[0] - - # Init position - x = 0 - y = 0 - - # Display top header - if len(servers_list) == 0: - msg = _("No Glances server detected on your network") - elif len(servers_list) == 1: - msg = _("One Glances server detected on your network") - else: - msg = _("%d Glances servers detected on your network" % - len(servers_list)) - self.term_window.addnstr(y, x, - msg, - screen_x - x, - self.__colors_list['TITLE']) - - if len(servers_list) == 0: - return False - - # Display the Glances server list - #================================ - - # Table of table - # Item description: [stats_id, column name, column size] - column_def = [ - ['name', _('Name'), 16], - ['load_min5', _('LOAD'), 6], - ['cpu_percent', _('CPU%'), 5], - ['mem_percent', _('MEM%'), 5], - ['ip', _('IP'), 15], - ['hr_name', _('OS'), 16], - ] - y = 2 - - # Display table header - cpt = 0 - xc = x + 2 - for c in column_def: - self.term_window.addnstr(y, xc, - c[1], - screen_x - x, - self.__colors_list['BOLD']) - xc += c[2] + 2 - cpt += 1 - y += 1 - - # Display table - line = 0 - try: - iteritems = servers_list.iteritems() - except AttributeError: - iteritems = servers_list.items() - for k, v in iteritems: - # Get server stats - try: - server_stat = {} - for c in column_def: - server_stat[c[0]] = servers_list[k][c[0]] - except KeyError as e: - logger.debug(_("Can not grab stats %s from server (KeyError: %s)") % (c[0], e)) - continue - - # Display line for server stats - cpt = 0 - xc = x - - # Is the line selected ? - if line == self.cursor_get(): - # Display cursor - self.term_window.addnstr(y, xc, - ">", - screen_x - x, - self.__colors_list['BOLD']) - - xc += 2 - for c in column_def: - # Display server stats - self.term_window.addnstr(y, xc, - "%s" % server_stat[c[0]], - screen_x - x, - self.__colors_list['DEFAULT']) - xc += c[2] + 2 - cpt += 1 - # Next line, next server... - y += 1 - line += 1 - - return True - def display(self, stats, cs_status="None"): """Display stats on the screen. @@ -767,7 +622,7 @@ class GlancesCurses(object): if is_input and not is_windows: # Create a subwindow for the text field subpop = popup.derwin(1, input_size, 2, 2 + len(m)) - subpop.attron(self.__colors_list['FILTER']) + subpop.attron(self.colors_list['FILTER']) # Init the field with the current value if input_value is not None: subpop.addnstr(0, 0, input_value, len(input_value)) @@ -856,7 +711,7 @@ class GlancesCurses(object): m['msg'], # Do not disply outside the screen screen_x - x, - self.__colors_list[m['decoration']]) + self.colors_list[m['decoration']]) except: pass else: @@ -883,14 +738,6 @@ class GlancesCurses(object): self.erase() self.display(stats, cs_status=cs_status) - def flush_browser(self, servers_list): - """Update the servers' list screen. - - servers_list: Dict of dict with servers stats - """ - self.erase() - self.display_browser(servers_list) - def update(self, stats, cs_status="None"): """Update the screen. @@ -915,26 +762,6 @@ class GlancesCurses(object): # Wait 100ms... curses.napms(100) - def update_browser(self, servers_list): - """Update the servers' list screen. - - Wait for __refresh_time sec / catch key every 100 ms. - - servers_list: Dict of dict with servers stats - """ - # Flush display - self.flush_browser(servers_list) - - # Wait - countdown = Timer(self.__refresh_time) - while not countdown.finished(): - # Getkey - if self.__catch_key_browser(servers_list) > -1: - # Redraw display - self.flush_browser(servers_list) - # Wait 100ms... - curses.napms(100) - def get_stats_display_width(self, curse_msg, without_option=False): """Return the width of the formatted curses message. @@ -966,6 +793,217 @@ class GlancesCurses(object): else: return c + 1 + +class GlancesCursesClient(_GlancesCurses): + + """Class for the Glances' curse client""" + + pass + + +class GlancesCursesBrowser(_GlancesCurses): + + """Class for the Glances' curse client browser""" + + def __init__(self, args=None): + # Init the father class + _GlancesCurses.__init__(self, args=args) + + # Init refresh time + self.__refresh_time = args.time + + # Init the cursor position for the client browser + self.cursor_init() + + def cursor_init(self): + """Init the cursor position to the top of the list""" + return self.cursor_set(0) + + def cursor_set(self, pos): + """Set the cursor position andd return it""" + self.cursor_position = pos + return self.cursor_position + + def cursor_get(self): + """Return the cursor position""" + return self.cursor_position + + def cursor_up(self): + """Set the cursor to position N-1 in the list""" + if self.cursor_position > 0: + self.cursor_position -= 1 + return self.cursor_position + + def cursor_down(self, servers_list): + """Set the cursor to position N-1 in the list""" + if self.cursor_position < len(servers_list) - 1: + self.cursor_position += 1 + return self.cursor_position + + def __catch_key(self, servers_list): + # Catch the browser pressed key + self.pressedkey = self.get_key(self.term_window) + + # Actions... + if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'): + # 'ESC'|'q' > Quit + self.end() + logger.info("Stop Glances client browser") + sys.exit(0) + elif self.pressedkey == 10: + # 'ENTER' > Run Glances on the selected server + logger.debug("Server number %s selected" % (self.cursor_get() + 1)) + self.run_client(servers_list[self.cursor_get()]) + elif self.pressedkey == 259: + # 'UP' > Up in the server list + logger + self.cursor_up() + elif self.pressedkey == 258: + # 'DOWN' > Down in the server list + self.cursor_down(servers_list) + + # Return the key code + return self.pressedkey + + def run_client(self, server): + """Run the Glances client to the given server""" + + logger.debug("Run Glances client on %s" % server) + + def update(self, servers_list): + """Update the servers' list screen. + + Wait for __refresh_time sec / catch key every 100 ms. + + servers_list: Dict of dict with servers stats + """ + # Flush display + self.flush(servers_list) + + # Wait + countdown = Timer(self.__refresh_time) + while not countdown.finished(): + # Getkey + if self.__catch_key(servers_list) > -1: + # Redraw display + self.flush(servers_list) + # Wait 100ms... + curses.napms(100) + + def flush(self, servers_list): + """Update the servers' list screen. + servers_list: List of dict with servers stats + """ + self.erase() + self.display(servers_list) + + def display(self, servers_list): + """Display the servers list + Return: + True if the stats have been displayed + False if the stats have not been displayed (no server available) + """ + # Init the internal line/column for Glances Curses + self.init_line_column() + + # Get the current screen size + screen_x = self.screen.getmaxyx()[1] + screen_y = self.screen.getmaxyx()[0] + + # Init position + x = 0 + y = 0 + + # Display top header + if len(servers_list) == 0: + msg = _("No Glances server detected on your network") + elif len(servers_list) == 1: + msg = _("One Glances server detected on your network") + else: + msg = _("%d Glances servers detected on your network" % + len(servers_list)) + self.term_window.addnstr(y, x, + msg, + screen_x - x, + self.colors_list['TITLE']) + + if len(servers_list) == 0: + return False + + # Display the Glances server list + #================================ + + # Table of table + # Item description: [stats_id, column name, column size] + column_def = [ + ['name', _('Name'), 16], + ['load_min5', _('LOAD'), 6], + ['cpu_percent', _('CPU%'), 5], + ['mem_percent', _('MEM%'), 5], + ['ip', _('IP'), 15], + ['hr_name', _('OS'), 16], + ] + y = 2 + + # Display table header + cpt = 0 + xc = x + 2 + for c in column_def: + self.term_window.addnstr(y, xc, + c[1], + screen_x - x, + self.colors_list['BOLD']) + xc += c[2] + self.space_between_column + cpt += 1 + y += 1 + + # If a servers has been deleted from the list... + # ... and if the cursor is in the latest position + if self.cursor_get() > len(servers_list) - 1: + # Set the cursor position to the latest item + self.cursor_set(len(servers_list) - 1) + + # Display table + line = 0 + for v in servers_list: + # Get server stats + try: + server_stat = {} + for c in column_def: + server_stat[c[0]] = v[c[0]] + except KeyError as e: + logger.debug( + "Can not grab stats %s from server (KeyError: %s)".format(c[0], e)) + continue + + # Display line for server stats + cpt = 0 + xc = x + + # Is the line selected ? + if line == self.cursor_get(): + # Display cursor + self.term_window.addnstr(y, xc, + ">", + screen_x - x, + self.colors_list['BOLD']) + + xc += 2 + for c in column_def: + # Display server stats + self.term_window.addnstr(y, xc, + "%s" % server_stat[c[0]], + screen_x - x, + self.colors_list['DEFAULT']) + xc += c[2] + self.space_between_column + cpt += 1 + # Next line, next server... + y += 1 + line += 1 + + return True + + if not is_windows: class glances_textbox(Textbox):