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
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

204 lines
9.4 KiB
HTML

{# plugins/unifi/templates/unifi/rows.html #}
{# ── WAN health bar ───────────────────────────────────────────────────────── #}
{% if latest_wan %}
<div style="display:flex;flex-wrap:wrap;gap:1.5rem;margin-bottom:1.25rem;padding:0.75rem 1rem;background:var(--card-bg);border:1px solid var(--border);border-radius:6px;font-size:0.85rem;font-variant-numeric:tabular-nums;align-items:center;">
<span>
<span style="color:var(--text-muted);">WAN</span>
<span style="margin-left:0.4rem;font-weight:600;color:{% if latest_wan.is_up %}var(--green){% else %}var(--red){% endif %};">
{% if latest_wan.is_up %}&#10003; Up{% else %}&#10007; Down{% endif %}
</span>
{% if latest_wan.wan_ip %}
<span style="color:var(--text-dim);margin-left:0.4rem;font-family:monospace;font-size:0.8rem;">{{ latest_wan.wan_ip }}</span>
{% endif %}
</span>
{% if latest_wan.latency_ms is not none %}
<span>
<span style="color:var(--text-muted);">Latency</span>
<span style="margin-left:0.4rem;
{% if latest_wan.latency_ms > 100 %}color:var(--red){% elif latest_wan.latency_ms > 50 %}color:var(--yellow){% else %}color:var(--green){% endif %};">
{{ "%.1f"|format(latest_wan.latency_ms) }}ms
</span>
<span style="margin-left:0.3rem;">{{ sparkline_latency | safe }}</span>
</span>
{% endif %}
{% if latest_wan.rx_bytes_rate is not none %}
<span>
<span style="color:var(--text-muted);">&#8595; RX</span>
<span style="margin-left:0.3rem;">
{% set bps = latest_wan.rx_bytes_rate %}
{% if bps >= 1048576 %}{{ "%.1f"|format(bps / 1048576) }} MB/s
{% elif bps >= 1024 %}{{ "%.0f"|format(bps / 1024) }} KB/s
{% else %}{{ "%.0f"|format(bps) }} B/s{% endif %}
</span>
<span style="margin-left:0.3rem;">{{ sparkline_rx | safe }}</span>
</span>
<span>
<span style="color:var(--text-muted);">&#8593; TX</span>
<span style="margin-left:0.3rem;">
{% set bps = latest_wan.tx_bytes_rate %}
{% if bps >= 1048576 %}{{ "%.1f"|format(bps / 1048576) }} MB/s
{% elif bps >= 1024 %}{{ "%.0f"|format(bps / 1024) }} KB/s
{% else %}{{ "%.0f"|format(bps) }} B/s{% endif %}
</span>
<span style="margin-left:0.3rem;">{{ sparkline_tx | safe }}</span>
</span>
{% endif %}
{% if latest_wan.uptime_seconds is not none %}
<span style="color:var(--text-dim);font-size:0.8rem;">
up {{ (latest_wan.uptime_seconds // 3600) }}h {{ ((latest_wan.uptime_seconds % 3600) // 60) }}m
</span>
{% endif %}
</div>
{% endif %}
{# ── Content columns ──────────────────────────────────────────────────────── #}
<div style="column-width:340px;column-count:3;column-gap:1rem;">
{# Client summary card #}
{% if latest_clients %}
<div class="card" style="break-inside:avoid;margin-bottom:1rem;">
<div class="section-title" style="margin-bottom:0.75rem;">
Clients
<span style="float:right;font-weight:normal;color:var(--text-muted);font-size:0.82rem;">{{ sparkline_clients | safe }}</span>
</div>
<div style="display:flex;gap:1.5rem;font-size:0.9rem;margin-bottom:0.75rem;font-variant-numeric:tabular-nums;">
<span>
<span style="font-size:1.4rem;font-weight:700;color:var(--text);">{{ latest_clients.total_clients }}</span>
<span style="color:var(--text-muted);font-size:0.8rem;margin-left:0.2rem;">total</span>
</span>
<span style="color:var(--text-muted);font-size:0.85rem;align-self:flex-end;">
{{ latest_clients.wireless_clients }}&#x2197; wireless &nbsp;
{{ latest_clients.wired_clients }}&#x2194; wired
{% if latest_clients.guest_clients %}&nbsp; {{ latest_clients.guest_clients }} guest{% endif %}
</span>
</div>
</div>
{% endif %}
{# Infrastructure devices card #}
{% if devices %}
<div class="card" style="break-inside:avoid;margin-bottom:1rem;">
<div class="section-title" style="margin-bottom:0.75rem;">Infrastructure Devices</div>
<table class="table" style="font-size:0.83rem;width:100%;">
{% for d in devices %}
<tr>
<td style="padding:0.2rem 0.5rem 0.2rem 0;">
<span style="color:{% if d.state == 1 %}var(--green){% else %}var(--red){% endif %};">&#9679;</span>
<span style="margin-left:0.3rem;">{{ d.name or d.mac }}</span>
</td>
<td style="padding:0.2rem 0.3rem;color:var(--text-muted);font-size:0.78rem;">{{ d.model or d.device_type or "" }}</td>
<td style="text-align:right;padding:0.2rem 0;color:var(--text-dim);font-size:0.78rem;font-family:monospace;">{{ d.ip or "" }}</td>
</tr>
{% if d.state == 1 and d.uptime_seconds %}
<tr>
<td colspan="3" style="padding:0 0 0.3rem 1.2rem;color:var(--text-dim);font-size:0.75rem;">
up {{ (d.uptime_seconds // 86400) }}d {{ ((d.uptime_seconds % 86400) // 3600) }}h
</td>
</tr>
{% endif %}
{% endfor %}
</table>
</div>
{% endif %}
{# Top talkers card #}
{% if top_clients %}
<div class="card" style="break-inside:avoid;margin-bottom:1rem;">
<div class="section-title" style="margin-bottom:0.75rem;">Top Talkers</div>
<table class="table" style="font-size:0.82rem;width:100%;">
{% for c in top_clients %}
<tr>
<td style="padding:0.2rem 0.5rem 0.2rem 0;word-break:break-all;">
{{ c.hostname }}
{% if c.type == "wireless" and c.signal %}
<span style="color:var(--text-dim);font-size:0.75rem;"> {{ c.signal }}dBm</span>
{% endif %}
</td>
<td style="text-align:right;padding:0.2rem 0;white-space:nowrap;font-size:0.78rem;">
{% set rx = c.rx_rate %}{% set tx = c.tx_rate %}
<span style="color:var(--text-muted);">&#8595;</span>
{% if rx >= 1048576 %}{{ "%.1f"|format(rx/1048576) }}M
{% elif rx >= 1024 %}{{ "%.0f"|format(rx/1024) }}K
{% else %}{{ "%.0f"|format(rx) }}B{% endif %}
<span style="color:var(--text-muted);margin-left:0.3rem;">&#8593;</span>
{% if tx >= 1048576 %}{{ "%.1f"|format(tx/1048576) }}M
{% elif tx >= 1024 %}{{ "%.0f"|format(tx/1024) }}K
{% else %}{{ "%.0f"|format(tx) }}B{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{# WLAN SSIDs card #}
{% if wlan_latest %}
<div class="card" style="break-inside:avoid;margin-bottom:1rem;">
<div class="section-title" style="margin-bottom:0.75rem;">Wireless Networks</div>
<table class="table" style="font-size:0.83rem;width:100%;">
<tr style="color:var(--text-muted);font-size:0.78rem;">
<th style="text-align:left;font-weight:normal;padding:0 0.5rem 0.3rem 0;">SSID</th>
<th style="text-align:right;font-weight:normal;padding:0 0.3rem 0.3rem;">Clients</th>
<th style="text-align:right;font-weight:normal;padding:0 0 0.3rem;">Score</th>
</tr>
{% for w in wlan_latest %}
<tr>
<td style="padding:0.2rem 0.5rem 0.2rem 0;">{{ w.ssid }}</td>
<td style="text-align:right;padding:0.2rem 0.3rem;">{{ w.num_sta }}</td>
<td style="text-align:right;padding:0.2rem 0;
{% if w.satisfaction is not none %}
{% if w.satisfaction >= 80 %}color:var(--green){% elif w.satisfaction >= 60 %}color:var(--yellow){% else %}color:var(--red){% endif %}
{% endif %};">
{% if w.satisfaction is not none %}{{ w.satisfaction }}%{% else %}—{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
{# Speedtest card #}
{% if speedtest %}
<div class="card" style="break-inside:avoid;margin-bottom:1rem;">
<div class="section-title" style="margin-bottom:0.75rem;">Last Speed Test</div>
<div style="display:flex;gap:1.5rem;font-size:0.88rem;font-variant-numeric:tabular-nums;">
{% if speedtest.download_mbps is not none %}
<span>
<span style="color:var(--text-muted);font-size:0.8rem;">&#8595; DL</span>
<span style="font-weight:600;margin-left:0.3rem;">{{ "%.1f"|format(speedtest.download_mbps) }}</span>
<span style="color:var(--text-muted);font-size:0.78rem;">Mbps</span>
</span>
{% endif %}
{% if speedtest.upload_mbps is not none %}
<span>
<span style="color:var(--text-muted);font-size:0.8rem;">&#8593; UL</span>
<span style="font-weight:600;margin-left:0.3rem;">{{ "%.1f"|format(speedtest.upload_mbps) }}</span>
<span style="color:var(--text-muted);font-size:0.78rem;">Mbps</span>
</span>
{% endif %}
{% if speedtest.latency_ms is not none %}
<span>
<span style="color:var(--text-muted);font-size:0.8rem;">Latency</span>
<span style="margin-left:0.3rem;">{{ "%.0f"|format(speedtest.latency_ms) }}ms</span>
</span>
{% endif %}
</div>
{% if speedtest.server_name %}
<div style="margin-top:0.4rem;color:var(--text-dim);font-size:0.78rem;">via {{ speedtest.server_name }}</div>
{% endif %}
<div style="margin-top:0.25rem;color:var(--text-dim);font-size:0.75rem;">{{ speedtest.run_at.strftime("%Y-%m-%d %H:%M") }} UTC</div>
</div>
{% endif %}
</div>
{% if not latest_wan and not devices %}
<div class="card">
<p style="color:var(--text-muted);">No UniFi data yet. Check your <code>host</code>, <code>username</code>, and <code>password</code> in the plugin config.</p>
</div>
{% endif %}