Feature request: make the central client UI configurable (example: GPU status) #1289

This commit is contained in:
nicolargo 2024-10-06 11:20:12 +02:00
parent 6e485a2bad
commit 56d7801afb
6 changed files with 98 additions and 59 deletions

View File

@ -518,10 +518,14 @@ disable=False
;min_interval=6
##############################################################################
# Client/server
# Browser mode - Static servers definition
##############################################################################
[serverlist]
# 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
# Define the static servers list
#server_1_name=localhost
#server_1_alias=My local PC

View File

@ -518,10 +518,14 @@ disable=False
;min_interval=6
##############################################################################
# Client/server
# Browser mode - Static servers definition
##############################################################################
[serverlist]
# 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
# Define the static servers list
#server_1_name=localhost
#server_1_alias=My local PC

View File

@ -102,6 +102,10 @@ Example:
.. code-block:: ini
[serverlist]
# 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
# Define the static servers list
server_1_name=xps
server_1_alias=xps

View File

@ -84,26 +84,30 @@ class GlancesClientBrowser:
t = GlancesClientTransport()
t.set_timeout(3)
# Get common stats
# Get common stats from Glances server
try:
s = ServerProxy(uri, transport=t)
except Exception as e:
logger.warning(f"Client browser couldn't create socket ({e})")
else:
# Mandatory stats
return server
# Get the stats
for column in self.static_server.get_columns():
server_key = column['plugin'] + '_' + column['field']
try:
# CPU%
# logger.info(f"CPU stats {s.getPlugin('cpu')}")
# logger.info(f"CPU views {s.getPluginView('cpu')}")
server['cpu_percent'] = json_loads(s.getPlugin('cpu'))['total']
server['cpu_percent_decoration'] = json_loads(s.getPluginView('cpu'))['total']['decoration']
# MEM%
server['mem_percent'] = json_loads(s.getPlugin('mem'))['percent']
server['mem_percent_decoration'] = json_loads(s.getPluginView('mem'))['percent']['decoration']
# OS (Human Readable name)
server['hr_name'] = json_loads(s.getPlugin('system'))['hr_name']
server['hr_name_decoration'] = 'DEFAULT'
except (OSError, Fault, KeyError) as e:
# Value
v_json = json_loads(s.getPlugin(column['plugin']))
if 'key' in column:
v_json = [i for i in v_json if i[i['key']].lower() == column['key'].lower()][0]
server[server_key] = v_json[column['field']]
# Decoration
d_json = json_loads(s.getPluginView(column['plugin']))
if 'key' in column:
d_json = d_json.get(column['key'])
server[server_key + '_decoration'] = d_json[column['field']]['decoration']
except (KeyError, IndexError, Fault) as e:
logger.debug(f"Error while grabbing stats form server ({e})")
except OSError as e:
logger.debug(f"Error while grabbing stats form server ({e})")
server['status'] = 'OFFLINE'
except ProtocolError as e:
@ -119,14 +123,6 @@ class GlancesClientBrowser:
# Status
server['status'] = 'ONLINE'
# Optional stats (load is not available on Windows OS)
try:
# LOAD
server['load_min5'] = round(json_loads(s.getPlugin('load'))['min5'], 1)
server['load_min5_decoration'] = json_loads(s.getPluginView('load'))['min5']['decoration']
except Exception as e:
logger.warning(f"Error while grabbing stats form server ({e})")
return server
def __display_server(self, server):

View File

@ -288,32 +288,36 @@ class GlancesCursesBrowser(_GlancesCurses):
return x, y
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}
# Add dynamic columns
for server_stat in current_page:
for k, v in server_stat.items():
if k.endswith('_decoration'):
column_def[k.split('_decoration')[0]] = 6
return column_def
def __display_server_list(self, stats, x, y, screen_x, screen_y):
if len(stats) == 0:
# No server to display
return False
stats_max = screen_y - 3
# 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],
['status', 'STATUS', 9],
['ip', 'IP', 15],
['hr_name', 'OS', 16],
]
y = 2
stats_list = self._get_stats(stats)
start_line = self._page_max_lines * self._current_page
end_line = start_line + self.get_pagelines(stats_list)
current_page = stats_list[start_line:end_line]
column_def = self.__build_column_def(current_page)
# Display table header
stats_max = screen_y - 3
y = 2
xc = x + 2
for cpt, c in enumerate(column_def):
if xc < screen_x and y < screen_y and c[1] is not None:
self.term_window.addnstr(y, xc, c[1], screen_x - x, self.colors_list['BOLD'])
xc += c[2] + self.space_between_column
for k, v in column_def.items():
if xc < screen_x and y < screen_y and v is not None:
self.term_window.addnstr(y, xc, k.split('_')[0].upper(), screen_x - x, self.colors_list['BOLD'])
xc += v + self.space_between_column
y += 1
# If a servers has been deleted from the list...
@ -322,11 +326,6 @@ class GlancesCursesBrowser(_GlancesCurses):
# Set the cursor position to the latest item
self.cursor = len(stats) - 1
stats_list = self._get_stats(stats)
start_line = self._page_max_lines * self._current_page
end_line = start_line + self.get_pagelines(stats_list)
current_page = stats_list[start_line:end_line]
# Display table
line = 0
for server_stat in current_page:
@ -345,22 +344,24 @@ class GlancesCursesBrowser(_GlancesCurses):
# Display the line
xc += 2
for c in column_def:
for k, v in column_def.items():
if xc < screen_x and y < screen_y:
# Display server stats
value = server_stat.get(c[0], '?')
if c[0] == 'name' and 'alias' in server_stat and server_stat['alias'] is not None:
value = server_stat.get(k, '?')
if isinstance(value, float):
value = round(value, 1)
if k == 'name' and 'alias' in server_stat and server_stat['alias'] is not None:
value = server_stat['alias']
decoration = self.colors_list.get(
server_stat[c[0] + '_decoration'].replace('_LOG', '')
if c[0] + '_decoration' in server_stat
server_stat[k + '_decoration'].replace('_LOG', '')
if k + '_decoration' in server_stat
else self.colors_list[server_stat['status']],
self.colors_list['DEFAULT'],
)
if c[0] == 'status':
if k == 'status':
decoration = self.colors_list[server_stat['status']]
self.term_window.addnstr(y, xc, format(value), c[2], decoration)
xc += c[2] + self.space_between_column
self.term_window.addnstr(y, xc, format(value), v, decoration)
xc += v + self.space_between_column
cpt += 1
# Next line, next server...
y += 1

View File

@ -12,6 +12,8 @@ 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."""
@ -21,10 +23,12 @@ 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 ...} ... ]
# Load the configuration file
self._server_list = self.load(config)
# 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(self, config):
def load_server_list(self, config):
"""Load the server list from the configuration file."""
server_list = []
@ -70,6 +74,28 @@ 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
@ -77,3 +103,7 @@ 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