Files
leocrm/templates/plugin-template/plugin.py
T
Agent Zero fc96a2f86c Phase 3: Plugin-UI-System (WordPress-Style)
Backend:
- PluginManifest um 5 neue UI-Felder erweitert: menu_items, page_routes,
  detail_tabs, settings_pages, dashboard_widgets (FrontendMenuItem,
  FrontendPageRoute, FrontendDetailTab, FrontendSettingsPage,
  FrontendDashboardWidget)
- GET /api/v1/plugins/active-manifests Endpoint liefert UI-Manifeste
  aller aktiven Plugins
- Registry.get_active_manifests() + PluginService.get_active_manifests()
- 12 Built-in Plugins mit UI-Manifest-Daten gefuellt (menu_items,
  page_routes, detail_tabs, settings_pages)
- Plugin-Install-System: POST /upload (ZIP), POST /install-url (URL)
  mit Validierung (Manifest, dangerous imports, SQL migrations)

Frontend:
- pluginStore.ts (Zustand) mit PluginUiManifest Typen + Selektoren
- useActivePluginManifests() React Query Hook
- PluginRegistry.tsx — fetcht Manifeste beim App-Start
- PluginLoader.tsx — dynamisches React.lazy() mit ErrorBoundary
- PluginRouteRenderer.tsx — Catch-all fuer Plugin-Routes
- routes/index.tsx — Catch-all Routes fuer Plugin-Pages + Settings
- Sidebar.tsx — dynamische Plugin Menu-Items mit Grouping + Icons
- Settings.tsx — dynamische Plugin Settings-Pages
- ContactDetail.tsx — dynamische Plugin Detail-Tabs mit Permissions
- AppShell.tsx — PluginRegistry Provider eingebunden
- SettingsPlugins.tsx — Install-UI (ZIP Upload + URL Install)
- plugins.ts — useUploadPlugin() + useInstallPluginFromUrl() Hooks

Docs & Templates:
- docs/plugin-development-guide.md — komplette Entwickler-Doku
- templates/plugin-template/ — Boilerplate mit allen Manifest-Feldern

Tests:
- 34 Vitest-Tests (PluginRegistry, PluginLoader, PluginRouteRenderer,
  pluginStore) — alle bestanden
- TSC: keine neuen Errors (nur pre-existing Dms.tsx)
2026-07-23 19:01:18 +02:00

179 lines
7.0 KiB
Python

"""
Plugin Template — Example plugin demonstrating all manifest fields.
Copy this directory to app/plugins/builtins/<your_plugin_name>/ and customize.
"""
from __future__ import annotations
from app.plugins.base import BasePlugin
from app.plugins.manifest import (
PluginManifest,
PluginRouteDef,
FieldDefinition,
FrontendMenuItem,
FrontendPageRoute,
FrontendDetailTab,
FrontendSettingsPage,
FrontendDashboardWidget,
)
class ExamplePlugin(BasePlugin):
"""
Example plugin demonstrating all manifest fields.
Remove or comment out fields you don't need.
"""
manifest = PluginManifest(
# ── Core Metadata ──────────────────────────────────────────────
name="example_plugin",
version="1.0.0",
display_name="Example Plugin",
description="A minimal example plugin demonstrating all manifest fields.",
dependencies=[],
is_core=False,
# ── API Routes ─────────────────────────────────────────────────
routes=[
PluginRouteDef(
path="/api/v1/example",
module="app.plugins.builtins.example.routes",
router_attr="router",
),
],
# ── Event Subscriptions ────────────────────────────────────────
events=[
"contact.created",
"contact.updated",
],
# ── Database Migrations ────────────────────────────────────────
migrations=[
"0001_initial.sql",
],
# ── RBAC Permissions ───────────────────────────────────────────
permissions=[
"example:read",
"example:write",
],
# ── Field Definitions (Field-Level Permissions) ────────────────
field_definitions=[
FieldDefinition(
module="contacts",
field="custom_field",
label="Custom Field",
sensitivity="normal",
),
],
# ── AI Agent Capabilities ──────────────────────────────────────
agent_capabilities=[
"example:search",
],
# ── Frontend UI: Sidebar Menu Items ────────────────────────────
menu_items=[
FrontendMenuItem(
label_key="nav.example",
label="Example",
path="/example",
icon="Sparkles",
order=100,
),
],
# ── Frontend UI: Page Routes ───────────────────────────────────
page_routes=[
FrontendPageRoute(
path="/example",
component="@/pages/Example",
protected=True,
),
],
# ── Frontend UI: Detail Tabs ───────────────────────────────────
detail_tabs=[
FrontendDetailTab(
entity_type="contact",
label_key="tabs.example",
label="Example",
component="@/components/ExampleTab",
icon="Sparkles",
order=50,
permission="example:read",
),
],
# ── Frontend UI: Settings Pages ────────────────────────────────
settings_pages=[
FrontendSettingsPage(
path="example",
label_key="settings.example",
label="Example",
component="@/pages/ExampleSettings",
icon="Sparkles",
order=100,
permission="example:write",
),
],
# ── Frontend UI: Dashboard Widgets ─────────────────────────────
dashboard_widgets=[
FrontendDashboardWidget(
id="example_stats",
label_key="widgets.example",
label="Example Stats",
component="@/components/ExampleWidget",
icon="LayoutDashboard",
order=100,
col_span=2,
row_span=1,
permission="example:read",
),
],
)
# ─── Lifecycle Hooks ───────────────────────────────────────────────
async def on_install(self, db, service_container):
"""Called after migrations are run."""
# Perform seed data or initial setup here
pass
async def on_activate(self, db, service_container, event_bus):
"""Called when the plugin is activated."""
# Default implementation subscribes to manifest events
await super().on_activate(db, service_container, event_bus)
async def on_deactivate(self, db, service_container, event_bus):
"""Called when the plugin is deactivated."""
# Default implementation unsubscribes all event listeners
await super().on_deactivate(db, service_container, event_bus)
async def on_uninstall(self, db, service_container):
"""Called before data tables are dropped."""
# Clean up external resources here
pass
# ─── Event Handlers ────────────────────────────────────────────────
async def on_contact_created(self, event_data: dict) -> None:
"""Handle contact.created event."""
contact_id = event_data.get("contact_id")
# React to new contact
pass
async def on_contact_updated(self, event_data: dict) -> None:
"""Handle contact.updated event."""
contact_id = event_data.get("contact_id")
# React to contact update
pass
# ─── Notification Types ────────────────────────────────────────────
def get_notification_types(self) -> list[dict]:
"""Return notification types this plugin registers."""
return [
{
"type_key": "example.notification",
"label": "Example Notification",
"category": "general",
"description": "Notification from the example plugin",
"is_enabled_by_default": True,
},
]