@@ -190,13 +190,6 @@ class PadNtpSource:
190190 poll = 14
191191
192192
193- class PadService :
194- name = 16
195- status = 8
196- pid = 8
197- description = 40
198-
199-
200193class PadWifiScan :
201194 ssid = 40
202195 encryption = 30
@@ -218,6 +211,34 @@ class PadDiskUsage:
218211 avail = 12
219212 percent = 6
220213
214+
215+ def format_memory_bytes (bytes_val ):
216+ """Convert bytes to human-readable format"""
217+ if bytes_val == 0 :
218+ return " "
219+ elif bytes_val < 1024 :
220+ return f"{ bytes_val } B"
221+ elif bytes_val < 1024 * 1024 :
222+ return f"{ bytes_val // 1024 } K"
223+ elif bytes_val < 1024 * 1024 * 1024 :
224+ return f"{ bytes_val // (1024 * 1024 ):.1f} M"
225+ else :
226+ return f"{ bytes_val // (1024 * 1024 * 1024 ):.1f} G"
227+
228+
229+ def format_uptime_seconds (seconds ):
230+ """Convert seconds to compact time format"""
231+ if seconds == 0 :
232+ return " "
233+ elif seconds < 60 :
234+ return f"{ seconds } s"
235+ elif seconds < 3600 :
236+ return f"{ seconds // 60 } m"
237+ elif seconds < 86400 :
238+ return f"{ seconds // 3600 } h"
239+ else :
240+ return f"{ seconds // 86400 } d"
241+
221242 @classmethod
222243 def table_width (cls ):
223244 """Total width of disk usage table"""
@@ -260,6 +281,89 @@ def table_width(cls):
260281 return cls .zone_locked + cls .zone_name + cls .zone_type + cls .zone_data \
261282 + cls .zone_services
262283
284+ class Column :
285+ """Column definition for SimpleTable"""
286+ def __init__ (self , name , align = 'left' , formatter = None ):
287+ self .name = name
288+ self .align = align
289+ self .formatter = formatter
290+
291+ class SimpleTable :
292+ """Simple table formatter that handles ANSI colors correctly and calculates dynamic column widths"""
293+
294+ def __init__ (self , columns ):
295+ self .columns = columns
296+ self .rows = []
297+
298+ @staticmethod
299+ def visible_width (text ):
300+ """Return visible character count, excluding ANSI escape sequences"""
301+ ansi_pattern = r'\x1b\[[0-9;]*m'
302+ clean_text = re .sub (ansi_pattern , '' , str (text ))
303+ return len (clean_text )
304+
305+ def row (self , * values ):
306+ """Store row data for later formatting"""
307+ if len (values ) != len (self .columns ):
308+ raise ValueError (f"Expected { len (self .columns )} values, got { len (values )} " )
309+ self .rows .append (values )
310+
311+ def print (self , styled = True ):
312+ """Calculate widths and print complete table"""
313+ column_widths = self ._calculate_column_widths ()
314+ print (self ._format_header (column_widths , styled ))
315+ for row_data in self .rows :
316+ print (self ._format_row (row_data , column_widths ))
317+
318+ def _calculate_column_widths (self ):
319+ """Calculate maximum width needed for each column"""
320+ widths = [self .visible_width (col .name ) for col in self .columns ]
321+
322+ for row_data in self .rows :
323+ for i , (value , column ) in enumerate (zip (row_data , self .columns )):
324+ formatted_value = column .formatter (value ) if column .formatter else value
325+ value_width = self .visible_width (str (formatted_value ))
326+ widths [i ] = max (widths [i ], value_width )
327+
328+ return widths
329+
330+ def _format_header (self , column_widths , styled = True ):
331+ """Generate formatted header row"""
332+ header_parts = []
333+ for i , column in enumerate (self .columns ):
334+ width = column_widths [i ]
335+ if column .align == 'right' :
336+ header_parts .append (f"{ column .name :>{width }} " )
337+ else :
338+ header_parts .append (f"{ column .name :{width }} " )
339+
340+ header_str = '' .join (header_parts ).rstrip ()
341+ return Decore .invert (header_str ) if styled else header_str
342+
343+ def _format_row (self , row_data , column_widths ):
344+ """Format a single data row"""
345+ row_parts = []
346+ for i , (value , column ) in enumerate (zip (row_data , self .columns )):
347+ formatted_value = self ._format_column_value (value , column , column_widths [i ])
348+ row_parts .append (formatted_value )
349+
350+ return '' .join (row_parts ).rstrip ()
351+
352+ def _format_column_value (self , value , column , width ):
353+ """Format a single column value with proper alignment"""
354+ if column .formatter :
355+ value = column .formatter (value )
356+
357+ value_str = str (value )
358+ visible_len = self .visible_width (value_str )
359+
360+ if column .align == 'right' :
361+ padding = width - visible_len
362+ return ' ' * max (0 , padding ) + value_str + ' '
363+ else :
364+ padding = width - visible_len
365+ return value_str + ' ' * max (0 , padding ) + ' '
366+
263367
264368class Decore ():
265369 @staticmethod
@@ -1693,17 +1797,25 @@ def show_services(json):
16931797 services_data = get_json_data ({}, json , 'ietf-system:system-state' , 'infix-system:services' )
16941798 services = services_data .get ("service" , [])
16951799
1696- hdr = (f"{ 'NAME' :<{PadService .name }} "
1697- f"{ 'STATUS' :<{PadService .status }} "
1698- f"{ 'PID' :>{PadService .pid - 1 }} "
1699- f" { 'DESCRIPTION' } " )
1700- print (Decore .invert (hdr ))
1800+ # This is the first usage of simple table. I assume this will be
1801+ # copied so I left a lot of comments. If you copy it feel free
1802+ # to be less verbose..
1803+ service_table = SimpleTable ([
1804+ Column ('NAME' ),
1805+ Column ('STATUS' ),
1806+ Column ('PID' , 'right' ),
1807+ Column ('MEM' , 'right' ),
1808+ Column ('UP' , 'right' ),
1809+ Column ('RST' , 'right' ),
1810+ Column ('DESCRIPTION' )
1811+ ])
17011812
17021813 for svc in services :
17031814 name = svc .get ('name' , '' )
17041815 status = svc .get ('status' , '' )
17051816 pid = svc .get ('pid' , 0 )
17061817 description = svc .get ('description' , '' )
1818+ stats = svc .get ('statistics' , {})
17071819
17081820 if status in ('running' , 'active' , 'done' ):
17091821 status_str = Decore .green (status )
@@ -1712,13 +1824,19 @@ def show_services(json):
17121824 else :
17131825 status_str = Decore .yellow (status )
17141826
1715- pid_str = str (pid ) if pid > 0 else '- '
1827+ pid_str = str (pid ) if pid > 0 else ' '
17161828
1717- row = f"{ name :<{PadService .name }} "
1718- row += f"{ status_str :<{PadService .status + 9 }} "
1719- row += f"{ pid_str :>{PadService .pid }} "
1720- row += f" { description } "
1721- print (row )
1829+ memory_bytes = int (stats .get ('memory-usage' , 0 ))
1830+ uptime_secs = int (stats .get ('uptime' , 0 ))
1831+ restart_count = stats .get ('restart-count' , 0 )
1832+
1833+ memory_str = format_memory_bytes (memory_bytes )
1834+ uptime_str = format_uptime_seconds (uptime_secs )
1835+
1836+ service_table .row (name , status_str , pid_str , memory_str ,
1837+ uptime_str , restart_count , description )
1838+
1839+ service_table .print ()
17221840
17231841
17241842def show_hardware (json ):
0 commit comments