This repository has been archived on 2026-06-01. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Roundtable-plugins/ups/templates/ups/rows.html
T
bvandeusen ad4fcd1cfd feat: initial release — traefik, unifi, ups plugins
First-party plugins for Fabled Scryer (https://github.com/bvandeusen/fabledscryer).

traefik: Prometheus metrics scraping, stats history, access log parsing (GeoIP optional)
unifi:   UniFi controller integration — WAN health, devices, clients, DPI, events
ups:     UPS monitoring via NUT (Network UPS Tools), Ansible shutdown automation

Each plugin follows the Fabled Scryer plugin contract:
  setup(app), get_scheduled_tasks(), get_blueprint()

index.yaml lists all three plugins in the catalog format. Populate
checksum_sha256 entries once release zips are published via GitHub Releases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 18:28:43 -04:00

186 lines
8.4 KiB
HTML

{# plugins/ups/templates/ups/rows.html #}
{# ── On-battery alert banner ──────────────────────────────────────────────── #}
{% if latest and latest.on_battery %}
<div style="background:var(--red-dim);border:1px solid var(--red);border-radius:6px;padding:0.75rem 1rem;margin-bottom:1.25rem;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:0.5rem;">
<span style="color:var(--red);font-weight:600;font-size:0.95rem;">
&#9888; On Battery Power
{% if on_battery_since %}
— {{ ((latest.scraped_at - on_battery_since).total_seconds() / 60) | int }}m elapsed
{% endif %}
</span>
{% if shutdown_triggered %}
<span style="color:var(--red);font-size:0.85rem;font-weight:600;">Shutdown sequence triggered</span>
{% elif latest.low_battery %}
<span style="color:var(--red);font-size:0.85rem;">Low battery — shutdown imminent</span>
{% endif %}
</div>
{% endif %}
{% if not latest %}
<div class="card">
<p style="color:var(--text-muted);">No UPS data yet. Check <code>nut_host</code>, <code>nut_port</code>, and <code>ups_name</code> in plugin settings.</p>
</div>
{% else %}
<div style="column-width:340px;column-count:3;column-gap:1rem;">
{# ── Current status card ──────────────────────────────────────────────── #}
<div class="card" style="break-inside:avoid;margin-bottom:1rem;">
<div class="section-title" style="margin-bottom:0.75rem;">
Status
{% if latest.manufacturer or latest.model %}
<span style="float:right;font-weight:normal;font-size:0.78rem;color:var(--text-dim);">
{{ latest.manufacturer or "" }} {{ latest.model or "" }}
</span>
{% endif %}
</div>
{# Status pill #}
<div style="margin-bottom:0.75rem;">
<span style="padding:0.25rem 0.7rem;border-radius:4px;font-size:0.88rem;font-weight:600;
{% if latest.on_battery %}background:var(--red-dim);color:var(--red);
{% else %}background:var(--green-dim);color:var(--green);{% endif %}">
{% if latest.on_battery %}On Battery{% else %}Online{% endif %}
</span>
{% if latest.low_battery %}
<span style="margin-left:0.5rem;padding:0.2rem 0.6rem;border-radius:4px;font-size:0.8rem;background:var(--red-dim);color:var(--red);">Low Battery</span>
{% endif %}
<span style="margin-left:0.5rem;color:var(--text-dim);font-size:0.78rem;font-family:monospace;">{{ latest.raw_status }}</span>
</div>
{# Battery charge bar #}
{% if latest.battery_charge_pct is not none %}
<div style="margin-bottom:0.6rem;">
<div style="display:flex;justify-content:space-between;font-size:0.8rem;margin-bottom:0.25rem;">
<span style="color:var(--text-muted);">Battery</span>
<span style="font-weight:600;color:
{% if latest.battery_charge_pct >= 80 %}var(--green)
{% elif latest.battery_charge_pct >= 40 %}var(--yellow)
{% else %}var(--red){% endif %};">
{{ "%.0f"|format(latest.battery_charge_pct) }}%
</span>
</div>
<div style="background:var(--border);border-radius:3px;height:8px;overflow:hidden;">
<div style="height:100%;border-radius:3px;width:{{ "%.1f"|format(latest.battery_charge_pct) }}%;background:
{% if latest.battery_charge_pct >= 80 %}var(--green)
{% elif latest.battery_charge_pct >= 40 %}var(--yellow)
{% else %}var(--red){% endif %};transition:width 0.3s;"></div>
</div>
</div>
{% endif %}
{# Key metrics #}
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;font-size:0.83rem;">
{% if latest.battery_runtime_secs is not none %}
<div>
<div style="color:var(--text-muted);font-size:0.75rem;">Runtime</div>
<div style="font-weight:600;">
{% set mins = latest.battery_runtime_secs // 60 %}
{% if mins >= 60 %}{{ mins // 60 }}h {{ mins % 60 }}m
{% else %}{{ mins }}m{% endif %}
</div>
</div>
{% endif %}
{% if latest.load_pct is not none %}
<div>
<div style="color:var(--text-muted);font-size:0.75rem;">Load</div>
<div style="font-weight:600;color:
{% if latest.load_pct >= 80 %}var(--red)
{% elif latest.load_pct >= 60 %}var(--yellow)
{% else %}var(--text){% endif %};">
{{ "%.0f"|format(latest.load_pct) }}%
</div>
</div>
{% endif %}
{% if latest.input_voltage is not none %}
<div>
<div style="color:var(--text-muted);font-size:0.75rem;">Input V</div>
<div>{{ "%.1f"|format(latest.input_voltage) }}V</div>
</div>
{% endif %}
{% if latest.output_voltage is not none %}
<div>
<div style="color:var(--text-muted);font-size:0.75rem;">Output V</div>
<div>{{ "%.1f"|format(latest.output_voltage) }}V</div>
</div>
{% endif %}
{% if latest.battery_voltage is not none %}
<div>
<div style="color:var(--text-muted);font-size:0.75rem;">Batt V</div>
<div>{{ "%.1f"|format(latest.battery_voltage) }}V</div>
</div>
{% endif %}
{% if latest.temperature is not none %}
<div>
<div style="color:var(--text-muted);font-size:0.75rem;">Temp</div>
<div>{{ "%.1f"|format(latest.temperature) }}°C</div>
</div>
{% endif %}
</div>
<div style="margin-top:0.5rem;color:var(--text-dim);font-size:0.74rem;">
Last updated {{ latest.scraped_at.strftime("%H:%M:%S") }} UTC
</div>
</div>
{# ── Trend charts ─────────────────────────────────────────────────────── #}
{% if history %}
<div class="card" style="break-inside:avoid;margin-bottom:1rem;">
<div class="section-title" style="margin-bottom:0.75rem;">
Trends
<span style="float:right;font-weight:normal;font-size:0.78rem;color:var(--text-dim);">{{ range_key }}</span>
</div>
<div style="display:flex;flex-direction:column;gap:0.75rem;font-size:0.82rem;">
<div>
<div style="display:flex;justify-content:space-between;color:var(--text-muted);font-size:0.75rem;margin-bottom:0.2rem;">
<span>Battery %</span>
{% if latest.battery_charge_pct is not none %}<span>{{ "%.0f"|format(latest.battery_charge_pct) }}%</span>{% endif %}
</div>
{{ sparkline_charge | safe }}
</div>
<div>
<div style="display:flex;justify-content:space-between;color:var(--text-muted);font-size:0.75rem;margin-bottom:0.2rem;">
<span>Load %</span>
{% if latest.load_pct is not none %}<span>{{ "%.0f"|format(latest.load_pct) }}%</span>{% endif %}
</div>
{{ sparkline_load | safe }}
</div>
<div>
<div style="display:flex;justify-content:space-between;color:var(--text-muted);font-size:0.75rem;margin-bottom:0.2rem;">
<span>Input Voltage</span>
{% if latest.input_voltage is not none %}<span>{{ "%.1f"|format(latest.input_voltage) }}V</span>{% endif %}
</div>
{{ sparkline_voltage | safe }}
</div>
</div>
</div>
{% endif %}
{# ── Battery event log ────────────────────────────────────────────────── #}
{% if battery_events %}
<div class="card" style="break-inside:avoid;margin-bottom:1rem;">
<div class="section-title" style="margin-bottom:0.75rem;">
Battery Events
<span style="float:right;font-weight:normal;font-size:0.78rem;color:var(--text-dim);">{{ range_key }}</span>
</div>
<table class="table" style="font-size:0.81rem;width:100%;">
{% for ev in battery_events %}
<tr>
<td style="padding:0.2rem 0.5rem 0.2rem 0;white-space:nowrap;color:var(--text-dim);font-size:0.76rem;">
{{ ev.scraped_at.strftime("%m-%d %H:%M") }}
</td>
<td style="padding:0.2rem 0.3rem;">
<span style="color:var(--red);font-size:0.78rem;">{{ ev.raw_status }}</span>
</td>
<td style="padding:0.2rem 0;text-align:right;color:var(--text-muted);font-size:0.76rem;">
{% if ev.battery_charge_pct is not none %}{{ "%.0f"|format(ev.battery_charge_pct) }}%{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
</div>
{% endif %}