230b542015
- Rename package fablednetmon → fabledscryer throughout - Multi-dashboard: ownership, per-user defaults, HTMX edit (add/remove/reorder) - Read-only share tokens scoped to individual dashboards - Dashboard edit is HTMX-driven (no page reloads) - Plugin management system: remote catalog, download/install, hot-reload, in-app restart - plugin_index.py: fetch/cache remote index.yaml; default URL → bvandeusen/fabledscryer-plugins - plugin_manager.py: download_and_install_plugin, hot_reload_plugin, restart_app - ZIP extraction handles GitHub archive formats (name-v1.0.0/, name-main/) - Settings split into tabbed sections: General, Notifications, Ansible, Plugins - Plugins tab: catalog browser (HTMX), install/activate/update/restart actions - UI/branding: dark palette (#07071a), crystal ball SVG logo, animated star field, Libertinus Serif applied to headings, nav, labels, and section titles - Widget registry (core/widgets.py) for dashboard plugin integration - UPS widget.html (dashboard card) and settings/_tabs.html include - Migrations 0005–0008: dashboards, is_default, ownership, share tokens - docs/plugins/: writing-a-plugin.md updated with publishing guide, index.yaml.example template for fabledscryer-plugins repo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
65 lines
2.5 KiB
HTML
65 lines
2.5 KiB
HTML
{# HTMX fragment — included in both /ping/ page and dashboard widget #}
|
|
{% macro pill_bg(p) %}
|
|
{%- if p is none or p.status.value == 'down' or p.response_time_ms is none -%}
|
|
#6a1515
|
|
{%- elif p.response_time_ms <= good_ms -%}
|
|
#1a6632
|
|
{%- elif p.response_time_ms <= warn_ms -%}
|
|
#6b5c00
|
|
{%- else -%}
|
|
#7a3800
|
|
{%- endif -%}
|
|
{% endmacro %}
|
|
{% macro pill_title(p) %}
|
|
{%- if p is none -%}No data
|
|
{%- elif p.status.value == 'down' -%}Down — {{ p.probed_at.strftime('%H:%M:%S') }} UTC
|
|
{%- else -%}{{ "%.1f"|format(p.response_time_ms) }} ms — {{ p.probed_at.strftime('%H:%M:%S') }} UTC
|
|
{%- endif -%}
|
|
{% endmacro %}
|
|
{% if hosts %}
|
|
{% for host in hosts %}
|
|
{% set host_pings = pings_by_host.get(host.id, []) %}
|
|
{% set last = host_pings[-1] if host_pings else none %}
|
|
<div class="ping-row">
|
|
<div class="ping-meta">
|
|
<span class="ping-name">{{ host.name }}</span>
|
|
<span class="ping-addr">{{ host.address }}</span>
|
|
</div>
|
|
<div class="ping-pills">
|
|
{% for _ in range([30 - host_pings|length, 0]|max) %}
|
|
<span class="pill pill-empty" title="No data"></span>
|
|
{% endfor %}
|
|
{% for p in host_pings %}
|
|
<span class="pill" style="background:{{ pill_bg(p) }};" title="{{ pill_title(p) }}"></span>
|
|
{% endfor %}
|
|
</div>
|
|
<div class="ping-cur">
|
|
{% if last is none %}
|
|
<span class="ping-cur-nd">—</span>
|
|
{% elif last.status.value == 'down' or last.response_time_ms is none %}
|
|
<span class="ping-cur-down">DOWN</span>
|
|
{% elif last.response_time_ms <= good_ms %}
|
|
<span class="ping-cur-good">{{ "%.1f"|format(last.response_time_ms) }} ms</span>
|
|
{% elif last.response_time_ms <= warn_ms %}
|
|
<span class="ping-cur-warn">{{ "%.1f"|format(last.response_time_ms) }} ms</span>
|
|
{% else %}
|
|
<span class="ping-cur-bad">{{ "%.1f"|format(last.response_time_ms) }} ms</span>
|
|
{% endif %}
|
|
</div>
|
|
{% if uptime_pcts is defined %}
|
|
{% set pct = uptime_pcts.get(host.id) %}
|
|
<div style="min-width:4.5rem;text-align:right;font-size:0.78rem;font-variant-numeric:tabular-nums;color:
|
|
{% if pct is none %}var(--text-dim)
|
|
{% elif pct >= 99 %}var(--green)
|
|
{% elif pct >= 95 %}var(--yellow)
|
|
{% else %}var(--red){% endif %};">
|
|
{% if pct is not none %}{{ "%.1f"|format(pct) }}%{% else %}—{% endif %}
|
|
<span style="color:var(--text-dim);font-size:0.7rem;margin-left:0.15rem;">{{ range_key }}</span>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<p style="color:#505070;font-size:0.85rem;padding:0.5rem 0;">No ping-enabled hosts. <a href="/hosts/" style="color:#7070c0;">Add hosts →</a></p>
|
|
{% endif %}
|