Files
FabledSteward/plugins/prometheus/templates/prometheus/form.html
T
bvandeusenandClaude Opus 5.5 f7a856cb90
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m37s
CI / integration (push) Failing after 1m54s
CI / publish (push) Skipped
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>
2026-11-01 14:16:42 -05:00

111 lines
6.6 KiB
HTML

{# plugins/prometheus/templates/prometheus/form.html — add or edit a scrape
target (milestone 506): where its /metrics is, its token, how often, and
what to keep, with Test to scrape it once before saving. The token is
never sent back to the browser; a blank token field keeps the stored one. #}
{% extends "base.html" %}
{% from "_macros.html" import crumbs, page_header %}
{% block title %}{{ ("Edit " ~ target.name) if target else "Add Prometheus target" }} — Steward{% endblock %}
{% from "prometheus/_tabs.html" import prometheus_tabs %}
{% block section_tabs %}{{ prometheus_tabs("targets") }}{% endblock %}
{% block breadcrumb %}
{%- if target -%}
{{ crumbs([("Prometheus", "/plugins/prometheus/"), (target.name, "/plugins/prometheus/targets/" ~ target.id), ("Edit", "")]) }}
{%- else -%}
{{ crumbs([("Prometheus", "/plugins/prometheus/"), ("Add target", "")]) }}
{%- endif -%}
{% endblock %}
{% block content %}
{{ page_header(("Edit " ~ target.name) if target else "Add Prometheus target") }}
<div class="form-page">
{% if error %}<p class="field-error" role="alert">{{ error }}</p>{% endif %}
<form method="post" id="prom-form" action="{{ '/plugins/prometheus/targets/' ~ target.id ~ '/edit' if target else '/plugins/prometheus/targets/new' }}">{{ csrf_field() }}
{% if target %}<input type="hidden" name="target_id" value="{{ target.id }}">{% endif %}
<section class="card form-section" aria-labelledby="prom-where">
<h2 id="prom-where" class="section-title">Endpoint</h2>
<div class="form-pair">
<div class="field">
<label class="field-label" for="prom-name">Name</label>
<input class="input" id="prom-name" name="name" required maxlength="100" value="{{ v.name or '' }}" placeholder="Archives" autocomplete="off">
<span class="field-hint">How its series are named in Steward: alert rules and charts show it.</span>
</div>
<div class="field">
<label class="field-label" for="prom-url">Metrics address</label>
<input class="input mono" id="prom-url" name="url" required maxlength="500" value="{{ v.url or '' }}" placeholder="http://archives:8081/metrics" autocomplete="off">
</div>
<div class="field">
<label class="field-label" for="prom-token">Bearer token</label>
<input class="input" id="prom-token" name="token" type="password" autocomplete="new-password"
placeholder="{{ 'kept; type to change' if v.has_token else 'none' }}">
<span class="field-hint">Sent as "Authorization: Bearer …". Blank {{ "keeps the saved one" if v.has_token else "sends none" }}.</span>
{% if v.has_token %}
<label style="display:flex;gap:0.5rem;align-items:center;margin-top:0.3rem;cursor:pointer;">
<input type="checkbox" name="clear_token" value="on"> <span class="field-hint">Remove the saved token</span>
</label>
{% endif %}
</div>
<div class="field">
<label class="field-label" for="prom-interval">Scrape every (seconds)</label>
<input class="input" id="prom-interval" name="interval_seconds" type="number" min="10" max="3600" value="{{ v.interval_seconds or 60 }}">
</div>
</div>
<div style="display:flex;gap:1.5rem;flex-wrap:wrap;margin-bottom:0.9rem;">
<label style="display:flex;gap:0.5rem;align-items:center;cursor:pointer;">
<input type="checkbox" name="verify_tls" value="on"{% if v.verify_tls %} checked{% endif %}> Check its HTTPS certificate
</label>
</div>
<div style="display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap;">
<button type="button" class="btn btn-sm btn-ghost"
hx-post="/plugins/prometheus/targets/test" hx-include="#prom-form" hx-target="#prom-test"
hx-indicator="#prom-testing">Test</button>
<span id="prom-testing" class="field-hint htmx-indicator">Scraping…</span>
<span class="field-hint">Scrapes the address once with what's typed here, filters included, and says what would be stored. Nothing is saved.</span>
</div>
<div id="prom-test" aria-live="polite"></div>
</section>
<section class="card form-section" aria-labelledby="prom-what">
<h2 id="prom-what" class="section-title">What to keep</h2>
<p class="field-hint" style="margin-top:0;">The endpoint hands over everything; Steward stores what you choose. Patterns match the endpoint's metric names, with <span class="mono">*</span> for any run of characters.</p>
<div class="form-pair">
<div class="field">
<label class="field-label" for="prom-keep">Keep</label>
<textarea class="input mono" id="prom-keep" name="keep" rows="4" placeholder="fabledarchives_*">{{ v.keep or '' }}</textarea>
<span class="field-hint">One a line. Empty keeps everything.</span>
</div>
<div class="field">
<label class="field-label" for="prom-drop">Leave out</label>
<textarea class="input mono" id="prom-drop" name="drop" rows="4" placeholder="go_*&#10;process_*">{{ v.drop or '' }}</textarea>
<span class="field-hint">One a line. Wins over Keep.</span>
</div>
<div class="field">
<label class="field-label" for="prom-cap">Series cap</label>
<input class="input" id="prom-cap" name="series_cap" type="number" min="1" max="10000" value="{{ v.series_cap or 1000 }}">
<span class="field-hint">The most series stored from one scrape. Past it, the rest are counted and shown, not stored.</span>
</div>
</div>
<p class="field-hint" style="margin:0;">Gauges are stored as they are. Counters are stored as a rate per second, named <span class="mono">…_per_sec</span>, from the second scrape on. Histogram buckets and summary quantiles aren't stored; their <span class="mono">_sum</span> and <span class="mono">_count</span> are, as rates.</p>
</section>
<div class="form-actions">
<button type="submit" class="btn">{{ "Save" if target else "Add target" }}</button>
<a href="{{ '/plugins/prometheus/targets/' ~ target.id if target else '/plugins/prometheus/' }}" class="btn btn-ghost">Cancel</a>
</div>
</form>
{% if target %}
<section class="card danger-zone" aria-labelledby="prom-delete">
<div>
<h2 id="prom-delete" class="section-title">Delete this target</h2>
<p class="field-hint">Steward stops scraping it. Its past series age out with the rest of the history.</p>
</div>
<form method="post" action="/plugins/prometheus/targets/{{ target.id }}/delete"
data-confirm="Delete Prometheus target {{ target.name }}? Steward stops scraping it.">{{ csrf_field() }}
<button class="btn btn-danger" type="submit">Delete target</button>
</form>
</section>
{% endif %}
</div>
{% endblock %}