diff --git a/src/ci_orchestrator/web/filters.py b/src/ci_orchestrator/web/filters.py
index ee48f18..1718edf 100644
--- a/src/ci_orchestrator/web/filters.py
+++ b/src/ci_orchestrator/web/filters.py
@@ -9,6 +9,7 @@ degraded ``operations/`` fetch, an unset timestamp, ...).
from __future__ import annotations
+import re
from datetime import UTC, datetime
from pathlib import Path
from typing import TYPE_CHECKING
@@ -179,6 +180,47 @@ def run_badge(status: str | None) -> str:
return _RUN_BADGES.get(status or "", "badge-muted")
+#: Icon id for a run status: a check/x for the two pass/fail verdicts, a play
+#: glyph for "still going". ``queued``/``cancelled``/``skipped`` are neither
+#: a verdict nor in progress, so they stay plain text -- an icon there would
+#: just be noise repeating the badge colour.
+_RUN_BADGE_ICONS: dict[str, str] = {
+ "success": "i-check",
+ "failure": "i-x",
+ "running": "i-play",
+}
+
+
+def run_badge_icon(status: str | None) -> str:
+ """Icon id for :func:`run_badge`'s pill, or ``""`` for non-terminal states."""
+ return _RUN_BADGE_ICONS.get(status or "", "")
+
+
+#: Marketing noise in ``/proc/cpuinfo``'s ``model name`` that costs tile width
+#: and says nothing an operator needs: trademark marks, the clock-speed tail
+#: (the CPU tile already shows live load, and the base clock is not the
+#: current one), and the core-count tail (the tile prints the real ``ncpu``).
+_CPU_NOISE = re.compile(
+ r"\((?:R|TM|tm|r)\)"
+ r"|\s+(?:CPU|Processor)?\s*@\s*[\d.]+\s*[GM]Hz"
+ r"|\s+\d+-Core\s+Processor"
+ r"|\s+CPU$"
+)
+
+
+def cpu_short(model: str | None) -> str:
+ """Trim a ``/proc/cpuinfo`` model name to what fits a status tile.
+
+ ``"Intel(R) Core(TM) i9-13900K CPU @ 3.00GHz"`` -> ``"Intel Core i9-13900K"``.
+ An empty/absent model stays ``""`` (not ``"-"``) so the template can drop
+ the line entirely instead of rendering a dash under the CPU label. The full
+ untrimmed name still shows in the Platform card.
+ """
+ if not model:
+ return ""
+ return " ".join(_CPU_NOISE.sub(" ", model).split())
+
+
def clone_job(name: str | None) -> ParsedClone:
"""Parse a VM/clone name into its ``job_id`` + ``started`` (Stage C1).
@@ -202,16 +244,20 @@ def register(templates: Jinja2Templates) -> None:
templates.env.filters["thousands"] = thousands
templates.env.filters["clone_job"] = clone_job
templates.env.filters["run_badge"] = run_badge
+ templates.env.filters["run_badge_icon"] = run_badge_icon
+ templates.env.filters["cpu_short"] = cpu_short
__all__ = [
"clone_job",
+ "cpu_short",
"duration",
"humanbytes",
"localtime",
"register",
"relative_time",
"run_badge",
+ "run_badge_icon",
"shorten_path",
"thousands",
]
diff --git a/src/ci_orchestrator/web/templates/partials/executions.html b/src/ci_orchestrator/web/templates/partials/executions.html
index 9043688..66f2727 100644
--- a/src/ci_orchestrator/web/templates/partials/executions.html
+++ b/src/ci_orchestrator/web/templates/partials/executions.html
@@ -3,10 +3,17 @@
Actions run (the live ones included), with the build VM(s) that run spawned
grouped into its VM cell, Remove action and all.
+ Every fact gets its own column -- no cell stacks two data points with
+
(Run/Repo, Branch/Commit, Trigger/Actor, Duration/Started, VM/IP are
+ each a pair that used to share one cell on two lines). A wide table is the
+ accepted cost here: the card is already `.card--wide` and its own
+ `.table-wrap` scrolls horizontally rather than the page.
+
Degraded states (no token, expired token, Gitea unreachable) render a notice
and still list every running VM below, so this card is never less useful
than the VM table it replaces. Orphan VMs are NOT handled here: the sweep
- lives in the Maintenance card and the count in the status-header VMs tile;
+ lives in the Maintenance card (Stage F retired the status-header VMs tile
+ that carried the count);
``executions.unmatched_vms`` only exists so a powered-on VM whose run has
scrolled out of the window (or that no run ever claimed) keeps its row and
its Remove button.
@@ -47,12 +54,17 @@
@@ -65,32 +77,35 @@
{% else %}
#{{ run.number }}{% if run.attempt > 1 %}.{{ run.attempt }}{% endif %}
{% endif %}
- {% if run.repo %}Run
+ Repo
Workflow
- Branch / commit
+ Branch
+ Commit
Trigger
+ Actor
Status
Duration
+ Started
VM
+ IP / Uptime
Actions
{{ run.repo }}{% endif %}
+
{{ run.commit[:7] }}{% else %}—{% endif %}{{ run.commit[:7] }}{% endif %}
-