diff --git a/plugins/host_agent/routes.py b/plugins/host_agent/routes.py index 0eeec1f..e25c1da 100644 --- a/plugins/host_agent/routes.py +++ b/plugins/host_agent/routes.py @@ -548,13 +548,16 @@ async def host_panel(host_id: str): hostlvl = latest.get(host.name, {}) ls = reg.last_seen_at if reg else None - stale = bool(reg) and ( - ls is None or (datetime.now(timezone.utc) - ls).total_seconds() > _stale_after_seconds()) + # "Deployed" is gated on the agent actually checking in (last_seen_at), NOT on + # the registration row — that row is minted before the playbook runs, so a + # failed provision would otherwise look deployed. + reporting = bool(reg) and ls is not None + stale = reporting and (datetime.now(timezone.utc) - ls).total_seconds() > _stale_after_seconds() ansible_cfg = current_app.config.get("ANSIBLE", {}) return await render_template( "panel.html", - host=host, reg=reg, hostlvl=hostlvl, stale=stale, target=target, + host=host, reg=reg, hostlvl=hostlvl, reporting=reporting, stale=stale, target=target, ansible_available=has_capability("ansible.run_playbook"), managed_key_set=bool((ansible_cfg.get("ssh_public_key") or "").strip()), ) diff --git a/plugins/host_agent/templates/panel.html b/plugins/host_agent/templates/panel.html index 4e8cc5c..5279945 100644 --- a/plugins/host_agent/templates/panel.html +++ b/plugins/host_agent/templates/panel.html @@ -3,16 +3,18 @@

Agent

- {% if reg %} + {% if reporting %} {{ 'stale' if stale else 'reporting' }}{% if reg.agent_version %} · v{{ reg.agent_version }}{% endif %} - {% if reg.last_seen_at %} · last seen {{ reg.last_seen_at.strftime('%Y-%m-%d %H:%M') }}{% endif %} + · last seen {{ reg.last_seen_at.strftime('%Y-%m-%d %H:%M') }} + {% elif reg %} + pending — no check-in yet {% endif %}
- {% if reg %} - {# ── Installed: at-a-glance metrics + lifecycle ── #} + {% if reporting %} + {# ── Reporting: at-a-glance metrics + lifecycle ── #}
{% set cpu = hostlvl.get('cpu_pct') %} {% set mem = hostlvl.get('mem_used_pct') %} @@ -54,10 +56,21 @@ {% endif %} {% else %} - {# ── Not installed: provisioning path ── #} + {# ── Not reporting: pending (token minted, no check-in) or never installed ── #} + {% if reg %} +
+ A token was minted for this host but no metrics have arrived yet — the + deploy may still be running, or it failed. Open the latest + Ansible run to check, then retry below. Live metrics appear here + once the agent checks in. +
+ {% else %}

No agent installed. The agent reports CPU, memory, disk, network and more back to Steward.

+ {% endif %} {% if session.user_role != 'admin' %}

An admin can install the agent here.

@@ -113,5 +126,12 @@ {% endif %} + + {% if reg and session.user_role == 'admin' %} +
+ +
+ {% endif %} {% endif %}
diff --git a/steward/ansible/executor.py b/steward/ansible/executor.py index dcc2038..3f51975 100644 --- a/steward/ansible/executor.py +++ b/steward/ansible/executor.py @@ -442,10 +442,21 @@ async def start_run( await _flush() await proc.wait() + recap = parse_recap(_run_lines.get(run_id, [])) if run_id in _cancelled: final_status = "cancelled" + elif proc.returncode != 0: + final_status = "failed" + elif not recap: + # ansible-playbook exits 0 on "no hosts matched" / empty inventory + # — nothing actually ran, so don't report it as a success. + final_status = "failed" + if len(failures) < _FAILURE_CAP: + failures.append( + "No hosts matched — nothing executed " + "(check the host's Ansible target and inventory).") else: - final_status = "success" if proc.returncode == 0 else "failed" + final_status = "success" finally: shutil.rmtree(tmpdir, ignore_errors=True)