P1: Rollenbasiertes Dashboard mit Plugin-Karten

- Plugin-Vertrag erweitert: optionaler Hook dashboard_karten(user) +
  DashboardKarte-Dataclass (Kern bleibt schlank, Fachlogik im Plugin)
- Karten: Neuheiten+System-Status (neuheiten), Planungs-Stats bzw.
  eigene Einträge (planung), offene Dedup-Konflikte (dedup), nächste
  Redaktionsschlüsse (erinnerung), ungelesene Benachrichtigungen
  (benachrichtigung), letzte Audit-Einträge (audit-log, nur Admin),
  Benutzer-Anzahl (Kern, nur Admin)
- 'Geladene Plugins' als aufklappbarer Admin-Technikbereich
This commit is contained in:
Flo Hartmann
2026-08-26 08:48:30 +00:00
parent 742c265df8
commit 9bd3b700c4
9 changed files with 326 additions and 17 deletions

View File

@@ -35,7 +35,13 @@ from sqlalchemy import func, or_, select
from redaktionskern.auth.deps import require_roles, require_user
from redaktionskern.auth.models import Role, User
from redaktionskern.contracts import BasePlugin, Migration, NavEntry, PluginContext
from redaktionskern.contracts import (
BasePlugin,
DashboardKarte,
Migration,
NavEntry,
PluginContext,
)
from .bgg import BggClient, BggFehler
from .models import Neuheit, QuellenStatus
@@ -394,6 +400,43 @@ class NeuheitenPlugin(BasePlugin):
def navigation(self) -> list[NavEntry]:
return [NavEntry(label=self.title, url="/neuheiten")]
def dashboard_karten(self, user: User) -> list[DashboardKarte]:
"""Dashboard: Neuheiten-Übersicht (Redaktion) + System-Status (Admin)."""
if user.role not in (Role.ADMIN.value, Role.REDAKTEUR.value):
return []
with self.context.session_factory() as db:
neu = db.scalar(
select(func.count())
.select_from(Neuheit)
.where(Neuheit.status == "neuheit")
) or 0
karten = [
DashboardKarte(
titel="Neuheiten",
wert=str(neu),
beschreibung="Offene Einträge in der Neuheitenliste",
link_url="/neuheiten",
link_label="Zu den Neuheiten",
ton="ok" if neu else "neutral",
)
]
if user.role == Role.ADMIN.value:
token = self._aktives_bgg_token()
sync_aktiv = self._scheduler is not None and self._scheduler.running
karten.append(
DashboardKarte(
titel="System-Status",
zeilen=(
f"BGG-API-Token: {'konfiguriert' if token else 'fehlt'}",
f"Hintergrund-Sync: {'aktiv' if sync_aktiv else 'aus'}",
),
ton="ok" if token else "fehler",
link_url="/neuheiten",
link_label="Neuheiten synchronisieren",
)
)
return karten
def on_load(self, context: PluginContext) -> None:
super().on_load(context)
import os