Ansible schedule form: structured playbook-variable fields + first-item dropdown defaults #2

Merged
bvandeusen merged 126 commits from dev into main 2026-06-30 23:44:04 -04:00
6 changed files with 64 additions and 39 deletions
Showing only changes of commit e58c86cf01 - Show all commits
@@ -43,7 +43,10 @@
elements: { point: { radius: 0 } },
scales: {
x: { type: "linear", ticks: { callback: function (v) { return fmtTime(v); }, maxTicksLimit: 6, color: "#8a8a92", font: { size: 10 } }, grid: { color: "#30303a" } },
y: { beginAtZero: true, max: 100, ticks: { color: "#8a8a92", font: { size: 10 } }, grid: { color: "#30303a" } },
// Auto-scale to the data (with a little headroom) so the lines fill the
// panel instead of hugging the bottom of a fixed 0100 axis. Axis labels
// still show the real %, so the values stay honest.
y: { beginAtZero: true, grace: "8%", ticks: { color: "#8a8a92", font: { size: 10 } }, grid: { color: "#30303a" } },
},
plugins: { legend: { labels: { color: "#b8b8b0", boxWidth: 10, font: { size: 11 } } } },
},
+22 -17
View File
@@ -1,9 +1,10 @@
{# host_agent widget — fleet glance: per-host row with a sparkline beside each
metric (cpu/mem/disk/load), mirroring the host-page AGENT panel. #}
{# host_agent widget — fleet glance. Per host: name on its own line (never
truncated), then a row of metric cells (cpu/mem/disk/load), each value with a
trend sparkline beneath it — the host-page AGENT panel, on the dashboard. #}
{% from "_macros.html" import metric_style %}
{% if rows %}
{% macro metric_cell(label, value, fmt, svg, style="", title="") %}
<div style="min-width:74px;flex-shrink:0;" title="{{ title }}">
<div style="min-width:74px;flex:0 0 auto;" title="{{ title }}">
<div style="font-size:0.66rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.03em;">{{ label }}</div>
<div style="font-weight:600;font-size:0.8rem;font-variant-numeric:tabular-nums;{{ style }}">
{% if value is not none %}{{ fmt|format(value) }}{% else %}—{% endif %}
@@ -11,27 +12,31 @@
<div style="line-height:0;height:16px;">{{ svg | safe }}</div>
</div>
{% endmacro %}
<div style="display:grid;gap:0.1rem;">
<div class="host-blocks">
{% for r in rows %}
{% set stale = r.stale %}
<div style="display:flex;align-items:center;gap:0.75rem;font-size:0.82rem;padding:0.45rem 0;border-bottom:1px solid var(--border);">
<span class="dot {% if stale %}dot-dim{% elif (r.cpu_pct or 0) >= 90 or (r.mem_used_pct or 0) >= 90 or (r.disk_worst or 0) >= 90 %}dot-warn{% else %}dot-up{% endif %}"
title="{% if stale %}stale / no recent data{% else %}warns at CPU/memory ≥90% or any disk mount ≥90% (the 'disk /' figure shows root only){% endif %}"></span>
{% set name_style = "flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500;" %}
{% if session.user_id %}
<a href="/hosts/{{ r.host.id }}" title="{{ r.host.name }}" style="{{ name_style }}">{{ r.host.name }}</a>
{% else %}
<span title="{{ r.host.name }}" style="{{ name_style }}">{{ r.host.name }}</span>
{% endif %}
<div style="display:flex;gap:1.1rem;flex-shrink:0;align-items:flex-start;">
<div style="padding:0.55rem 0;border-bottom:1px solid var(--border);">
{# Line 1 — health dot + full host name (own line) + last seen #}
<div style="display:flex;align-items:center;gap:0.5rem;">
<span class="dot {% if stale %}dot-dim{% elif (r.cpu_pct or 0) >= 90 or (r.mem_used_pct or 0) >= 90 or (r.disk_worst or 0) >= 90 %}dot-warn{% else %}dot-up{% endif %}"
style="flex-shrink:0;"
title="{% if stale %}stale / no recent data{% else %}warns at CPU/memory ≥90% or any disk mount ≥90% (the 'disk /' figure shows root only){% endif %}"></span>
{% if session.user_id %}
<a href="/hosts/{{ r.host.id }}" style="font-weight:600;font-size:0.85rem;overflow-wrap:anywhere;">{{ r.host.name }}</a>
{% else %}
<span style="font-weight:600;font-size:0.85rem;overflow-wrap:anywhere;">{{ r.host.name }}</span>
{% endif %}
<span style="margin-left:auto;font-size:0.72rem;color:var(--text-dim);flex-shrink:0;">
{{ r.reg.last_seen_at.strftime("%H:%M:%S") if r.reg.last_seen_at else "never" }}
</span>
</div>
{# Line 2 — metric cells with value + trend; wraps instead of truncating #}
<div style="display:flex;flex-wrap:wrap;gap:0.4rem 1.1rem;margin-top:0.35rem;padding-left:1.1rem;">
{{ metric_cell("cpu", r.cpu_pct, "%.0f%%", r.sparks.cpu, metric_style(r.cpu_pct), "Average CPU utilization") }}
{{ metric_cell("mem", r.mem_used_pct, "%.0f%%", r.sparks.mem, metric_style(r.mem_used_pct), "Memory in use") }}
{{ metric_cell("disk /", r.disk_root, "%.0f%%", r.sparks.disk, metric_style(r.disk_root), "Root filesystem (/) usage") }}
{{ metric_cell("load", r.load_1m, "%.2f", r.sparks.load, "", "Load average (1m)") }}
</div>
<span style="min-width:58px;text-align:right;font-size:0.72rem;color:var(--text-dim);flex-shrink:0;">
{{ r.reg.last_seen_at.strftime("%H:%M:%S") if r.reg.last_seen_at else "never" }}
</span>
</div>
{% endfor %}
</div>
+9 -1
View File
@@ -163,7 +163,7 @@ textarea { resize: vertical; }
.ping-row { display:flex; align-items:center; gap:1rem; padding:0.45rem 0; border-bottom:1px solid var(--border); }
.ping-row:last-child { border-bottom:none; }
.ping-meta { min-width:120px; max-width:180px; flex-shrink:1; overflow:hidden; }
.ping-name { font-size:0.875rem; font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; display:block; }
.ping-name { font-size:0.875rem; font-weight:500; display:block; overflow-wrap:anywhere; }
a.ping-name:hover { text-decoration:underline; }
/* Dashboard widget grid — Gridstack-rendered. Static (positioned but inert) in
view mode; the dashboard itself becomes the edit surface when Edit is toggled,
@@ -174,6 +174,14 @@ a.ping-name:hover { text-decoration:underline; }
padding:0.8rem 1rem; display:flex; flex-direction:column; overflow:hidden;
}
.grid-stack-item-content .dash-cell-body { flex:1; min-height:0; overflow:auto; }
/* The widget body is a query container, so fragments can restyle to their OWN
panel width (not the viewport) — widgets adapt as you resize them. */
.dash-cell-body { container-type:inline-size; }
/* Host-list widgets flow into columns as the panel widens: use the width, drop
the vertical deadspace; stay single-column (and untruncated) when narrow. */
.host-blocks { display:grid; grid-template-columns:1fr; gap:0 1.5rem; }
@container (min-width:520px) { .host-blocks { grid-template-columns:1fr 1fr; } }
@container (min-width:900px) { .host-blocks { grid-template-columns:1fr 1fr 1fr; } }
.panel-chrome { display:none; }
.panel-drag { cursor:grab; color:var(--text-dim); user-select:none; }
.panel-drag:active { cursor:grabbing; }
+5 -1
View File
@@ -44,7 +44,7 @@
.ping-row { display:flex; align-items:center; gap:1rem; padding:0.45rem 0; border-bottom:1px solid var(--border); }
.ping-row:last-child { border-bottom:none; }
.ping-meta { min-width:120px; max-width:180px; flex-shrink:1; overflow:hidden; }
.ping-name { font-size:0.875rem; font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; display:block; }
.ping-name { font-size:0.875rem; font-weight:500; display:block; overflow-wrap:anywhere; }
.ping-addr { display:block; color:var(--text-muted); font-size:0.75rem; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ping-pills { display:flex; gap:2px; flex:1; min-width:0; overflow:hidden; align-items:center; }
.pill { display:inline-block; width:7px; height:22px; border-radius:2px; }
@@ -59,6 +59,10 @@
.dash-grid { display:grid; grid-template-columns:repeat(12,1fr); grid-auto-rows:70px; gap:1rem; }
.dash-grid > .dash-cell { margin-bottom:0; min-width:0; overflow:hidden; display:flex; flex-direction:column; }
.dash-cell .dash-cell-body { flex:1; min-height:0; overflow:auto; }
.dash-cell-body { container-type:inline-size; }
.host-blocks { display:grid; grid-template-columns:1fr; gap:0 1.5rem; }
@container (min-width:520px) { .host-blocks { grid-template-columns:1fr 1fr; } }
@container (min-width:900px) { .host-blocks { grid-template-columns:1fr 1fr 1fr; } }
@media (max-width:820px) {
.dash-grid { grid-template-columns:1fr; grid-auto-rows:auto; }
.dash-grid > .dash-cell { grid-column:1 / -1 !important; grid-row:auto !important; }
+22 -17
View File
@@ -1,33 +1,38 @@
{# Unified Hosts widget — monitor status + agent glance per host, links to the hub. #}
{# Unified Hosts widget — monitor status + agent glance per host, links to the hub.
Name sits on its own line (never truncated); its data wraps beneath it, grouped
under the host. Prefer vertical growth over cramming a row. #}
{% from "_macros.html" import metric_style %}
{% if hosts %}
<div style="display:grid;gap:0.1rem;">
<div class="host-blocks">
{% for host in hosts %}
{% set ping = latest_pings.get(host.id) %}
{% set dns = latest_dns.get(host.id) %}
{% set ut = uptime.get(host.id, {}) %}
{% set a = agent.get(host.name, {}) %}
{% set down = host.ping_enabled and ping and ping.status.value != 'up' %}
<div style="display:flex;align-items:center;gap:0.6rem;font-size:0.82rem;padding:0.3rem 0;border-bottom:1px solid var(--border);">
<span class="dot {% if down %}dot-down{% elif host.ping_enabled and ping %}dot-up{% else %}dot-dim{% endif %}"
title="{% if not host.ping_enabled %}ping off{% elif ping %}{{ ping.status.value }}{% else %}no data{% endif %}"></span>
{% set name_style = "flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500;" %}
{% if session.user_id %}
<a href="/hosts/{{ host.id }}" title="{{ host.name }}" style="{{ name_style }}">{{ host.name }}</a>
{% else %}
<span title="{{ host.name }}" style="{{ name_style }}">{{ host.name }}</span>
{% endif %}
{% set spark = cpu_sparks.get(host.name) %}
{% if spark %}<span title="CPU — last hour" style="flex-shrink:0;line-height:0;opacity:0.85;">{{ spark | safe }}</span>{% endif %}
<span style="display:flex;gap:0.75rem;font-variant-numeric:tabular-nums;color:var(--text-muted);font-size:0.75rem;flex-shrink:0;">
{# Monitors #}
{% set spark = cpu_sparks.get(host.name) %}
<div style="padding:0.5rem 0;border-bottom:1px solid var(--border);">
{# Line 1 — status dot + full host name (own line) + cpu trend #}
<div style="display:flex;align-items:center;gap:0.5rem;">
<span class="dot {% if down %}dot-down{% elif host.ping_enabled and ping %}dot-up{% else %}dot-dim{% endif %}"
style="flex-shrink:0;"
title="{% if not host.ping_enabled %}ping off{% elif ping %}{{ ping.status.value }}{% else %}no data{% endif %}"></span>
{% if session.user_id %}
<a href="/hosts/{{ host.id }}" style="font-weight:600;font-size:0.85rem;overflow-wrap:anywhere;">{{ host.name }}</a>
{% else %}
<span style="font-weight:600;font-size:0.85rem;overflow-wrap:anywhere;">{{ host.name }}</span>
{% endif %}
{% if spark %}<span title="CPU — last hour" style="margin-left:auto;line-height:0;opacity:0.85;flex-shrink:0;">{{ spark | safe }}</span>{% endif %}
</div>
{# Line 2 — monitors + agent metrics; wraps instead of truncating #}
<div style="display:flex;flex-wrap:wrap;gap:0.25rem 0.9rem;margin-top:0.25rem;padding-left:1.1rem;
font-variant-numeric:tabular-nums;color:var(--text-muted);font-size:0.75rem;">
{% if host.ping_enabled and ping and ping.response_time_ms is not none %}
<span title="Ping latency"><span style="color:var(--text-dim);">ping</span> {{ "%.0f"|format(ping.response_time_ms) }}ms</span>
{% endif %}
{% if ut.get('24h') is not none %}
<span title="Uptime 24h"><span style="color:var(--text-dim);">24h</span> {{ "%.1f"|format(ut['24h']) }}%</span>
{% endif %}
{# Agent glance — failing metric coloured amber/red (≥80/≥90) #}
{% if a %}
{% if a.get('cpu_pct') is not none %}<span title="CPU" style="{{ metric_style(a.cpu_pct) }}"><span style="color:var(--text-dim);">cpu</span> {{ "%.0f"|format(a.cpu_pct) }}%</span>{% endif %}
{% if a.get('mem_used_pct') is not none %}<span title="Memory" style="{{ metric_style(a.mem_used_pct) }}"><span style="color:var(--text-dim);">mem</span> {{ "%.0f"|format(a.mem_used_pct) }}%</span>{% endif %}
@@ -36,7 +41,7 @@
{% else %}
<span style="color:var(--text-dim);" title="No agent reporting">no agent</span>
{% endif %}
</span>
</div>
</div>
{% endfor %}
</div>
+2 -2
View File
@@ -17,9 +17,9 @@
<div style="display:grid;grid-template-columns:1fr 52px 52px 52px;gap:0.35rem;align-items:center;padding:0.15rem 0;font-size:0.82rem;">
{% if session.user_id %}
<a href="/hosts/{{ host.id }}" title="{{ host.name }}"
style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:inherit;text-decoration:none;">{{ host.name }}</a>
style="overflow-wrap:anywhere;color:inherit;text-decoration:none;">{{ host.name }}</a>
{% else %}
<span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;" title="{{ host.name }}">{{ host.name }}</span>
<span style="overflow-wrap:anywhere;" title="{{ host.name }}">{{ host.name }}</span>
{% endif %}
{% for window in ["24h", "7d", "30d"] %}
{% set pct = ut.get(window) %}