feat(homeassistant): it shows system problems and installs updates on a weekly slot, and leaves device readings alone (#6748, #6749, #6747)
The connection's job is keeping Home Assistant running. Each poll now reads open
repairs, Supervisor health, disk, recovery mode and add-ons that should be running
over its websocket, for the page, the overview and the problems_open, disk_free_pct
and update_failed metrics. That needs a websocket client, so websockets joins the
dependencies.
A weekly slot, set on the page or through MCP and off until then, installs add-ons,
then Core, then the OS, each after its own confirmed backup, ending at the first
failure. The slot arithmetic moves from the host agent's cadence to steward.core.weekly
so both share it.
The device scrape goes: the page's Metrics section and a target's token_from from
4c9ed02 are removed, and a migration deletes targets using it, as they'd otherwise ask
with no token. A refused token still stops a scrape.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -36,12 +36,33 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="stat-strip">
|
||||
{{ stat_tile("System problems", problems|length if health.repairs_read else "—", state="degraded" if problems else none) }}
|
||||
{{ stat_tile("Disk free", (health.disk_free_gb ~ " GB") if health.disk_free_gb is not none and health.disk_free_gb is defined else "—") }}
|
||||
{{ stat_tile("Updates waiting", installable|length if connection.last_ok_at else "—") }}
|
||||
{{ stat_tile("Last good backup", (backup_at|ago) if backup_at else "—", state="degraded" if backup_tone == 'warn' else none) }}
|
||||
{{ stat_tile("Next backup", (backup_next|ago) if backup_next else "—") }}
|
||||
</div>
|
||||
<p class="{{ 'tone-warn' if backup_tone == 'warn' else 'field-hint' }}">{{ backup_says }}</p>
|
||||
|
||||
<section class="card" aria-labelledby="ha-system">
|
||||
<h2 id="ha-system" class="section-title">System</h2>
|
||||
{% if problems %}
|
||||
<table class="table">
|
||||
<thead><tr><th scope="col">Problem</th><th scope="col">Severity</th></tr></thead>
|
||||
<tbody>
|
||||
{% for p in problems %}<tr><td>{{ p.title }}</td><td>{{ status_badge('down' if p.severity in ('critical', 'error') else 'degraded', p.severity|capitalize) }}</td></tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="field-hint">Fix these in Home Assistant: Settings → System → Repairs.</p>
|
||||
{% elif health.repairs_read %}
|
||||
<p class="field-hint">No repairs open, and nothing wrong reported.</p>
|
||||
{% elif connection.last_ok_at %}
|
||||
<p class="tone-warn">Home Assistant wouldn't share its repairs with this user. {% if admin %}<a href="/plugins/homeassistant/connection">Sign in again</a> as an administrator to see them.{% endif %}</p>
|
||||
{% endif %}
|
||||
{% if health.repairs_read and not health.supervised %}<p class="field-hint">No Supervisor answered, so disk space and add-ons aren't checked here.</p>{% endif %}
|
||||
{% if health.error %}<p class="tone-warn" style="margin-bottom:0;">This is from an earlier read. {{ health.error }}</p>{% endif %}
|
||||
</section>
|
||||
|
||||
<section class="card" aria-labelledby="ha-updates">
|
||||
<h2 id="ha-updates" class="section-title">Updates</h2>
|
||||
{% if installable %}
|
||||
@@ -73,21 +94,36 @@
|
||||
{% if others %}<p class="field-hint" style="margin-bottom:0;">Waiting, for Home Assistant to install: {% for u in others %}{{ u.title }}{{ ", " if not loop.last }}{% endfor %}. Steward installs Core, OS and add-on updates only.</p>{% endif %}
|
||||
</section>
|
||||
|
||||
{% if metrics %}
|
||||
<section class="card" aria-labelledby="ha-metrics">
|
||||
<h2 id="ha-metrics" class="section-title">Metrics</h2>
|
||||
{% if metrics.uses_sign_in %}
|
||||
<p class="field-hint" style="margin-bottom:0;">{{ status_badge(metrics.reach) }} Read with this sign-in into the target <a href="/plugins/prometheus/targets/{{ metrics.id }}">{{ metrics.name }}</a>: {{ metrics.says }}{% if metrics.reach == 'up' %} {{ metrics.kept }} series kept.{% endif %}</p>
|
||||
{% elif metrics.id %}
|
||||
<p class="field-hint">The target <a href="/plugins/prometheus/targets/{{ metrics.id }}">{{ metrics.name }}</a> reads these with a token of its own: {{ metrics.says }}</p>
|
||||
{% if admin %}<p class="field-hint">Use this sign-in instead, so one Home Assistant user covers everything. The target keeps its name, filters and history, and forgets its token.</p>
|
||||
<form method="post" action="/plugins/homeassistant/metrics">{{ csrf_field() }}<button class="btn btn-sm" type="submit">Use this sign-in</button></form>{% endif %}
|
||||
<section class="card" aria-labelledby="ha-schedule">
|
||||
<h2 id="ha-schedule" class="section-title">Scheduled updates</h2>
|
||||
{% if plan and plan.enabled %}
|
||||
<p class="field-hint">{{ plan_when }}. Next: {{ plan_next|ago }}. Add-ons first, then Core, then the OS, each after a confirmed backup. A failure ends the run and the rest wait.</p>
|
||||
{% else %}
|
||||
<p class="field-hint">Home Assistant's entity readings can be kept as metrics too, for charts and alert rules, with this sign-in and nothing else to set up.</p>
|
||||
{% if admin %}<form method="post" action="/plugins/homeassistant/metrics">{{ csrf_field() }}<button class="btn btn-sm" type="submit">Read its metrics</button></form>{% endif %}
|
||||
<p class="field-hint">Off. Updates are only installed when someone chooses Install.</p>
|
||||
{% endif %}
|
||||
{% if plan and plan.stopped %}<p class="tone-warn">The last run stopped early. {{ plan.stopped }}</p>{% endif %}
|
||||
{% if admin %}
|
||||
<p class="tone-warn">A Core update restarts Home Assistant and an OS update reboots the machine, with nobody there to watch. An update can also change how automations behave.</p>
|
||||
<form method="post" action="/plugins/homeassistant/schedule" style="display:flex;flex-wrap:wrap;gap:0.75rem;align-items:flex-end;">{{ csrf_field() }}
|
||||
<label style="display:flex;gap:0.5rem;align-items:center;cursor:pointer;margin-bottom:0.4rem;">
|
||||
<input type="checkbox" name="enabled" value="on"{% if plan and plan.enabled %} checked{% endif %}> Install on a schedule
|
||||
</label>
|
||||
<div class="field" style="margin-bottom:0;">
|
||||
<label class="field-label" for="ha-day">Day</label>
|
||||
<select class="input" id="ha-day" name="weekday">{% for d in weekdays %}<option value="{{ loop.index0 }}"{% if loop.index0 == (plan.weekday if plan else 5) %} selected{% endif %}>{{ d }}</option>{% endfor %}</select>
|
||||
</div>
|
||||
<div class="field" style="margin-bottom:0;">
|
||||
<label class="field-label" for="ha-time">Time</label>
|
||||
<input class="input" id="ha-time" type="time" name="at_time" required value="{{ plan.at_time if plan else '05:00' }}">
|
||||
</div>
|
||||
<div class="field" style="margin-bottom:0;">
|
||||
<label class="field-label" for="ha-zone">Time zone</label>
|
||||
<input class="input" id="ha-zone" name="time_zone" required value="{{ plan.time_zone if plan else default_zone }}">
|
||||
</div>
|
||||
<button class="btn btn-sm" type="submit">Save</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% if runs %}
|
||||
<section class="card" aria-labelledby="ha-runs">
|
||||
|
||||
Reference in New Issue
Block a user