Correct an issue on the Webbrowser, the column list is now shared between static and dynamic Glances servers list

This commit is contained in:
nicolargo 2024-11-08 18:21:40 +01:00
parent a9e3820947
commit efd79b0bd8
5 changed files with 104 additions and 41 deletions

View File

@ -540,7 +540,7 @@ disable=False
# Define columns (comma separated list of <plugin>:<field>:(<key>)) to grab/display
# Default is: system:hr_name,load:min5,cpu:total,mem:percent
# 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
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)

View File

@ -195,7 +195,7 @@ class GlancesRestfulApi:
def __update_servers_list(self):
# Never update more than 1 time per cached_time
if self.timer.finished():
if self.timer.finished() and self.servers_list is not None:
self.servers_list.update_servers_stats()
self.timer = Timer(self.args.cached_time)
@ -246,6 +246,8 @@ class GlancesRestfulApi:
f'{plugin_path}/{{item}}/history/{{nb}}': self._api_item_history,
f'{plugin_path}/{{item}}/description': self._api_item_description,
f'{plugin_path}/{{item}}/unit': self._api_item_unit,
f'{plugin_path}/{{item}}/{{key}}': self._api_key,
f'{plugin_path}/{{item}}/{{key}}/views': self._api_key_views,
f'{plugin_path}/{{item}}/{{value:path}}': self._api_value,
}
@ -420,7 +422,7 @@ class GlancesRestfulApi:
# Update the servers list (and the stats for all the servers)
self.__update_servers_list()
return GlancesJSONResponse(self.servers_list.get_servers_list())
return GlancesJSONResponse(self.servers_list.get_servers_list() if self.servers_list else [])
def _api_all(self):
"""Glances API RESTful implementation.
@ -623,6 +625,30 @@ class GlancesRestfulApi:
return GlancesJSONResponse(ret)
def _api_key(self, plugin: str, item: str, key: str):
"""Glances API RESTful implementation.
Return the JSON representation of plugin/item/key
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
self._check_if_plugin_available(plugin)
# Update the stat
self.__update_stats()
try:
# Get the RAW value of the stat views
ret = self.stats.get_plugin(plugin).get_raw_stats_key(item, key)
except Exception as e:
raise HTTPException(
status.HTTP_404_NOT_FOUND,
f"Cannot get item {item} in plugin {plugin} ({str(e)})",
)
return GlancesJSONResponse(ret)
def _api_item_views(self, plugin: str, item: str):
"""Glances API RESTful implementation.
@ -647,6 +673,30 @@ class GlancesRestfulApi:
return GlancesJSONResponse(ret)
def _api_key_views(self, plugin: str, item: str, key: str):
"""Glances API RESTful implementation.
Return the JSON view representation of plugin/item/key
HTTP/200 if OK
HTTP/400 if plugin is not found
HTTP/404 if others error
"""
self._check_if_plugin_available(plugin)
# Update the stat
self.__update_stats()
try:
# Get the RAW value of the stat views
ret = self.stats.get_plugin(plugin).get_views().get(key).get(item)
except Exception as e:
raise HTTPException(
status.HTTP_404_NOT_FOUND,
f"Cannot get item {item} in plugin view {plugin} ({str(e)})",
)
return GlancesJSONResponse(ret)
def _api_item_history(self, plugin: str, item: str, nb: int = 0):
"""Glances API RESTful implementation.

View File

@ -393,6 +393,17 @@ class GlancesPluginModel:
"""
return dictlist(self.get_raw(), item)
def get_raw_stats_key(self, item, key):
"""Return the stats object for a specific item in RAW format.
Stats should be a list of dict (processlist, network...)
"""
try:
return {item: [i for i in self.get_raw() if 'key' in i and i[i['key']] == key][0].get(item)}
except (KeyError, ValueError) as e:
logger.error(f"Cannot get item ({item}) for key ({key}) ({e})")
return None
def get_stats_item(self, item):
"""Return the stats object for a specific item in JSON format.

View File

@ -32,8 +32,12 @@ else:
# Correct issue #1025 by monkey path the xmlrpc lib
xmlrpc.monkey_patch()
DEFAULT_COLUMNS = "system:hr_name,load:min5,cpu:total,mem:percent"
class GlancesServersList:
_section = "serverlist"
def __init__(self, config=None, args=None):
# Store the arg/config
self.args = args
@ -59,6 +63,30 @@ class GlancesServersList:
self.static_server = GlancesStaticServer(config=self.config)
# Init the password list (if defined)
self.password = GlancesPassword(config=self.config)
# Load columns to grab/display
self._columns = self.load_columns()
def load_columns(self):
"""Load columns definition from the configuration file.
Read: 'system:hr_name,load:min5,cpu:total,mem:percent,sensors:value:Ambient'
Return: [{'plugin': 'system', 'field': 'hr_name'},
{'plugin': 'load', 'field': 'min5'},
{'plugin': 'cpu', 'field': 'total'},
{'plugin': 'mem', 'field': 'percent'},
{'plugin': 'sensors', 'field': 'value', 'key': 'Ambient'}]
"""
if self.config is None:
logger.debug("No configuration file available. Cannot load columns definition.")
elif not self.config.has_section(self._section):
logger.warning(f"No [{self._section}] section in the configuration file. Cannot load columns definition.")
columns_def = (
self.config.get_value(self._section, 'columns')
if self.config.get_value(self._section, 'columns')
else DEFAULT_COLUMNS
)
return [dict(zip(['plugin', 'field', 'key'], i.split(':'))) for i in columns_def.split(',')]
def get_servers_list(self):
"""Return the current server list (list of dict).
@ -74,6 +102,10 @@ class GlancesServersList:
return ret
def get_columns(self):
"""Return the columns definitions"""
return self._columns
def update_servers_stats(self):
"""For each server in the servers list, update the stats"""
for v in self.get_servers_list():
@ -84,9 +116,6 @@ class GlancesServersList:
self.threads_list[key] = thread
thread.start()
def get_columns(self):
return self.static_server.get_columns()
def get_uri(self, server):
"""Return the URI for the given server dict."""
# Select the connection mode (with or without password)
@ -112,7 +141,7 @@ class GlancesServersList:
def __update_stats(self, server):
"""Update stats for the given server"""
server['uri'] = self.get_uri(server)
server['columns'] = [self.__get_key(column) for column in self.static_server.get_columns()]
server['columns'] = [self.__get_key(column) for column in self.get_columns()]
if server['protocol'].lower() == 'rpc':
self.__update_stats_rpc(server['uri'], server)
elif server['protocol'].lower() == 'rest' and not import_requests_error_tag:
@ -133,7 +162,7 @@ class GlancesServersList:
return server
# Get the stats
for column in self.static_server.get_columns():
for column in self.get_columns():
server_key = self.__get_key(column)
try:
# Value
@ -176,11 +205,14 @@ class GlancesServersList:
else:
server['status'] = 'ONLINE'
for column in self.static_server.get_columns():
for column in self.get_columns():
server_key = self.__get_key(column)
request_uri = f'{uri}/{column['plugin']}/{column['field']}'
if 'key' in column:
request_uri += f'/{column['key']}'
# Value
try:
r = requests.get(f'{uri}/{column['plugin']}/{column['field']}', timeout=3)
r = requests.get(request_uri, timeout=3)
except requests.exceptions.RequestException as e:
logger.debug(f"Error while grabbing stats form server ({e})")
return server
@ -188,7 +220,7 @@ class GlancesServersList:
server[server_key] = r.json()[column['field']]
# Decoration
try:
r = requests.get(f'{uri}/{column['plugin']}/{column['field']}/views', timeout=3)
r = requests.get(request_uri + '/views', timeout=3)
except requests.exceptions.RequestException as e:
logger.debug(f"Error while grabbing stats view form server ({e})")
return server

View File

@ -12,8 +12,6 @@ from socket import gaierror, gethostbyname
from glances.logger import logger
DEFAULT_COLUMNS = "system:hr_name,load:min5,cpu:total,mem:percent"
class GlancesStaticServer:
"""Manage the static servers list for the client browser."""
@ -25,8 +23,6 @@ class GlancesStaticServer:
# [ {'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
self._columns = self.load_columns(config)
def load_server_list(self, config):
"""Load the server list from the configuration file."""
@ -87,28 +83,6 @@ class GlancesStaticServer:
return server_list
def load_columns(self, config):
"""Load columns definition from the configuration file.
Read: 'system:hr_name,load:min5,cpu:total,mem:percent,sensors:value:Ambient'
Return: [{'plugin': 'system', 'field': 'hr_name'},
{'plugin': 'load', 'field': 'min5'},
{'plugin': 'cpu', 'field': 'total'},
{'plugin': 'mem', 'field': 'percent'},
{'plugin': 'sensors', 'field': 'value', 'key': 'Ambient'}]
"""
if config is None:
logger.debug("No configuration file available. Cannot load columns definition.")
elif not config.has_section(self._section):
logger.warning(f"No [{self._section}] section in the configuration file. Cannot load columns definition.")
columns_def = (
config.get_value(self._section, 'columns')
if config.get_value(self._section, 'columns')
else DEFAULT_COLUMNS
)
return [dict(zip(['plugin', 'field', 'key'], i.split(':'))) for i in columns_def.split(',')]
def get_servers_list(self):
"""Return the current server list (list of dict)."""
return self._server_list
@ -116,7 +90,3 @@ class GlancesStaticServer:
def set_server(self, server_pos, key, value):
"""Set the key to the value for the server_pos (position in the list)."""
self._server_list[server_pos][key] = value
def get_columns(self):
"""Return the columns definitions"""
return self._columns