ad4fcd1cfd
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>
204 lines
9.4 KiB
HTML
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 %}✓ Up{% else %}✗ 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);">↓ 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);">↑ 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 }}↗ wireless
|
|
{{ latest_clients.wired_clients }}↔ wired
|
|
{% if latest_clients.guest_clients %} {{ 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 %};">●</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);">↓</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;">↑</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;">↓ 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;">↑ 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 %}
|