feat: Web Admin Dashboard — plugin-contributed panels via Starlette #43

Open
opened 2026-02-20 17:23:34 +00:00 by nazim · 0 comments
Contributor

Summary

A built-in web admin dashboard where every plugin contributes panels, settings, and actions. Same pattern as register_commands() / wizard_section() — plugins declare what to show, the framework renders it.

Stack

  • Starlette (async, minimal, what FastAPI is built on)
  • Jinja2 templates (server-rendered)
  • HTMX (~14KB) for interactivity (auto-refresh, forms, SSE)
  • Pico.css or similar classless CSS
  • Built into cobot as a loop plugin (no separate process)

Plugin Extension Points

3 new methods on Plugin base class:

def web_panels(self) -> list[WebPanel]:     # Dashboard cards
    ...
def web_settings(self) -> list[WebSetting]:  # Config UI
    ...
def web_routes(self) -> list[WebRoute]:      # Custom API endpoints
    ...

web_panels() — Dashboard Cards

Plugins return structured data, framework renders it. No HTML from plugins.

def web_panels(self):
    return [WebPanel(
        id="ppq-status",
        title="LLM Provider",
        category="status",       # status | config | logs | tools
        priority=10,
        refresh_seconds=30,
    )]

Panel content kinds: kv (key-value), table, log, chart, form, markdown.

Data is snapshot-based: plugins update an atomic snapshot in their loop, web reads last snapshot. No cross-thread calls.

web_settings() — Configuration UI

def web_settings(self):
    return [
        WebSetting(key="ppq.model", label="Model", kind="select",
                   options=["gpt-4o", "gpt-4o-mini"]),
        WebSetting(key="ppq.api_key", label="API Key", kind="secret"),
    ]

Framework generates forms, validates, writes to cobot.yml.

Hot-reload where possible. If a change requires restart, show a banner.

web_routes() — Custom Actions

Namespaced automatically: /api/plugins/{plugin_id}/...

def web_routes(self):
    return [WebRoute("POST", "/test-message", self.send_test, "Send test")]
    # Becomes: POST /api/plugins/telegram/test-message

What Each Plugin Contributes

Plugin Panels Settings Routes
ppq LLM status, balance API key, model
telegram Connection, groups, recent msgs Bot token, timeout Send test msg
pairing Pending/authorized users Enabled, owner IDs Approve/reject
communication Channel overview
logger Live log stream (SSE) Log level Clear
loop Message rate, uptime, errors Poll interval
persistence Conversation count, disk Enabled, max msgs Clear history
compaction Stats, last run Token budget Force compact
tools Tool call stats Exec enabled, blocklist
wallet Balance, payments Pay invoice

Live Streaming

SSE (Server-Sent Events) for real-time data: log lines, status changes. HTMX has native SSE support (hx-ext="sse").

Auth

None for now. Bind to 127.0.0.1 only.

Config

web:
  enabled: true
  host: "127.0.0.1"
  port: 8080

Phases

  1. Phase 1: WebPlugin + dashboard with web_panels() — read-only status
  2. Phase 2: web_settings() — config via browser (hot-reload + restart detection)
  3. Phase 3: web_routes() — custom actions
  4. Phase 4: SSE live streaming (logs, real-time status)

Design Decisions

  • Structured data, not HTML — Plugins return dicts, framework renders. Custom UI reserved for later.
  • Snapshot-based data — Plugins update atomic snapshots in their loop. Web reads last snapshot. Thread-safe by design.
  • Route namespacing/api/plugins/{id}/... prevents collisions (same approach as CLI plugin groups).
  • Starlette over FastAPI — No pydantic overhead, we control all inputs. Starlette is the foundation FastAPI sits on.
  • No auth initially — localhost-only. Auth can layer on later (pairing integration, session tokens).
## Summary A built-in web admin dashboard where every plugin contributes panels, settings, and actions. Same pattern as `register_commands()` / `wizard_section()` — plugins declare what to show, the framework renders it. ## Stack - **Starlette** (async, minimal, what FastAPI is built on) - **Jinja2** templates (server-rendered) - **HTMX** (~14KB) for interactivity (auto-refresh, forms, SSE) - **Pico.css** or similar classless CSS - Built into cobot as a loop plugin (no separate process) ## Plugin Extension Points 3 new methods on `Plugin` base class: ```python def web_panels(self) -> list[WebPanel]: # Dashboard cards ... def web_settings(self) -> list[WebSetting]: # Config UI ... def web_routes(self) -> list[WebRoute]: # Custom API endpoints ... ``` ### web_panels() — Dashboard Cards Plugins return structured data, framework renders it. No HTML from plugins. ```python def web_panels(self): return [WebPanel( id="ppq-status", title="LLM Provider", category="status", # status | config | logs | tools priority=10, refresh_seconds=30, )] ``` Panel content kinds: `kv` (key-value), `table`, `log`, `chart`, `form`, `markdown`. Data is **snapshot-based**: plugins update an atomic snapshot in their loop, web reads last snapshot. No cross-thread calls. ### web_settings() — Configuration UI ```python def web_settings(self): return [ WebSetting(key="ppq.model", label="Model", kind="select", options=["gpt-4o", "gpt-4o-mini"]), WebSetting(key="ppq.api_key", label="API Key", kind="secret"), ] ``` Framework generates forms, validates, writes to `cobot.yml`. **Hot-reload** where possible. If a change requires restart, show a banner. ### web_routes() — Custom Actions Namespaced automatically: `/api/plugins/{plugin_id}/...` ```python def web_routes(self): return [WebRoute("POST", "/test-message", self.send_test, "Send test")] # Becomes: POST /api/plugins/telegram/test-message ``` ## What Each Plugin Contributes | Plugin | Panels | Settings | Routes | |--------|--------|----------|--------| | ppq | LLM status, balance | API key, model | — | | telegram | Connection, groups, recent msgs | Bot token, timeout | Send test msg | | pairing | Pending/authorized users | Enabled, owner IDs | Approve/reject | | communication | Channel overview | — | — | | logger | Live log stream (SSE) | Log level | Clear | | loop | Message rate, uptime, errors | Poll interval | — | | persistence | Conversation count, disk | Enabled, max msgs | Clear history | | compaction | Stats, last run | Token budget | Force compact | | tools | Tool call stats | Exec enabled, blocklist | — | | wallet | Balance, payments | — | Pay invoice | ## Live Streaming SSE (Server-Sent Events) for real-time data: log lines, status changes. HTMX has native SSE support (`hx-ext="sse"`). ## Auth None for now. Bind to `127.0.0.1` only. ## Config ```yaml web: enabled: true host: "127.0.0.1" port: 8080 ``` ## Phases 1. **Phase 1:** WebPlugin + dashboard with `web_panels()` — read-only status 2. **Phase 2:** `web_settings()` — config via browser (hot-reload + restart detection) 3. **Phase 3:** `web_routes()` — custom actions 4. **Phase 4:** SSE live streaming (logs, real-time status) ## Design Decisions - **Structured data, not HTML** — Plugins return dicts, framework renders. Custom UI reserved for later. - **Snapshot-based data** — Plugins update atomic snapshots in their loop. Web reads last snapshot. Thread-safe by design. - **Route namespacing** — `/api/plugins/{id}/...` prevents collisions (same approach as CLI plugin groups). - **Starlette over FastAPI** — No pydantic overhead, we control all inputs. Starlette is the foundation FastAPI sits on. - **No auth initially** — localhost-only. Auth can layer on later (pairing integration, session tokens).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ultanio/cobot#43
No description provided.