diff --git a/conf/glances.conf b/conf/glances.conf index 153ff3d4..aa4752a3 100644 --- a/conf/glances.conf +++ b/conf/glances.conf @@ -542,14 +542,23 @@ disable=False # You can also add stats with key, like sensors:value:Ambient (key is case sensitive) #columns=system:hr_name,load:min5,cpu:total,mem:percent,memswap:percent,sensors:value:Ambient,sensors:value:Composite # Define the static servers list +# _protocol can be: rpc (default if not defined) or rest +# List is limited to 256 servers max (1 to 256) +server_1_name=localhost +server_1_alias=Local WebUI +server_1_port=61208 +server_1_protocol=rest #server_1_name=localhost #server_1_alias=My local PC #server_1_port=61209 +#server_1_protocol=rpc #server_2_name=localhost #server_2_port=61235 +#server_2_protocol=rpc #server_3_name=192.168.0.17 #server_3_alias=Another PC on my network #server_3_port=61209 +#server_1_protocol=rpc #server_4_name=pasbon #server_4_port=61237 diff --git a/glances/autodiscover.py b/glances/autodiscover.py index 8be3c2a8..3a5bd24e 100644 --- a/glances/autodiscover.py +++ b/glances/autodiscover.py @@ -42,7 +42,7 @@ class AutoDiscovered: def __init__(self): # server_dict is a list of dict (JSON compliant) - # [ {'key': 'zeroconf name', ip': '172.1.2.3', 'port': 61209, 'cpu': 3, 'mem': 34 ...} ... ] + # [ {'key': 'zeroconf name', ip': '172.1.2.3', 'port': 61209, 'protocol': 'rpc', 'cpu': 3, 'mem': 34 ...} ... ] self._server_list = [] def get_servers_list(self): @@ -60,6 +60,7 @@ class AutoDiscovered: 'name': name.split(':')[0], # Short name 'ip': ip, # IP address seen by the client 'port': port, # TCP port + 'protocol': 'rpc', # RPC protocol 'username': 'glances', # Default username 'password': '', # Default password 'status': 'UNKNOWN', # Server status: 'UNKNOWN', 'OFFLINE', 'ONLINE', 'PROTECTED' diff --git a/glances/client_browser.py b/glances/client_browser.py index 06d4fbf3..7bcdbff6 100644 --- a/glances/client_browser.py +++ b/glances/client_browser.py @@ -9,9 +9,11 @@ """Manage the Glances client browser (list of Glances server).""" import threading +import webbrowser from defusedxml import xmlrpc +from glances import __apiversion__ from glances.autodiscover import GlancesAutoDiscoverServer from glances.client import GlancesClient, GlancesClientTransport from glances.globals import json_loads @@ -20,6 +22,15 @@ from glances.outputs.glances_curses_browser import GlancesCursesBrowser from glances.password_list import GlancesPasswordList as GlancesPassword from glances.static_list import GlancesStaticServer +try: + import requests +except ImportError as e: + import_requests_error_tag = True + # Display debug message if import error + logger.warning(f"Missing Python Lib ({e}), Client browser will not grab stats from Glances REST server") +else: + import_requests_error_tag = False + # Correct issue #1025 by monkey path the xmlrpc lib xmlrpc.monkey_patch() @@ -77,14 +88,18 @@ class GlancesClientBrowser: clear_password = self.password.get_password(server['name']) if clear_password is not None: server['password'] = self.password.get_hash(clear_password) - return 'http://{}:{}@{}:{}'.format(server['username'], server['password'], server['ip'], server['port']) - return 'http://{}:{}'.format(server['ip'], server['port']) + uri = 'http://{}:{}@{}:{}'.format(server['username'], server['password'], server['ip'], server['port']) + else: + uri = 'http://{}:{}'.format(server['ip'], server['port']) + return uri - def __update_stats(self, server): - """Update stats for the given server (picked from the server list)""" - # Get the server URI - uri = self.__get_uri(server) + def __get_key(self, column): + server_key = column.get('plugin') + '_' + column.get('field') + if 'key' in column: + server_key += '_' + column.get('key') + return server_key + def __update_stats_rpc(self, uri, server): # Try to connect to the server t = GlancesClientTransport() t.set_timeout(3) @@ -98,9 +113,7 @@ class GlancesClientBrowser: # Get the stats for column in self.static_server.get_columns(): - server_key = column.get('plugin') + '_' + column.get('field') - if 'key' in column: - server_key += '_' + column.get('key') + server_key = self.__get_key(column) try: # Value v_json = json_loads(s.getPlugin(column['plugin'])) @@ -132,11 +145,55 @@ class GlancesClientBrowser: return server + def __update_stats_rest(self, uri, server): + try: + requests.get(f'{uri}/status', timeout=3) + except requests.exceptions.RequestException as e: + logger.debug(f"Error while grabbing stats form server ({e})") + server['status'] = 'OFFLINE' + return server + else: + server['status'] = 'ONLINE' + + for column in self.static_server.get_columns(): + server_key = self.__get_key(column) + try: + r = requests.get(f'{uri}/{column['plugin']}/{column['field']}', timeout=3) + except requests.exceptions.RequestException as e: + logger.debug(f"Error while grabbing stats form server ({e})") + return server + else: + server[server_key] = r.json()[column['field']] + + return server + + def __update_stats(self, server): + """Update stats for the given server (picked from the server list)""" + # Get the server URI + uri = self.__get_uri(server) + + if server['protocol'].lower() == 'rpc': + self.__update_stats_rpc(uri, server) + elif server['protocol'].lower() == 'rest' and not import_requests_error_tag: + self.__update_stats_rest(f'{uri}/api/{__apiversion__}', server) + + return server + def __display_server(self, server): """Connect and display the given server""" # Display the Glances client for the selected server logger.debug(f"Selected server {server}") + if server['protocol'].lower() == 'rest': + # Display a popup + self.screen.display_popup( + 'Open the WebUI {}:{} in a Web Browser'.format(server['name'], server['port']), duration=1 + ) + # Try to open a Webbrowser + webbrowser.open(self.__get_uri(server), new=2, autoraise=1) + self.screen.active_server = None + return + # Connection can take time # Display a popup self.screen.display_popup('Connect to {}:{}'.format(server['name'], server['port']), duration=1) diff --git a/glances/outputs/glances_curses_browser.py b/glances/outputs/glances_curses_browser.py index 1bc2e8af..bf88175b 100644 --- a/glances/outputs/glances_curses_browser.py +++ b/glances/outputs/glances_curses_browser.py @@ -290,7 +290,7 @@ class GlancesCursesBrowser(_GlancesCurses): def __build_column_def(self, current_page): """Define the column and it size to display in the browser""" - column_def = {'name': 16, 'ip': 15, 'status': 9} + column_def = {'name': 16, 'ip': 15, 'status': 9, 'protocol': 8} # Add dynamic columns for server_stat in current_page: diff --git a/glances/static_list.py b/glances/static_list.py index 95aaf486..90f8933d 100644 --- a/glances/static_list.py +++ b/glances/static_list.py @@ -22,7 +22,7 @@ class GlancesStaticServer: def __init__(self, config=None, args=None): # server_list is a list of dict (JSON compliant) - # [ {'key': 'zeroconf name', ip': '172.1.2.3', 'port': 61209, 'cpu': 3, 'mem': 34 ...} ... ] + # [ {'key': 'zeroconf name', ip': '172.1.2.3', 'port': 61209, 'protocol': 'rpc', 'cpu': 3, 'mem': 34 ...} ... ] # Load server list from the Glances configuration file self._server_list = self.load_server_list(config) # Load columns to grab/display in the browser mode @@ -39,34 +39,48 @@ class GlancesStaticServer: else: logger.info(f"Start reading the [{self._section}] section in the configuration file") for i in range(1, 256): + # Read the configuration new_server = {} postfix = f'server_{str(i)}_' - # Read the server name (mandatory) - for s in ['name', 'port', 'alias']: + for s in ['name', 'port', 'alias', 'protocol']: new_server[s] = config.get_value(self._section, f'{postfix}{s}') - if new_server['name'] is not None: - # Manage optional information - if new_server['port'] is None: - new_server['port'] = '61209' - new_server['username'] = 'glances' - # By default, try empty (aka no) password - new_server['password'] = '' - try: - new_server['ip'] = gethostbyname(new_server['name']) - except gaierror as e: - logger.error("Cannot get IP address for server {} ({})".format(new_server['name'], e)) - continue - new_server['key'] = new_server['name'] + ':' + new_server['port'] - # Default status is 'UNKNOWN' - new_server['status'] = 'UNKNOWN' + if new_server['name'] is None: + logger.error(f'Name not define for {postfix}, skip it.') + continue - # Server type is 'STATIC' - new_server['type'] = 'STATIC' + # Type in order to support both RPC and REST servers (see #1121) + if new_server['protocol'] is None: + new_server['protocol'] = 'rpc' + new_server['protocol'] = new_server['protocol'].lower() + if new_server['protocol'] not in ('rpc', 'rest'): + logger.error(f'Unknow protocol for {postfix}, skip it.') + continue - # Add the server to the list - logger.debug("Add server {} to the static list".format(new_server['name'])) - server_list.append(new_server) + # Default port + if new_server['port'] is None: + new_server['port'] = '61209' if new_server['type'] == 'rpc' else '61208' + + # By default, try empty (aka no) password + new_server['username'] = 'glances' + new_server['password'] = '' + + try: + new_server['ip'] = gethostbyname(new_server['name']) + except gaierror as e: + logger.error("Cannot get IP address for server {} ({})".format(new_server['name'], e)) + continue + new_server['key'] = new_server['name'] + ':' + new_server['port'] + + # Default status is 'UNKNOWN' + new_server['status'] = 'UNKNOWN' + + # Server type is 'STATIC' + new_server['type'] = 'STATIC' + + # Add the server to the list + logger.debug("Add server {} to the static list".format(new_server['name'])) + server_list.append(new_server) # Server list loaded logger.info(f"{len(server_list)} server(s) loaded from the configuration file")