diff --git a/fablednetmon/app.py b/fablednetmon/app.py index ca2770b..9007194 100644 --- a/fablednetmon/app.py +++ b/fablednetmon/app.py @@ -87,6 +87,7 @@ def create_app( from .dashboard.routes import dashboard_bp from .hosts.routes import hosts_bp from .ping.routes import ping_bp + from .dns.routes import dns_bp from .alerts.routes import alerts_bp from .ansible.routes import ansible_bp from .settings.routes import settings_bp @@ -95,6 +96,7 @@ def create_app( app.register_blueprint(dashboard_bp) app.register_blueprint(hosts_bp) app.register_blueprint(ping_bp) + app.register_blueprint(dns_bp) app.register_blueprint(alerts_bp) app.register_blueprint(ansible_bp) app.register_blueprint(settings_bp) diff --git a/fablednetmon/dashboard/routes.py b/fablednetmon/dashboard/routes.py index 95b5472..dc1cd63 100644 --- a/fablednetmon/dashboard/routes.py +++ b/fablednetmon/dashboard/routes.py @@ -16,8 +16,7 @@ async def index(): # Latest ping per host latest_ping_subq = ( select(PingResult.host_id, func.max(PingResult.probed_at).label("max_at")) - .group_by(PingResult.host_id) - .subquery() + .group_by(PingResult.host_id).subquery() ) pr = await db.execute( select(PingResult).join( @@ -33,8 +32,7 @@ async def index(): # Latest DNS per host latest_dns_subq = ( select(DnsResult.host_id, func.max(DnsResult.resolved_at).label("max_at")) - .group_by(DnsResult.host_id) - .subquery() + .group_by(DnsResult.host_id).subquery() ) dr = await db.execute( select(DnsResult).join( @@ -49,14 +47,15 @@ async def index(): total_hosts = (await db.execute(select(func.count()).select_from(Host))).scalar() + plugins = current_app.config.get("PLUGINS", {}) + traefik_enabled = plugins.get("traefik", {}).get("enabled", False) poll_interval = current_app.config.get("MONITORS_POLL_INTERVAL", 60) return await render_template( "dashboard/index.html", - ping_up=ping_up, - ping_down=ping_down, - dns_ok=dns_ok, - dns_fail=dns_fail, + ping_up=ping_up, ping_down=ping_down, + dns_ok=dns_ok, dns_fail=dns_fail, total_hosts=total_hosts, + traefik_enabled=traefik_enabled, poll_interval=poll_interval, ) diff --git a/fablednetmon/dns/__init__.py b/fablednetmon/dns/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fablednetmon/dns/routes.py b/fablednetmon/dns/routes.py new file mode 100644 index 0000000..8fbcd25 --- /dev/null +++ b/fablednetmon/dns/routes.py @@ -0,0 +1,52 @@ +from __future__ import annotations +from quart import Blueprint, current_app, render_template +from sqlalchemy import select, func +from fablednetmon.auth.middleware import require_role +from fablednetmon.models.users import UserRole +from fablednetmon.models.hosts import Host +from fablednetmon.models.monitors import DnsResult + +dns_bp = Blueprint("dns", __name__, url_prefix="/dns") + + +async def _latest_dns(db, host_ids: list[str]) -> dict: + if not host_ids: + return {} + subq = ( + select(DnsResult.host_id, func.max(DnsResult.resolved_at).label("max_at")) + .where(DnsResult.host_id.in_(host_ids)) + .group_by(DnsResult.host_id) + .subquery() + ) + pr = await db.execute( + select(DnsResult).join( + subq, + (DnsResult.host_id == subq.c.host_id) & (DnsResult.resolved_at == subq.c.max_at), + ) + ) + return {r.host_id: r for r in pr.scalars()} + + +@dns_bp.get("/") +@require_role(UserRole.viewer) +async def index(): + async with current_app.db_sessionmaker() as db: + result = await db.execute( + select(Host).where(Host.dns_enabled.is_(True)).order_by(Host.name) + ) + hosts = result.scalars().all() + latest = await _latest_dns(db, [h.id for h in hosts]) + poll_interval = current_app.config.get("MONITORS_POLL_INTERVAL", 60) + return await render_template("dns/index.html", hosts=hosts, latest=latest, poll_interval=poll_interval) + + +@dns_bp.get("/rows") +@require_role(UserRole.viewer) +async def rows(): + async with current_app.db_sessionmaker() as db: + result = await db.execute( + select(Host).where(Host.dns_enabled.is_(True)).order_by(Host.name) + ) + hosts = result.scalars().all() + latest = await _latest_dns(db, [h.id for h in hosts]) + return await render_template("dns/rows.html", hosts=hosts, latest=latest) diff --git a/fablednetmon/templates/alerts/list.html b/fablednetmon/templates/alerts/list.html index 4824701..be43f26 100644 --- a/fablednetmon/templates/alerts/list.html +++ b/fablednetmon/templates/alerts/list.html @@ -1,40 +1,40 @@ {% extends "base.html" %} {% block title %}Alerts — FabledNetMon{% endblock %} {% block content %} -
| State | -Rule | -Resource | -Metric | -+ | ||||
|---|---|---|---|---|---|---|---|---|
| State | +Rule | +Resource | +Metric | +|||||
| + | ||||||||
| {% if state.state.value == 'firing' %} - FIRING + FIRING {% elif state.state.value == 'acknowledged' %} - ACK + ACK {% else %} - PENDING + PENDING {% endif %} | -{{ rule.name }} | -{{ rule.resource_name }} | -{{ rule.metric_name }} {{ rule.operator.value }} {{ rule.threshold }} | -+ | {{ rule.name }} | +{{ rule.resource_name }} | +{{ rule.metric_name }} {{ rule.operator.value }} {{ rule.threshold }} | +{% if state.state.value == 'firing' %} {% endif %} | @@ -44,40 +44,40 @@
No active alerts.
+No active alerts.
{% endif %} {% if rules %} -| Name | -Condition | -Consec. | -Enabled | -+ | |||
|---|---|---|---|---|---|---|---|
| Name | +Condition | +Consec. | +Enabled | +||||
| {{ rule.name }} | -+ | ||||||
| {{ rule.name }} | +{{ rule.source_module }}/{{ rule.resource_name }}:{{ rule.metric_name }} {{ rule.operator.value }} {{ rule.threshold }} | -{{ rule.consecutive_failures_required }} | -- {% if rule.enabled %}yes{% else %}no{% endif %} + | {{ rule.consecutive_failures_required }} | ++ {% if rule.enabled %}yes{% else %}no{% endif %} | -+ | @@ -87,6 +87,6 @@ |
No alert rules configured.
+No alert rules configured.
{% endif %} {% endblock %} diff --git a/fablednetmon/templates/alerts/rules_form.html b/fablednetmon/templates/alerts/rules_form.html index 6a7bbcf..6c5d23f 100644 --- a/fablednetmon/templates/alerts/rules_form.html +++ b/fablednetmon/templates/alerts/rules_form.html @@ -2,7 +2,7 @@ {% block title %}New Alert Rule — FabledNetMon{% endblock %} {% block content %}