a7a281cb11
First-party plugins (host_agent, http, snmp, traefik, unifi, docker) are now tracked under plugins/ and baked into the image, so they version atomically with core — ending the cross-repo import drift the roundtable->steward rename exposed. History for these files is preserved in the archived Roundtable-plugins repo. Plugin discovery becomes multi-root: PLUGIN_DIR (single) -> PLUGIN_DIRS (bundled first, then external) + PLUGIN_INSTALL_DIR. Bundled ships in the image; third-party plugins still mount at runtime into the external root (STEWARD_PLUGIN_DIR, default /data/plugins) and downloads/installs land there. Bundled shadows external on a name collision. - config.py: load_bootstrap returns plugin_dirs + plugin_install_dir - app.py: iterate PLUGIN_DIRS at the migration + load sites - migration_runner.py: discover_all_in() unions every plugin root - plugin_manager.py: resolve_plugin_path() (pure, first-root-wins); load / install / hot-reload span all roots; installs target the external root - settings/routes.py: _discover_plugins scans all roots, dedup bundled-first - Dockerfile: COPY plugins/ ; docker-compose: drop host bind, document external - tests/test_plugin_dirs.py: resolution, multi-root discovery, bootstrap split Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
128 lines
5.6 KiB
HTML
128 lines
5.6 KiB
HTML
{# plugins/traefik/templates/traefik/rows.html #}
|
|
|
|
{# ── Global stats bar ──────────────────────────────────────────────────────── #}
|
|
{% if global_stat %}
|
|
<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;">
|
|
|
|
{% if global_stat.open_conns_total is not none %}
|
|
<span>
|
|
<span style="color:var(--text-muted);">Open conns</span>
|
|
<span style="margin-left:0.4rem;font-weight:600;">{{ global_stat.open_conns_total | int }}</span>
|
|
</span>
|
|
{% endif %}
|
|
|
|
{% if global_stat.config_last_reload_success is not none %}
|
|
<span>
|
|
<span style="color:var(--text-muted);">Config</span>
|
|
{% if global_stat.config_last_reload_success == 1.0 %}
|
|
<span style="margin-left:0.4rem;color:var(--green);">✓ OK</span>
|
|
{% else %}
|
|
<span style="margin-left:0.4rem;color:var(--red);">✗ failed</span>
|
|
{% endif %}
|
|
{% if global_stat.config_reloads_total is not none %}
|
|
<span style="color:var(--text-dim);margin-left:0.3rem;">({{ global_stat.config_reloads_total | int }} reloads)</span>
|
|
{% endif %}
|
|
</span>
|
|
{% endif %}
|
|
|
|
{% if global_stat.process_memory_bytes is not none %}
|
|
<span>
|
|
<span style="color:var(--text-muted);">Memory</span>
|
|
{% set mb = (global_stat.process_memory_bytes / 1048576) %}
|
|
<span style="margin-left:0.4rem;">
|
|
{% if mb >= 1024 %}{{ "%.1f"|format(mb / 1024) }} GB{% else %}{{ "%.0f"|format(mb) }} MB{% endif %}
|
|
</span>
|
|
</span>
|
|
{% endif %}
|
|
|
|
<span style="margin-left:auto;color:var(--text-dim);font-size:0.78rem;">
|
|
{{ global_stat.scraped_at.strftime("%H:%M:%S") }} UTC
|
|
</span>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{# ── Per-service cards + cert card in shared columns layout ───────────────── #}
|
|
{% if router_data or certs %}
|
|
<div style="column-width:340px;column-count:3;column-gap:1rem;">
|
|
|
|
{% for r in router_data %}
|
|
<div class="card" style="break-inside:avoid;margin-bottom:1rem;">
|
|
<h3 class="section-title" style="margin-bottom:0.75rem;word-break:break-all;">{{ r.name }}</h3>
|
|
<table class="table" style="font-size:0.85rem;">
|
|
<tr>
|
|
<td style="color:var(--text-muted);padding:0.2rem 0;">Req/s</td>
|
|
<td style="padding:0.2rem 0.5rem;">{{ "%.2f"|format(r.latest.request_rate) }}</td>
|
|
<td style="padding:0.2rem 0;">{{ r.sparkline_req | safe }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="color:var(--text-muted);padding:0.2rem 0;">Bandwidth</td>
|
|
<td style="padding:0.2rem 0.5rem;">
|
|
{% set bps = r.latest.response_bytes_rate %}
|
|
{% if bps >= 1048576 %}{{ "%.1f"|format(bps / 1048576) }} MB/s
|
|
{% elif bps >= 1024 %}{{ "%.1f"|format(bps / 1024) }} KB/s
|
|
{% else %}{{ "%.0f"|format(bps) }} B/s{% endif %}
|
|
</td>
|
|
<td style="padding:0.2rem 0;"></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="color:var(--text-muted);padding:0.2rem 0;">p95 ms</td>
|
|
<td style="padding:0.2rem 0.5rem;">{{ "%.1f"|format(r.latest.latency_p95_ms) }}</td>
|
|
<td style="padding:0.2rem 0;">{{ r.sparkline_p95 | safe }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="color:var(--text-muted);padding:0.2rem 0;">p99 ms</td>
|
|
<td style="padding:0.2rem 0.5rem;">{{ "%.1f"|format(r.latest.latency_p99_ms) }}</td>
|
|
<td style="padding:0.2rem 0;"></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="color:var(--text-muted);padding:0.2rem 0;">4xx %</td>
|
|
<td style="color:{% if r.latest.error_rate_4xx_pct > 1 %}var(--yellow){% else %}var(--green){% endif %};padding:0.2rem 0.5rem;">
|
|
{{ "%.1f"|format(r.latest.error_rate_4xx_pct) }}%
|
|
</td>
|
|
<td style="padding:0.2rem 0;"></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="color:var(--text-muted);padding:0.2rem 0;">5xx %</td>
|
|
<td style="color:{% if r.latest.error_rate_5xx_pct > 0.1 %}var(--red){% else %}var(--green){% endif %};padding:0.2rem 0.5rem;">
|
|
{{ "%.1f"|format(r.latest.error_rate_5xx_pct) }}%
|
|
</td>
|
|
<td style="padding:0.2rem 0;">{{ r.sparkline_5xx | safe }}</td>
|
|
</tr>
|
|
</table>
|
|
<div style="color:var(--text-dim);font-size:0.75rem;margin-top:0.5rem;">
|
|
Last scraped {{ r.latest.scraped_at.strftime("%H:%M:%S") }} UTC
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
{% if certs %}
|
|
<div class="card" style="break-inside:avoid;margin-bottom:1rem;">
|
|
<div class="section-title" style="margin-bottom:0.75rem;">TLS Certificates</div>
|
|
<table class="table" style="font-size:0.83rem;width:100%;">
|
|
{% for cert in certs %}
|
|
<tr>
|
|
<td style="padding:0.2rem 0 0.2rem 0;word-break:break-all;">{{ cert.cn }}</td>
|
|
<td style="text-align:right;padding:0.2rem 0;white-space:nowrap;font-weight:600;
|
|
{% if cert.days_remaining < 7 %}color:var(--red);
|
|
{% elif cert.days_remaining < 30 %}color:var(--yellow);
|
|
{% else %}color:var(--green);{% endif %}">
|
|
{{ cert.days_remaining | int }}d
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td colspan="2" style="padding:0 0 0.4rem 0;color:var(--text-dim);font-size:0.75rem;">
|
|
{{ cert.not_after.strftime("%Y-%m-%d") }}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
</div>
|
|
{% endif %}
|
|
|
|
</div>
|
|
{% else %}
|
|
<div class="card">
|
|
<p style="color:#606080;">No Traefik metrics yet. Configure <code>metrics_url</code> in <code>config.yaml</code> under <code>plugins.traefik</code>.</p>
|
|
</div>
|
|
{% endif %}
|