diff --git a/plugins/traefik/routes.py b/plugins/traefik/routes.py new file mode 100644 index 0000000..07db47a --- /dev/null +++ b/plugins/traefik/routes.py @@ -0,0 +1,70 @@ +# plugins/traefik/routes.py +from __future__ import annotations +from quart import Blueprint, current_app, render_template +from sqlalchemy import select + +from fablednetmon.auth.middleware import require_role +from fablednetmon.models.users import UserRole +from plugins.traefik.models import TraefikMetric + +traefik_bp = Blueprint("traefik", __name__, template_folder="templates") + + +def _sparkline(values: list[float], width: int = 80, height: int = 20) -> str: + """Generate an inline SVG sparkline from a list of float values.""" + if len(values) < 2: + return f'' + mn, mx = min(values), max(values) + if mx == mn: + mx = mn + 1.0 + step = width / (len(values) - 1) + pts = [] + for i, v in enumerate(values): + x = i * step + y = height - (v - mn) / (mx - mn) * (height - 2) - 1 + pts.append(f"{x:.1f},{y:.1f}") + poly = " ".join(pts) + return ( + f'' + ) + + +@traefik_bp.get("/") +@require_role(UserRole.viewer) +async def index(): + # Last 20 data points per router for sparklines + history_limit = 20 + + async with current_app.db_sessionmaker() as db: + # Get distinct router names with a recent row + result = await db.execute( + select(TraefikMetric.router_name) + .distinct() + .order_by(TraefikMetric.router_name) + ) + routers = [row[0] for row in result.all()] + + router_data = [] + for router in routers: + result = await db.execute( + select(TraefikMetric) + .where(TraefikMetric.router_name == router) + .order_by(TraefikMetric.scraped_at.desc()) + .limit(history_limit) + ) + rows = list(reversed(result.scalars().all())) + if not rows: + continue + latest = rows[-1] + router_data.append({ + "name": router, + "latest": latest, + "sparkline_req": _sparkline([r.request_rate for r in rows]), + "sparkline_p95": _sparkline([r.latency_p95_ms for r in rows]), + "sparkline_5xx": _sparkline([r.error_rate_5xx_pct for r in rows]), + }) + + return await render_template("traefik/index.html", router_data=router_data) diff --git a/plugins/traefik/templates/traefik/index.html b/plugins/traefik/templates/traefik/index.html new file mode 100644 index 0000000..0d3c875 --- /dev/null +++ b/plugins/traefik/templates/traefik/index.html @@ -0,0 +1,53 @@ +{# plugins/traefik/templates/traefik/index.html #} +{% extends "base.html" %} +{% block title %}Traefik — FabledNetMon{% endblock %} +{% block content %} +
No Traefik metrics yet. Configure metrics_url in config.yaml under plugins.traefik.
| Req/s | +{{ "%.2f"|format(r.latest.request_rate) }} | +{{ r.sparkline_req | safe }} | +
| p95 ms | +{{ "%.1f"|format(r.latest.latency_p95_ms) }} | +{{ r.sparkline_p95 | safe }} | +
| p99 ms | +{{ "%.1f"|format(r.latest.latency_p99_ms) }} | ++ |
| 4xx % | ++ {{ "%.1f"|format(r.latest.error_rate_4xx_pct) }}% + | ++ |
| 5xx % | ++ {{ "%.1f"|format(r.latest.error_rate_5xx_pct) }}% + | +{{ r.sparkline_5xx | safe }} | +