43 lines
1.4 KiB
Python
43 lines
1.4 KiB
Python
from collections.abc import Awaitable, Callable
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
@dataclass
|
|
class Plugin:
|
|
"""A self-contained dashboard plugin.
|
|
|
|
Each plugin registers a router (mounted in create_app) and reports
|
|
itself here with a display title/description. `open`/`close` are
|
|
optional lifecycle hooks run from the app lifespan; `skeleton_fn`
|
|
renders the plugin's initial fragment for the Plugins tab.
|
|
"""
|
|
|
|
id: str
|
|
title: str
|
|
description: str = ""
|
|
skeleton_fn: Callable[[], Awaitable[str]] | None = field(default=None)
|
|
open_fn: Callable[[], Awaitable[None]] | None = field(default=None)
|
|
close_fn: Callable[[], Awaitable[None]] | None = field(default=None)
|
|
|
|
async def skeleton(self) -> str:
|
|
"""Render the plugin's initial fragment.
|
|
|
|
Returns:
|
|
The HTML fragment for the Plugins tab.
|
|
|
|
Raises:
|
|
NotImplementedError: if no skeleton_fn was provided.
|
|
"""
|
|
if self.skeleton_fn is None:
|
|
raise NotImplementedError
|
|
return await self.skeleton_fn()
|
|
|
|
async def open(self) -> None:
|
|
"""Run the plugin's startup hook (no-op when not provided)."""
|
|
if self.open_fn is not None:
|
|
await self.open_fn()
|
|
|
|
async def close(self) -> None:
|
|
"""Run the plugin's shutdown hook (no-op when not provided)."""
|
|
if self.close_fn is not None:
|
|
await self.close_fn()
|