From 7591268a8916f924a51b6e80ac5653351cd5acd2 Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Fri, 10 Jul 2026 15:15:46 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20GET=20/api/0/log=20=E2=80=94=20paged,?= =?UTF-8?q?=20filtered=20events=20journal=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- hbd/server/http.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/hbd/server/http.py b/hbd/server/http.py index 6108282..2407b2d 100644 --- a/hbd/server/http.py +++ b/hbd/server/http.py @@ -20,6 +20,7 @@ from . import users as users_mod from . import oauth as oauth_mod from . import ws as ws_mod from . import configio as configio_mod +from . import journal as journal_mod from . import config_access logger = logging.getLogger(__name__) @@ -362,6 +363,46 @@ async def start( lst = data.msgs[-30:] return web.json_response(lst) + async def api_log(request): + """Paged, filtered read of the events journal (newest first).""" + user, err = _require_auth(request) + if err: + return err + qa = request.rel_url.query + try: + limit = max(1, min(int(qa.get("limit", "100")), 1000)) + except ValueError: + limit = 100 + before = None + if qa.get("before"): + try: + before = float(qa["before"]) + except ValueError: + return web.json_response({"error": "invalid before"}, status=400) + host_f = qa.get("host") or None + level_f = qa.get("level") or None + q_f = qa.get("q") or None + + def visible(ev): + h = ev.get("host") + return not h or ws_mod._user_can_see_host(user, h) + + ej = journal_mod.get_events_journal(config) + if ej.enabled and ej.journal_path.is_file(): + events, more = journal_mod.read_events( + ej.journal_dir, ej.journal_file, + limit=limit, before=before, host=host_f, level=level_f, q=q_f, + predicate=visible, + ) + else: + # Journal disabled or not yet written: serve the in-memory ring + events, more = journal_mod.filter_events( + reversed(data.msgs), + limit=limit, before=before, host=host_f, level=level_f, q=q_f, + predicate=visible, + ) + return web.json_response({"events": events, "more": more}) + async def cmd(request): user, err = _require_auth(request) if err: @@ -1779,6 +1820,7 @@ async def start( web.get("/api/0/hosts", api_hosts), web.get("/api/0/alert_summary", api_alert_summary), web.get("/api/0/messages", api_messages), + web.get("/api/0/log", api_log), web.get("/api/0/hosts/{hostname}/plugins", api_host_plugins), web.get("/api/0/hosts/{hostname}/plugins/{plugin_name}", api_host_plugin_detail), web.get("/api/0/hosts/{hostname}/alerts", api_host_alerts),