feat(homeassistant): its page offers the metrics scrape under the same sign-in, so one user covers everything (#6744)
A Prometheus target's token_from names the plugin that supplies its token at each scrape. Read its metrics makes the target; Use this sign-in moves one at that address across, keeping its name, filters and history. A refusal stops the connection, so one Try again or sign-in starts everything, and the sign-in is only sent to its own address. A 404 says how to turn on Home Assistant's Prometheus integration, as Steward doesn't change Home Assistant. Refused also joins the volatile columns a backup check ignores. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,7 @@ from steward.core.crypto import encrypt_secret
|
||||
from steward.core.settings import public_base_url
|
||||
from steward.models.users import UserRole
|
||||
|
||||
from . import client
|
||||
from . import client, metrics
|
||||
from .models import HomeAssistantConnection, HomeAssistantUpdate
|
||||
from .service import STALE_BACKUP_HOURS, backup_age_hours, backup_verdict, installable, parse_moment
|
||||
from .updates import CannotStart, start_update
|
||||
@@ -55,6 +55,7 @@ async def _render_index(error: str | None = None, status: int = 200):
|
||||
connection = await _connection(db)
|
||||
runs = list((await db.execute(select(HomeAssistantUpdate).order_by(
|
||||
HomeAssistantUpdate.started_at.desc()).limit(RECENT_UPDATES))).scalars())
|
||||
scrape = await metrics.state(db, connection) if connection else None
|
||||
if connection is None:
|
||||
return await render_template("homeassistant/index.html", connection=None, error=error), status
|
||||
now = datetime.now(timezone.utc)
|
||||
@@ -63,7 +64,7 @@ async def _render_index(error: str | None = None, status: int = 200):
|
||||
backup = connection.backup or {}
|
||||
tone, backup_says = backup_verdict(backup, now)
|
||||
return await render_template(
|
||||
"homeassistant/index.html", connection=connection, error=error, reach=reach, says=says,
|
||||
"homeassistant/index.html", connection=connection, error=error, reach=reach, says=says, metrics=scrape,
|
||||
installable=installable(connection),
|
||||
others=[u for u in updates if u["waiting"] and not u["kind"]],
|
||||
current=[u for u in updates if not u["waiting"] and u["kind"]],
|
||||
@@ -95,6 +96,26 @@ async def install():
|
||||
return redirect("/plugins/homeassistant/")
|
||||
|
||||
|
||||
@homeassistant_bp.post("/metrics")
|
||||
@require_role(UserRole.admin)
|
||||
async def connect_metrics():
|
||||
"""Read Home Assistant's metrics with this sign-in, after asking for them once
|
||||
to show they're there. An admin's explicit action, so it may ask."""
|
||||
async with current_app.db_sessionmaker() as db:
|
||||
connection = await _connection(db)
|
||||
if connection is None:
|
||||
return redirect("/plugins/homeassistant/")
|
||||
try:
|
||||
await metrics.try_reading(current_app, connection)
|
||||
target_id, name, created = await metrics.use_sign_in(current_app, connection)
|
||||
except metrics.CannotRead as exc:
|
||||
return await _render_index(f"Nothing changed: {exc}", 409)
|
||||
await log_audit(current_app, *_actor(), "prometheus.target_created" if created else "prometheus.target_updated",
|
||||
entity_type="prometheus_target", entity_id=target_id,
|
||||
detail={"name": name, "token_from": metrics.SOURCE})
|
||||
return redirect("/plugins/homeassistant/")
|
||||
|
||||
|
||||
# ── The connection ───────────────────────────────────────────────────────────
|
||||
|
||||
async def _render_form(connection, url: str | None = None, verify_tls: bool | None = None,
|
||||
@@ -167,6 +188,7 @@ async def sign_in_callback():
|
||||
connection.can_install, connection.signed_in_by = can_install, username
|
||||
connection.signed_in_at = datetime.now(timezone.utc)
|
||||
connection.refused, connection.last_error, connection.last_polled_at = False, None, None
|
||||
await metrics.follow_sign_in(db, connection)
|
||||
await db.flush()
|
||||
connection_id = connection.id
|
||||
if previous is not None:
|
||||
@@ -192,7 +214,9 @@ async def _end_sign_in(connection) -> bool:
|
||||
@homeassistant_bp.post("/connection/retry")
|
||||
@require_role(UserRole.admin)
|
||||
async def retry_connection():
|
||||
"""Ask again after a refusal, once a ban has been dealt with."""
|
||||
"""Ask again after a refusal, once a ban has been dealt with. A metrics scrape
|
||||
using this sign-in starts again with it."""
|
||||
client.forget_access()
|
||||
async with current_app.db_sessionmaker() as db:
|
||||
async with db.begin():
|
||||
connection = await _connection(db)
|
||||
|
||||
Reference in New Issue
Block a user