From 6c954c10ae339815546d83776e6b7224183c7afa Mon Sep 17 00:00:00 2001 From: Jana Librova Date: Mar 25 2026 07:53:56 +0000 Subject: Improve channelinfo, hosts and hostinfo - hosts status Fixes: https://pagure.io/koji/issue/4549 --- diff --git a/tests/test_www/test_channelinfo.py b/tests/test_www/test_channelinfo.py index 9d6fc6b..1205746 100644 --- a/tests/test_www/test_channelinfo.py +++ b/tests/test_www/test_channelinfo.py @@ -40,9 +40,12 @@ class TestChannelInfo(unittest.TestCase): self.server.getChannel.return_value = {'name': 'test-channel', 'id': self.channel_id} self.server.listTasks.return_value = 5 self.server.listHosts.return_value = [ - {'id': 1, 'name': 'test-host-1', 'enabled': True, 'ready': True}, - {'id': 2, 'name': 'test-host-2', 'enabled': False, 'ready': False}, - {'id': 3, 'name': 'test-host-3', 'enabled': True, 'ready': False}] + {'id': 1, 'name': 'test-host-1', 'enabled': True, 'ready': True, + 'update_ts': 1774025255.931238}, + {'id': 2, 'name': 'test-host-2', 'enabled': False, 'ready': False, + 'update_ts': 1774025258.931238}, + {'id': 3, 'name': 'test-host-3', 'enabled': True, 'ready': False, + 'update_ts': 1774025253.931238}] webidx.channelinfo(self.environ, self.channel_id) self.server.getChannel.assert_called_once_with(int(self.channel_id)) diff --git a/www/kojiweb/index.py b/www/kojiweb/index.py index 02c2109..ddfe94b 100644 --- a/www/kojiweb/index.py +++ b/www/kojiweb/index.py @@ -1749,26 +1749,25 @@ def hosts(environ, state='enabled', start=None, order='name', ready='all', chann server = _getServer(environ) values['order'] = order + values['sleep_ts'] = (datetime.datetime.now() - datetime.timedelta(minutes=15)).timestamp() hosts = server.listHosts() values['arches'] = sorted(set(itertools.chain(*[host['arches'].split() for host in hosts]))) - if state == 'enabled': - hosts = [x for x in hosts if x['enabled']] + if state == 'ready': + hosts = [x for x in hosts if x['enabled'] and x['ready']] + elif state == 'busy': + hosts = [x for x in hosts if x['enabled'] and not x['ready'] and + x['update_ts'] > values['sleep_ts']] + elif state == 'problem': + hosts = [x for x in hosts if x['enabled'] and not x['ready'] and + x['update_ts'] < values['sleep_ts']] elif state == 'disabled': - hosts = [x for x in hosts if not x['enabled']] + hosts = [x for x in hosts if not x['enabled'] and not x['ready']] else: state = 'all' values['state'] = state - if ready == 'yes': - hosts = [x for x in hosts if x['ready']] - elif ready == 'no': - hosts = [x for x in hosts if not x['ready']] - else: - ready = 'all' - values['ready'] = ready - if arch != 'all': arch = _validate_arch(arch) if arch: @@ -1802,6 +1801,8 @@ def hosts(environ, state='enabled', start=None, order='name', ready='all', chann for host in hosts: host['last_update'] = koji.formatTimeLong(host['update_ts']) + if host['update_ts'] is None: + host['update_ts'] = 0 # Paginate after retrieving last update info so we can sort on it kojiweb.util.paginateList(values, hosts, start, 'hosts', 'host', order) @@ -1853,6 +1854,7 @@ def hostinfo(environ, hostID=None, userID=None): values['perms'] = server.getUserPerms(environ['koji.currentUser']['id']) else: values['perms'] = [] + values['sleep_ts'] = (datetime.datetime.now() - datetime.timedelta(minutes=15)).timestamp() return _genHTML(environ, 'hostinfo.html.j2') @@ -1951,8 +1953,20 @@ def channelinfo(environ, channelID): values['channel'] = channel values['hosts'] = hosts - values['enabled_hosts'] = len([h for h in hosts if h['enabled']]) - values['ready_hosts'] = len([h for h in hosts if h['ready']]) + values['ready_hosts'] = 0 + values['busy_hosts'] = 0 + values['problem_hosts'] = 0 + values['disabled_hosts'] = 0 + values['sleep_ts'] = (datetime.datetime.now() - datetime.timedelta(minutes=15)).timestamp() + for h in hosts: + if h['enabled'] and h['ready']: + values['ready_hosts'] += 1 + elif h['enabled'] and not h['ready'] and values['sleep_ts'] < h['update_ts']: + values['busy_hosts'] += 1 + elif h['enabled'] and not h['ready'] and values['sleep_ts'] < h['update_ts']: + values['problem_hosts'] += 1 + elif not h['enabled'] and not h['ready']: + values['disabled_hosts'] += 1 return _genHTML(environ, 'channelinfo.html.j2') diff --git a/www/kojiweb/templates/channelinfo.html.j2 b/www/kojiweb/templates/channelinfo.html.j2 index 565482f..077d4e7 100644 --- a/www/kojiweb/templates/channelinfo.html.j2 +++ b/www/kojiweb/templates/channelinfo.html.j2 @@ -34,21 +34,28 @@ - - + #for host in hosts - - + #if host.enabled and host.ready + + #elif host.enabled and not host.ready and sleep_ts < host.update_ts + + #elif host.enabled and not host.ready and sleep_ts > host.update_ts + + #elif not host.enabled and not host.ready + + #endif #endfor - - - - - +
HostnameEnabledReadyStatus
{{ host.name }}{{ util.imageTag('yes') if host.enabled else util.imageTag('no') }}{{ util.imageTag('yes') if host.ready else util.imageTag('no') }}{{util.imageTag('ready')}}{{util.imageTag('building', 'busy')}}{{util.imageTag('failed', 'problem')}}{{util.imageTag('canceled', 'disabled')}}
Total{{ enabled_hosts }}{{ ready_hosts }}
+ + + + +
Total:{{util.imageTag('ready')}} ready hosts:{{ ready_hosts }}
{{util.imageTag('building')}} busy hosts:{{ busy_hosts }}
{{util.imageTag('failed')}} problem hosts:{{ problem_hosts }}
{{util.imageTag('canceled')}} disabled hosts:{{ disabled_hosts }}
#else No hosts diff --git a/www/kojiweb/templates/hostinfo.html.j2 b/www/kojiweb/templates/hostinfo.html.j2 index febfa1c..f64be7e 100644 --- a/www/kojiweb/templates/hostinfo.html.j2 +++ b/www/kojiweb/templates/hostinfo.html.j2 @@ -27,24 +27,26 @@ Comment{{ host.comment or '' }} - #set enabled = host.enabled and 'yes' or 'no' - Enabled? - - {{ util.imageTag(enabled) }} - #if 'admin' in perms - #if host.enabled - (disable) - #else - (enable) - #endif - #endif + State + #if host.enabled and host.ready + {{util.imageTag('ready')}} + #elif host.enabled and not host.ready and sleep_ts < host.update_ts + {{util.imageTag('building', 'busy')}} + #elif host.enabled and not host.ready and sleep_ts > host.update_ts + {{util.imageTag('failed', 'problem')}} + #elif not host.enabled and not host.ready + {{util.imageTag('canceled', 'disabled')}} + #endif + #if 'admin' in perms + #if host.enabled + (disable host) + #else + (enable host) + #endif + #endif - #set ready = host.ready and 'yes' or 'no' - Ready?{{ util.imageTag(ready) }} - - Last Update{{ util.formatTime(lastUpdate) }} diff --git a/www/kojiweb/templates/hosts.html.j2 b/www/kojiweb/templates/hosts.html.j2 index 301dd46..c57f61b 100644 --- a/www/kojiweb/templates/hosts.html.j2 +++ b/www/kojiweb/templates/hosts.html.j2 @@ -1,7 +1,11 @@ #macro headerState(state) - #if state == 'enabled' + #if state == 'ready' Enabled hosts + #elif state == 'busy' +Busy hosts + #elif state == 'problem' +Problem hosts #elif state == 'disabled' Disabled hosts #else @@ -9,14 +13,6 @@ Hosts #endif #endmacro -#macro headerReady(ready) - #if ready == 'ready' -which are ready - #elif ready == 'notready' -which are not ready - #endif -#endmacro - #macro headerArch(arch) #if arch == 'all' on all arches @@ -33,12 +29,12 @@ in {{ channel }} channel #endif #endmacro -#set _PASSTHROUGH = ['state', 'order', 'ready', 'channel', 'arch'] +#set _PASSTHROUGH = ['state', 'order', 'channel', 'arch'] #include "header.html.j2" # from "macros.html.j2" import rowToggle -

{{ headerState(state) }} {{ headerReady(ready) }} {{ headerArch(arch) }} {{ headerChannel(channel) }}

+

{{ headerState(state) }} {{ headerArch(arch) }} {{ headerChannel(channel) }}

- - - - + @@ -129,8 +117,15 @@ in {{ channel }} channel {{ channame }} #endfor - - + #if host.enabled and host.ready + + #elif host.enabled and not host.ready and sleep_ts < host.update_ts + + #elif host.enabled and not host.ready and sleep_ts > host.update_ts + + #elif not host.enabled and not host.ready + + #endif diff --git a/www/lib/kojiweb/util.py b/www/lib/kojiweb/util.py index 2519f9d..2002524 100644 --- a/www/lib/kojiweb/util.py +++ b/www/lib/kojiweb/util.py @@ -568,11 +568,13 @@ def stateName(stateID): @safe_return -def imageTag(name): +def imageTag(name, title=None): """Return an img tag that loads an icon with the given name""" name = escapeHTML(name) + if not title: + title = name return '%s' \ - % (themePath("images/%s.png" % name), name, name) + % (themePath("images/%s.png" % name), title, name) def stateImage(stateID):
@@ -47,7 +43,9 @@ in {{ channel }} channel State: @@ -62,16 +60,7 @@ in {{ channel }} channel #endfor
- Ready: - - - + Arches: Name {{ util.sortImage('name') }} Arches {{ util.sortImage('arches') }} Channels {{ util.sortImage('channels') }}Enabled? {{ util.sortImage('enabled') }}Ready? {{ util.sortImage('ready') }}State {{ util.sortImage('state') }} Load {{ util.sortImage('task_load') }} Cap. {{ util.sortImage('capacity') }} Last Update {{ util.sortImage('last_update') }}{{ util.imageTag('yes') if host.enabled else util.imageTag('no') }}{{ util.imageTag('yes') if host.ready else util.imageTag('no') }}{{util.imageTag('ready')}}{{util.imageTag('building', 'busy')}}{{util.imageTag('failed', 'problem')}}{{util.imageTag('canceled', 'disabled')}}{{ '%.2f' % host.task_load }} {{ host.capacity }} {{ util.formatTime(host.last_update) }}