feat: initial commit - Phase 1 & 2 core features

This commit is contained in:
hiderfong
2026-04-22 17:07:33 +08:00
commit 1773bda06b
25005 changed files with 6252106 additions and 0 deletions
@@ -0,0 +1,13 @@
{% extends "base.html" %}
{% block container %}
<div class="col-12">
<p>
{% if message %}
{{ message }}
{% else %}
Error, page not found
{% end %}
</p>
</div>
{% end %}
@@ -0,0 +1,35 @@
{% import pprint %}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flower</title>
<link rel="stylesheet" href="{{ static_url('css/bootstrap.min.css') }}">
<link rel="stylesheet" href="{{ static_url('css/datatables-1.13.4.min.css') }}">
<link href="{{ static_url('css/flower.css') }}" rel="stylesheet">
</head>
<body class="m-2">
{% block navbar %}
{% module Template("navbar.html", active_tab="") %}
{% end %}
<div class="container-fluid my-2">
<div id="alert-container"></div>
<input type="hidden" value="{{ url_prefix or '' }}" id='url_prefix'>
</div>
{% block container %}
{% end %}
<script src="{{ static_url('js/bootstrap.bundle.min.js') }}"></script>
<script src="{{ static_url('js/jquery-3.6.4.min.js') }}"></script>
<script src="{{ static_url('js/datatables-1.13.4.min.js') }}"></script>
<script src="{{ static_url('js/moment-2.29.4.min.js') }}"></script>
<script src="{{ static_url('js/moment-timezone-with-data-2.29.4.min.js') }}"></script>
<script src="{{ static_url('js/flower.js') }}"></script>
{% block extra_scripts %}
{% end %}
</body>
</html>
@@ -0,0 +1,37 @@
{% extends "base.html" %}
{% block navbar %}
{% module Template("navbar.html", active_tab="broker") %}
{% end %}
{% block container %}
<div class="container-fluid">
<figure class="table-responsive mt-3">
<table id="queue-table" class="table table-bordered table-striped caption-top">
<caption>{{ broker_url }}</caption>
<thead>
<tr>
<th>Queue</th>
<th>Messages</th>
<th>Unacked</th>
<th>Ready</th>
<th>Consumers</th>
<th>Idle since</th>
</tr>
</thead>
<tbody>
{% for queue in queues %}
<tr id="{{ url_escape(queue['name']) }}">
<td>{{ queue['name'] }}</td>
<td>{{ queue.get('messages', 'N/A') }}</td>
<td>{{ queue.get('messages_unacknowledged', 'N/A') }}</td>
<td>{{ queue.get('messages_ready', 'N/A') }}</td>
<td>{{ queue.get('consumers', 'N/A') }}</td>
<td>{{ queue.get('idle_since', 'N/A') }}</td>
</tr>
{% end %}
</tbody>
</table>
</figure>
</div>
{% end %}
@@ -0,0 +1,20 @@
{% extends "base.html" %}
{% block container %}
{% if debug %}
<div class="col-12">
<p>It looks like you have found a bug! You can help to improve
Flower by opening an issue in <a href="https://github.com/mher/flower/issues">https://github.com/mher/flower/issues</a>
</p>
<pre>
{{ bugreport }}
{{ error_trace }}
</pre>
</div>
{% else %}
<div class="col-12">
Error {{ status_code }}
</div>
{% end %}
{% end %}
@@ -0,0 +1,41 @@
<nav class="navbar navbar-expand-lg navbar-light bg-green mx-2">
<a class="navbar-brand" href="{{ reverse_url('main') }}">
<img src="{{ static_url('favicon.ico') }}" width="30" height="30" class="d-inline-block align-top" alt="">
Flower
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link text-dark" href="{{ reverse_url('workers') }}">Workers</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" href="{{ reverse_url('tasks') }}">Tasks</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" href="{{ reverse_url('broker') }}">Broker</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" href="https://flower.readthedocs.io/" target="_blank" rel="noopener">Documentation</a>
</li>
</ul>
<ul class="navbar-nav flex-row flex-wrap ms-md-auto">
<li class="nav-item col-6 col-lg-auto">
<a class="nav-link py-2 px-0 px-lg-2" href="https://github.com/mher/flower" target="_blank" rel="noopener">
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" class="navbar-nav-svg" viewBox="0 0 512 499.36"
role="img">
<path fill="currentColor" fill-rule="evenodd"
d="M256 0C114.64 0 0 114.61 0 256c0 113.09 73.34 209 175.08 242.9 12.8 2.35 17.47-5.56 17.47-12.34 0-6.08-.22-22.18-.35-43.54-71.2 15.49-86.2-34.34-86.2-34.34-11.64-29.57-28.42-37.45-28.42-37.45-23.27-15.84 1.73-15.55 1.73-15.55 25.69 1.81 39.21 26.38 39.21 26.38 22.84 39.12 59.92 27.82 74.5 21.27 2.33-16.54 8.94-27.82 16.25-34.22-56.84-6.43-116.6-28.43-116.6-126.49 0-27.95 10-50.8 26.35-68.69-2.63-6.48-11.42-32.5 2.51-67.75 0 0 21.49-6.88 70.4 26.24a242.65 242.65 0 0 1 128.18 0c48.87-33.13 70.33-26.24 70.33-26.24 14 35.25 5.18 61.27 2.55 67.75 16.41 17.9 26.31 40.75 26.31 68.69 0 98.35-59.85 120-116.88 126.32 9.19 7.9 17.38 23.53 17.38 47.41 0 34.22-.31 61.83-.31 70.23 0 6.85 4.61 14.81 17.6 12.31C438.72 464.97 512 369.08 512 256.02 512 114.62 397.37 0 256 0z" />
</svg>
<small class="d-lg-none ms-2">GitHub</small>
</a>
</li>
</ul>
</div>
</nav>
@@ -0,0 +1,92 @@
{% extends "base.html" %}
{% block navbar %}
{% module Template("navbar.html", active_tab="tasks") %}
{% end %}
{% block container %}
<div id='task-page' class="container-fluid mt-3">
<div class="row-fluid">
<div class="col-lg-12">
<div class="page-header">
<p id="taskid" class="d-none">{{ task.uuid }}</p>
<h2>{{ getattr(task, 'name', None) }}
<small class="text-muted fs-5">{{ task.uuid }}</small>
{% if task.state == "STARTED" %}
<button class="btn btn-danger float-end" id="task-terminate">Terminate</button>
{% elif task.state == "RECEIVED" or task.state == "RETRY" %}
<button class="btn btn-danger float-end" id="task-revoke">Revoke</button>
{% end %}
</h2>
</div>
<div class="row-fluid">
<div class="col-lg-6">
<table class="table table-bordered table-striped">
<tbody>
<tr>
<td>Name</td>
<td>{{ getattr(task, 'name', None) }}</td>
</tr>
<tr>
<td>UUID</td>
<td>{{ task.uuid }}</td>
</tr>
<tr>
<td>State</td>
<td>
{% if task.state == "SUCCESS" %}
<span class="badge bg-success">{{ task.state }}</span>
{% elif task.state == "FAILURE" %}
<span class="badge bg-danger">{{ task.state }}</span>
{% else %}
<span class="badge bg-secondary">{{ task.state }}</span>
{% end %}
</td>
</tr>
<tr>
<td>args</td>
<td>{{ task.args }}</td>
</tr>
<tr>
<td>kwargs</td>
<td>{{ task.kwargs }}</td>
</tr>
<tr>
<td>Result</td>
<td>{{ getattr(task, 'result', '') }}</td>
</tr>
{% for name in task._fields %}
{% if name not in ['name', 'uuid', 'state', 'args', 'kwargs', 'result'] and getattr(task, name, None) is not None %}
<tr>
<td>{{ humanize(name) }}</td>
<td>
{% if name in ['sent', 'received', 'started', 'succeeded', 'retried', 'timestamp', 'failed', 'revoked'] %}
{{ humanize(getattr(task, name, None), type='time') }}
{% elif name == 'worker' %}
<a
href="{{ reverse_url('worker', task.worker.hostname) }}">{{ task.worker.hostname }}</a>
{% elif name == 'traceback' %}
<pre>{{ getattr(task, name, None) }}</pre>
{% elif name in ['parent_id', 'root_id'] %}
<a
href="{{ reverse_url('task', getattr(task, name, None)) }}">{{ getattr(task, name, None) }}</a>
{% elif name == 'children' %}
{% for child in getattr(task, name, {}) %}
<a href="{{ reverse_url('task', child.id) }}">{{ child.id }}</a>
<br>
{% end %}
{% else %}
{{ getattr(task, name, None) }}
{% end %}
</td>
</tr>
{% end %}
{% end %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% end %}
@@ -0,0 +1,73 @@
{% extends "base.html" %}
{% block navbar %}
{% module Template("navbar.html", active_tab="tasks") %}
{% end %}
{% block container %}
<input type="hidden" value="{{ time }}" id='time'>
<input type="hidden" value="{{ columns }}" id='columns'>
<div class="container-fluid mt-3">
<table id="tasks-table" class="table table-bordered table-striped table-hover w-100">
<thead>
<tr>
<th>Name</th>
<th>UUID</th>
<th class="text-center">State</th>
<th>args</th>
<th>kwargs</th>
<th>Result</th>
<th class="text-center">Received</th>
<th class="text-center">Started</th>
<th class="text-center">Runtime</th>
<th>Worker</th>
<th>Exchange</th>
<th>Routing Key</th>
<th class="text-center">Retries</th>
<th class="text-center">Revoked</th>
<th>Exception</th>
<th class="text-center">Expires</th>
<th class="text-center">ETA</th>
</tr>
</thead>
<tbody>
{% for uuid, task in tasks %}
{% if getattr(task, 'name', None) is None %}
{% continue %}
{% end %}
<tr>
<td>{{ task.name }}</td>
<td>{{ task.uuid }}</td>
<td>{{ task.state }}</td>
<td>{{ task.args }}</td>
<td>{{ task.kwargs }}</td>
<td>
{% if task.state == "SUCCESS" %}
{{ task.result }}
{% elif task.state == "FAILURE" %}
{{ task.exception }}
{% end %}
</td>
<td>{{ humanize(task.received, type='time') }}</td>
<td>{{ humanize(task.started, type='time') }}</td>
<td>
{% if task.timestamp and task.started %}
{{ '%.2f' % humanize(task.timestamp - task.started) }} sec
{% end %}
</td>
<td>{{ task.worker }}</td>
<td>{{ task.exchange }}</td>
<td>{{ task.routing_key }}</td>
<td>{{ task.retries }}</td>
<td>{{ humanize(task.revoked, type='time') }}</td>
<td>{{ task.exception }}</td>
<td>{{ task.expires }}</td>
<td>{{ task.eta }}</td>
</tr>
{% end %}
</tbody>
</table>
</div>
{% end %}
@@ -0,0 +1,390 @@
{% extends "base.html" %}
{% block navbar %}
{% module Template("navbar.html", active_tab="workers") %}
{% end %}
{% block container %}
{% set other = {key: value for key, value in worker['stats'].items() if key not in
'pool pid prefetch_count autoscaler consumer broker clock total rusage'.split()} %}
<div class="container-fluid">
<div class="row-fluid">
<div class="col-lg-12">
<div class="mt-4 mb-4">
<h3 id="workername">{{ worker['name'] }}</h3>
</div>
<div class="btn-group float-end" role="group" aria-label="Button Group">
<button id="worker-group" type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown"
aria-expanded="false">
Refresh
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop">
<li><a id="worker-shutdown" class="dropdown-item" data-bs-dismiss="dropdown">Shut Down</a></li>
<li><a id="worker-pool-restart" class="dropdown-item" data-bs-dismiss="dropdown">Restart Pool</a></li>
<li><a id="worker-refresh" class="dropdown-item" data-bs-dismiss="dropdown">Refresh</a></li>
<li><a id="worker-refresh-all" class="dropdown-item" data-bs-dismiss="dropdown">Refresh All</a></li>
</ul>
</div>
<div class="tabbable">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item" role="presentation"><a class="nav-link active" href="#tab-pool" data-bs-toggle="tab"
data-bs-target="#tab-pool" type="button" role="tab" aria-controls="tab-pool" aria-selected="true">Pool</a>
</li>
<li class="nav-item" role="presentation"><a class="nav-link" href="#tab-broker" data-bs-toggle="tab"
data-bs-target="#tab-broker" type="button" role="tab" aria-controls="tab-broker"
aria-selected="false">Broker</a></li>
<li class="nav-item" role="presentation"><a class="nav-link" href="#tab-queues" data-bs-toggle="tab"
data-bs-target="#tab-queues" type="button" role="tab" aria-controls="tab-queues"
aria-selected="false">Queues</a></li>
<li class="nav-item" role="presentation"><a class="nav-link" href="#tab-tasks" data-bs-toggle="tab"
data-bs-target="#tab-tasks" type="button" role="tab" aria-controls="tab-tasks"
aria-selected="false">Tasks</a></li>
<li class="nav-item" role="presentation"><a class="nav-link" href="#tab-limits" data-bs-toggle="tab"
data-bs-target="#tab-limits" type="button" role="tab" aria-controls="tab-limits"
aria-selected="false">Limits</a></li>
<li class="nav-item" role="presentation"><a class="nav-link" href="#tab-config" data-bs-toggle="tab"
data-bs-target="#tab-config" type="button" role="tab" aria-controls="tab-config"
aria-selected="false">Config</a></li>
<li class="nav-item" role="presentation"><a class="nav-link" href="#tab-system" data-bs-toggle="tab"
data-bs-target="#tab-system" type="button" role="tab" aria-controls="tab-system"
aria-selected="false">System</a></li>
{% if other %}
<li class="nav-item"><a class="nav-link" href="#tab-other" data-bs-toggle="tab" data-bs-target="#tab-other"
type="button" role="tab" aria-controls="tab-other" aria-selected="false">Other</a></li>
{% end %}
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="tab-pool" role="tabpanel" aria-labelledby="tab-pool">
<div class="container-fluid">
<div class="row">
<div class="col-lg-8">
<table class="table table-bordered table-striped caption-top">
<caption>Worker pool options</caption>
<tbody>
{% for name,value in worker['stats'].get('pool', {}).items() %}
<tr>
<td>{{ humanize(name) }}</td>
<td>{{ humanize(value) }}</td>
</tr>
{% end %}
<tr>
<td>Worker PID</td>
<td>{{ worker['stats'].get('pid', 'N/A')}}</td>
</tr>
<tr>
<td>Prefetch Count</td>
<td>{{ worker['stats'].get('prefetch_count', 'N/A')}}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-lg-4 container">
<form class="mx-auto">
<legend class="form-label mt-md-5">Pool size control</legend>
<div class="mb-3">
<label for="pool-size" class="col-sm-2 col-form-label text-nowrap">Pool size</label>
<input type="number" id="pool-size" min="1" max="100" value="1">
<button id="worker-pool-grow" class="btn btn-primary btn-sm" type="button">Grow</button>
<button id="worker-pool-shrink" class="btn btn-primary btn-sm" type="button">Shrink</button>
</div>
<div class="mb-3 input-group">
<label for="min-autoscale" class="col-sm-2 form-label text-nowrap">Auto scale</label>
<input class="form-control-sm" id="min-autoscale" type="number" placeholder="Min" min="1"
max="100">
<input class="form-control-sm mx-1" id="max-autoscale" type="number" placeholder="Max" min="1"
max="100">
<button id="worker-pool-autoscale" class="btn btn-primary btn-sm" type="button">Apply</button>
</div>
</form>
</div>
</div>
{% if worker['stats'].get('autoscaler', None) %}
<div class="col-md-4">
<table class="table table-bordered table-striped caption-top">
<caption>Autoscaler options</caption>
<tbody>
{% for name,value in worker['stats']['autoscaler'].items() %}
<tr>
<td>{{ humanize(name) }}</td>
<td>{{ humanize(value) }}</td>
</tr>
{% end %}
</tbody>
</table>
</div>
{% end %}
</div>
</div> <!-- end pool tab -->
<div class="tab-pane fade" id="tab-broker" role="tabpanel" aria-labelledby="tab-broker">
<div class="col-lg-6">
<table class="table table-bordered table-striped caption-top">
<caption>Broker options</caption>
<tbody>
{% for name,value in (worker['stats'].get('consumer', None) or worker['stats'])['broker'].items() %}
<tr>
<td>{{ humanize(name) }}</td>
<td>{{ value }}</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div> <!-- end broker tab -->
<div class="tab-pane fade" id="tab-queues" role="tabpanel" aria-labelledby="tab-queues">
<table class="table table-bordered table-striped caption-top">
<caption>Active queues</caption>
<thead>
<tr>
<th>Name</th>
<th>Exclusive</th>
<!-- <th>Exchange</th> -->
<th>Durable</th>
<th>Routing key</th>
<th>No ACK</th>
<th>Alias</th>
<th>Queue arguments</th>
<th>Binding arguments</th>
<th>Auto delete</th>
<th style="width: 125px;"></th>
</tr>
</thead>
<tbody id="worker-queues">
{% for queue in worker.get('active_queues', []) %}
<tr>
<td>{{ queue['name'] }}</td>
<td>{{ queue['exclusive'] }}</td>
<!-- <td>{{ queue['exchange'] }}</td> -->
<td>{{ queue['durable'] }}</td>
<td>{{ queue['routing_key'] }}</td>
<td>{{ queue['no_ack'] }}</td>
<td>{{ queue['alias'] }}</td>
<td>{{ queue['queue_arguments'] }}</td>
<td>{{ queue['binding_arguments'] }}</td>
<td>{{ queue['auto_delete'] }}</td>
<td><button id="worker-cancel-consumer-{{ queue['name'] }}" class="btn btn-danger text-nowrap">Cancel
Consumer</button></td>
</tr>
{% end %}
</tbody>
</table>
<div class="control-group col-lg-3">
<div class="input-group mb-3">
<input id="add-consumer-name" type="text" class="form-control" placeholder="New consumer"
aria-label="New consumer" aria-describedby="worker-add-consumer">
<button class="btn btn-primary mx-1" type="button" id="worker-add-consumer">Add</button>
</div>
</div>
</div> <!-- end queues tab -->
<div class="tab-pane fade" id="tab-tasks" role="tabpanel" aria-labelledby="tab-tasks">
<table class="table table-bordered table-striped caption-top">
<caption>Processed tasks</caption>
<tbody>
{% for name,value in worker['stats']['total'].items() %}
<tr>
<td>{{ name }}</td>
<td>{{ value }}</td>
</tr>
{% end %}
</tbody>
</table>
<table class="table table-bordered table-striped caption-top">
<caption>Active tasks</caption>
<thead>
<tr>
<th>Name</th>
<th>UUID</th>
<th>Ack</th>
<th>PID</th>
<th>args</th>
<th>kwargs</th>
</tr>
</thead>
<tbody>
{% for task in worker.get('active', {}) %}
<tr>
<td>{{ task['name'] }}</td>
<td><a href="{{ reverse_url('task', task['id']) }}">{{ task['id'] }}</a></td>
<td>{{ task['acknowledged'] }}</td>
<td>{{ task['worker_pid'] }}</td>
<td>{{ task.get('args', 'N/A') }}</td>
<td>{{ task.get('kwargs', 'N/A') }}</td>
</tr>
{% end %}
</tbody>
</table>
<table class="table table-bordered table-striped caption-top">
<caption>Scheduled tasks</caption>
<thead>
<tr>
<th>Name</th>
<th>UUID</th>
<th>args</th>
<th>kwargs</th>
</tr>
</thead>
<tbody>
{% for task in worker.get('scheduled', {}) %}
<tr>
<td>{{ task['request']['name'] }}</td>
<td><a href="{{ reverse_url('task', task['request']['id']) }}">{{ task['request']['id'] }}</a></td>
<td>{{ task['request']['args'] }}</td>
<td>{{ task['request']['kwargs'] }}</td>
</tr>
{% end %}
</tbody>
</table>
<table class="table table-bordered table-striped caption-top">
<caption>Reserved tasks</caption>
<thead>
<tr>
<th>Name</th>
<th>UUID</th>
<th>args</th>
<th>kwargs</th>
</tr>
</thead>
<tbody>
{% for task in worker.get('reserved', {}) %}
<tr>
<td>{{ task['name'] }}</td>
<td><a href="{{ reverse_url('task', task['id']) }}">{{ task['id'] }}</a></td>
<td>{{ task['args'] }}</td>
<td>{{ task['kwargs'] }}</td>
</tr>
{% end %}
</tbody>
</table>
<table class="table table-bordered table-striped caption-top">
<caption>Revoked tasks</caption>
<thead>
<tr>
<th>UUID</th>
</tr>
</thead>
<tbody>
{% for task in worker.get('revoked', []) %}
<tr>
<td><a href="{{ reverse_url('task', task) }}">{{ task }}</a></td>
</tr>
{% end %}
</tbody>
</table>
</div> <!-- end tasks tab -->
<div class="tab-pane fade" id="tab-limits" role="tabpanel" aria-labelledby="tab-limits">
<div class="col-lg-10">
<table class="table table-bordered table-striped caption-top">
<caption>Task limits</caption>
<thead>
<tr>
<th>Task</th>
<th class="text-center">Rate limit</th>
<th class="text-center">Timeouts</th>
</tr>
</thead>
<tbody id="limits-table">
{% for taskname in worker.get('registered', []) %}
<tr>
<td>{{ taskname }}</td>
<td class="col-lg-2">
<div class="form-group">
<div class="input-group">
<input class="form-control form-control-sm" type="number">
<button class="btn btn-primary btn-sm mx-1" type="button"
id="task-rate-limit-{{taskname}}">Apply</button>
</div>
</div>
</td>
<td class="col-lg-2">
<div class="form-group">
<div class="input-group">
<input class="form-control form-control-sm" type="number">
<button class="btn btn-primary btn-sm mx-1" type="button"
id="task-timeout-soft-{{taskname}}">Soft</button>
<button class="btn btn-primary btn-sm mx-1" type="button"
id="task-timeout-hard-{{taskname}}">Hard</button>
</div>
</div>
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div> <!-- end limits tab -->
<div class="tab-pane fade" id="tab-config" role="tabpanel" aria-labelledby="tab-config">
<div class="col-lg-8">
<table class="table table-bordered table-striped caption-top">
<caption>Configuration options</caption>
<tbody>
{% for name,value in sorted(worker.get('conf', {}).items()) %}
{% if value is not None %}
<tr>
<td><a
href="https://docs.celeryq.dev/en/latest/userguide/configuration.html#{{ name.lower().replace('_', '-') }}"
target="_blank">{{ name }}</a></td>
<td>{{ value }}</td>
</tr>
{% end %}
{% end %}
</tbody>
</table>
</div>
</div> <!-- end config tab -->
<div class="tab-pane fade" id="tab-system" role="tabpanel" aria-labelledby="tab-system">
<div class="col-lg-8">
<table class="table table-bordered table-striped caption-top">
<caption>System usage statistics</caption>
<tbody>
{% if isinstance(worker['stats'].get('rusage', None), dict) %}
{% for name, value in worker['stats']['rusage'].items() %}
<tr>
<td>{{ name }}</td>
<td>{{ value }}</td>
</tr>
{% end %}
{% end %}
</tbody>
</table>
</div>
</div> <!-- end system tab -->
{% if other %}
<div class="tab-pane fade" id="tab-other" role="tabpanel" aria-labelledby="tab-other">
<div class="col-lg-8">
<table class="table table-bordered table-striped caption-top">
<caption>Other statistics</caption>
<tbody>
{% for name, value in other.items() %}
<tr>
<td>{{ name }}</td>
<td>{{ value }}</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div> <!-- end other tab -->
{% end %}
</div>
</div>
</div>
</div>
{% end %}
@@ -0,0 +1,58 @@
{% extends "base.html" %}
{% block navbar %}
{% module Template("navbar.html", active_tab="workers")%}
{% end %}
{% block container %}
<div class="container-fluid mt-3">
<figure class="table-responsive">
<table id="workers-table" class="table table-bordered table-striped table-hover w-100">
<thead>
<tr>
<th>Worker</th>
<th class="text-center">Status</th>
<th class="text-center">Active</th>
<th class="text-center">Processed</th>
<th class="text-center">Failed</th>
<th class="text-center">Succeeded</th>
<th class="text-center">Retried</th>
<th class="text-center">Load Average</th>
</tr>
</thead>
<tbody>
{% for name, info in workers.items() %}
<tr id="{{ url_escape(name) }}">
<td>{{ name }}</td>
<td>{{ info.get('status', None) }}</td>
<td>{{ info.get('active', 0) or 0 }}</td>
<td>{{ info.get('task-received', 0) }}</td>
<td>{{ info.get('task-failed', 0) }}</td>
<td>{{ info.get('task-succeeded', 0) }}</td>
<td>{{ info.get('task-retried', 0) }}</td>
<td>{{ humanize(info.get('loadavg', 'N/A')) }}</td>
</tr>
{% end %}
</tbody>
<tfoot>
<tr>
<th>Total</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</tfoot>
</table>
</figure>
</div>
{% end %}
{% block extra_scripts %}
<script type="text/javascript">
var autorefresh = {{ autorefresh }};
</script>
{% end %}