From 04ccb30d7294dd065d26aba230d1d81df00af4fe Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 21 May 2026 13:53:05 +0400 Subject: [PATCH] 0.3.5: minimal status dashboard at GET / 2KB inline HTML page, monospace, dark background. Polls /health every 10s and renders source statuses with colored dots, last sync time, duration, file counts. Zero dependencies. --- CHANGELOG.md | 6 +++++ pyproject.toml | 2 +- src/oikb/__init__.py | 2 +- src/oikb/daemon.py | 61 +++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 68 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 348f73d..e18222c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [0.3.5] - 2025-05-21 + +### Added + +- Status dashboard at `GET /` — monospace status page rendered client-side from `/health`. Shows source name, status dot, last sync time, duration, file counts, next sync. Polls every 10s. Zero dependencies. + ## [0.3.4] - 2025-05-21 ### Added diff --git a/pyproject.toml b/pyproject.toml index a09710d..5aeeb9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oikb" -version = "0.3.4" +version = "0.3.5" description = "Sync anything to Open WebUI Knowledge Bases" readme = "README.md" authors = [ diff --git a/src/oikb/__init__.py b/src/oikb/__init__.py index 82e68b1..28c108d 100644 --- a/src/oikb/__init__.py +++ b/src/oikb/__init__.py @@ -1,3 +1,3 @@ """oikb — Open WebUI Knowledge Base CLI.""" -__version__ = "0.3.4" +__version__ = "0.3.5" diff --git a/src/oikb/daemon.py b/src/oikb/daemon.py index e9cf391..c385d68 100644 --- a/src/oikb/daemon.py +++ b/src/oikb/daemon.py @@ -40,7 +40,7 @@ async def verify_api_key( app = FastAPI( title="oikb", description="Sync engine for Open WebUI Knowledge Bases. Trigger syncs, check status, and query history.", - version="0.3.4", + version="0.3.5", ) # Runtime state populated by start_daemon(). @@ -82,8 +82,63 @@ def _next_cron_delay(cron_expr: str) -> float: return max((next_time - now).total_seconds(), 1.0) +# ── Dashboard ──────────────────────────────────────────────────── + +from fastapi.responses import HTMLResponse + +_DASHBOARD_HTML = """\ + + + + +oikb + + + +

oikb

+
loading...
+ + +""" + + # ── FastAPI routes ─────────────────────────────────────────────── +@app.get("/", include_in_schema=False, response_class=HTMLResponse) +async def dashboard(): + """Status dashboard — polls /health and renders source statuses.""" + return _DASHBOARD_HTML + + @app.get( "/health", operation_id="get_sync_status", @@ -93,6 +148,7 @@ async def health(): """Returns the current sync status for every configured source, including last sync time, duration, file counts, and any errors. Use this to check if syncs are running and healthy.""" return { "status": "ok", + "version": __version__, "sources": _scheduler_state, } @@ -215,6 +271,7 @@ async def _run_entry_locked(entry: dict, dry_run: bool = False) -> dict | None: _scheduler_state[source] = { **_scheduler_state.get(source, {}), + "name": entry.get("name", source), "status": "running", "started_at": started_at, } @@ -269,6 +326,7 @@ async def _run_entry_locked(entry: dict, dry_run: bool = False) -> dict | None: status = "success" if not result.errors else "partial" _scheduler_state[source] = { + "name": entry.get("name", source), "status": status, "last_sync": time.time(), "duration_ms": duration_ms, @@ -319,6 +377,7 @@ async def _run_entry_locked(entry: dict, dry_run: bool = False) -> dict | None: except Exception as e: _scheduler_state[source] = { + "name": entry.get("name", source), "status": "error", "last_sync": time.time(), "error": str(e),