-
Notifications
You must be signed in to change notification settings - Fork 0
Dashboard Integration DE
🌐 English · Deutsch
Dies ist die Entwickler-Anleitung, um einen eigenen Red-DiscordBot-Cog an das PDC Web Dashboard anzubinden. Du deklarierst deine Beiträge — Widgets, Panels, Listen, Seiten — mit einer Handvoll Decorators und lieferst deklarative Schemas zurück, niemals rohes HTML. Das Frontend rendert sie in eine gemeinsame, themebare Oberfläche.
Zwei Prinzipien machen das sicher und reibungslos: Es gibt keine harte Abhängigkeit (dein Cog lädt und läuft auch ohne das Dashboard, weil die Decorators dann zu No-ops werden), und es läuft parallel zu AAA3As Red-Web-Dashboard (unterschiedliche Marker-Attribute, keine Kollision). Du kannst einen einzigen Cog ausliefern, der beide Dashboards gleichzeitig bedient.

Die kanonische Referenz ist der dashboardtemplate-Cog (ein voll kommentiertes Template, das nichts Sinnvolles tut, aber jeden Beitragstyp zeigt) sowie der kleinere dashboardexample-Cog. Beide liegen in diesem Repository.
Kopiere die Datei pdc_dashboard.py 1:1 in deinen Cog-Ordner (sie liegt in jedem angebundenen Cog, z. B. dashboardtemplate/pdc_dashboard.py). Sie ist die gesamte Integrationsfläche — ein einzelnes Modul ohne externe Anforderungen.
Was diese Datei leistet:
| Verhalten | Detail |
|---|---|
| Importiert die echte Maschinerie | Ist pdc_webdashboard installiert, re-exportiert sie die Decorators und Schema-Klassen aus pdc_webdashboard.integration. |
| Fällt auf No-ops zurück | Ist pdc_webdashboard nicht installiert, wird jeder Decorator zum Durchreicher und jede Schema-Klasse zu einem harmlosen Stub. Dein Cog lädt unverändert. |
Stellt DASHBOARD_AVAILABLE bereit |
Ein Boolean, das du zur Laufzeit prüfen kannst, um optional zu verzweigen. |
| Liefert Register-/Unregister-Helfer |
register_dashboard(cog) und unregister_dashboard(cog) — beide sicher aufrufbar, egal ob das Dashboard geladen ist. |
Importiere dann aus deiner lokalen Kopie, nicht aus pdc_webdashboard:
from .pdc_dashboard import (
dashboard_widget, dashboard_panel, dashboard_list, dashboard_page,
WidgetData, PanelSchema, PageSchema, Field, Component, SubmitResult,
register_dashboard, unregister_dashboard, DASHBOARD_AVAILABLE,
)Wenn dein Cog ohnehin hart von
pdc_webdashboardabhängt, kannst du auch direkt auspdc_webdashboard.integrationimportieren — empfohlen ist aber die Drop-in-Kopie, damit der Cog eigenständig nutzbar bleibt.
Der Fallback ist voll robust: Selbst das No-op-
@dashboard_panelhängt ein.on_submitan (und die No-op-Liste ihre Edit-/Delete-Handler), sodass dein Cog nicht abstürzt, wenn WebDashboard fehlt oder mitten im Reload ist.
Registriere in cog_load, deregistriere in cog_unload. Beide Aufrufe sind No-ops, wenn das Dashboard nicht geladen ist.
async def cog_load(self) -> None:
register_dashboard(self) # integriert NUR, wenn WebDashboard geladen ist
# ... deine eigene Lade-Logik ...
def cog_unload(self) -> None:
unregister_dashboard(self) # immer sicherregister_dashboard deckt den Fall ab, dass dein Cog nach dem Dashboard geladen wird. Das Dashboard scannt beim Start zudem alle bereits geladenen Cogs automatisch, womit auch der Fall „Cog vor dem Dashboard geladen" abgedeckt ist — der explizite register_dashboard-Aufruf ist aber der robuste Weg für beide Reihenfolgen.
Ein Cog steuert genau ein Modul zur Detailseite eines Servers bei. Alle Panels und Listen dieses Cogs werden zu Tabs innerhalb dieses Moduls, sortiert über das Schlüsselwort order= (kleiner = weiter links). So bleiben zusammengehörige Einstellungen beisammen, statt pro Cog eine isolierte Extra-Seite zu erzeugen. Nutze order= für eine bewusste Tab-Anordnung — z. B. ein „Anlegen"-Panel bei order=25 direkt links neben seiner Liste bei order=30.
Ein Widget ist eine kleine, schreibgeschützte Kachel auf dem zentralen Board / der Server-Übersicht. Die Methode erhält einen DashboardContext und liefert ein WidgetData.
@dashboard_widget("member_count", "Mitglieder", size="sm", refresh=60, permission="guild_member")
async def member_count(self, ctx):
return WidgetData.kpi(value=ctx.guild.member_count, label="Mitglieder", icon="users")| Argument | Bedeutung |
|---|---|
identifier, name
|
Stabile ID und Anzeigetitel. |
size |
sm · md · lg. |
refresh |
Auto-Refresh-Intervall in Sekunden (optional). |
permission |
Mindest-Berechtigungsstufe. |
scope |
guild (Standard) oder global. |
WidgetData bietet Convenience-Konstruktoren je Widget-Art:
| Konstruktor | Rendert |
|---|---|
WidgetData.kpi(value, label, …) |
Eine einzelne Kennzahl (mit optionalem trend, icon, intent). |
WidgetData.list(items, …) |
Eine Liste von Einträgen. |
WidgetData.chart(series, chart_type=…) |
Ein Mini-Chart (line · bar · area · doughnut). |
WidgetData.status(state, label, …) |
Ein Status-Indikator (ok / warn / error). |
WidgetData.markdown(text) |
Sicher gerendertes Markdown. |
Ein Panel ist ein eingebettetes Formular (ein Tab in deinem Modul), keine eigene Seite. Die dekorierte Methode liefert ein PanelSchema; der Speicher-Handler wird mit @<panel>.on_submit angehängt und erhält ein flaches {field_key: value}-Dict.
@dashboard_panel("welcome", "Willkommensnachricht", mount="guild_settings",
permission="guild_admin", order=10)
async def welcome_panel(self, ctx):
cfg = await self.config.guild(ctx.guild).welcome()
return PanelSchema(fields=[
Field.switch("enabled", "Aktiviert", value=cfg["enabled"]),
Field.textarea("message", "Nachricht", value=cfg["message"], max_length=2000),
Field.select("channel", "Kanal", channel_options, value=str(cfg["channel"] or "")),
])
@welcome_panel.on_submit
async def save_welcome(self, ctx, data):
await self.config.guild(ctx.guild).welcome.set(data)
return SubmitResult.ok("Gespeichert.")| Argument | Bedeutung |
|---|---|
mount |
Einbettungsort: guild_settings (Server-Einstellungen) oder bot_settings (global, mit scope="global"). |
permission |
Mindeststufe (Standard guild_admin). |
order |
Tab-Reihenfolge innerhalb des Moduls. |
scope |
guild oder global. Für ein globales/Owner-Panel: scope="global", mount="bot_settings", permission="bot_owner" — und ctx.guild nicht anfassen (es ist None). |
Eine Liste rendert eine Tabelle mit Anlegen-/Bearbeiten-/Löschen-Aktionen. Die dekorierte Methode liefert Zeilen [{"id": ..., "cells": {column_key: value, …}}]. Drei optionale Handler vervollständigen den CRUD-Zyklus:
| Handler | Signatur | Zweck |
|---|---|---|
@<list>.edit_form |
(ctx, id) -> PanelSchema |
Das Formular zum Bearbeiten einer Zeile. |
@<list>.on_edit |
(ctx, id, data) -> SubmitResult |
Speichert die bearbeitete Zeile. |
@<list>.on_delete |
(ctx, id) -> SubmitResult |
Löscht eine Zeile. |
Spalten werden am Decorator deklariert: columns=[{"key": "name", "label": "Name"}, …]. Kombiniere die Liste mit einem kleinen @dashboard_panel („Anlegen"-Tab) bei niedrigerem order=, um neue Einträge hinzuzufügen.
Für den seltenen Fall, dass ein Panel/Tab nicht reicht, registriert @dashboard_page eine ganze Seite aus einem PageSchema von Component-Knoten (heading · text · table · chart · panel_ref · divider · grid) — ebenfalls ohne rohes HTML. Mit nav=False bleibt sie aus der Seitennavigation.
Field-Builder bilden je eine Einstellung auf je ein Eingabefeld ab. Dass deklarative Felder (statt HTML) zurückkommen, eliminiert die XSS-Fläche vollständig.
| Builder | Eingabe |
|---|---|
Field.text(key, label, …) |
Einzeiliger Text. |
Field.textarea(key, label, max_length=…, variables=…) |
Mehrzeiliger Text; variables=[{"token": "{member}", "desc": "Mitglied"}] ergänzt Einfüge-Buttons. |
Field.number(key, label, min=…, max=…) |
Begrenzte Zahl. |
Field.switch(key, label) |
Boolescher Schalter. |
Field.select(key, label, options, reload_on_change=…) |
Einfachauswahl aus options. |
Field.multiselect(key, label, options) |
Mehrfachauswahl. |
Field.channel(key, label) / Field.role(key, label)
|
Kanal-/Rollen-Auswahl. |
Zwei Feld-Optionen verdienen besondere Erwähnung:
-
reload_on_change=Truean einemselect— ändert der Nutzer den Wert, wird das Panel sofort gespeichert und neu geladen. Nutze es für eine Einstellung, von der andere Felder abhängen (z. B. Umschalten eines „Profils" oder einer Modul-Sprache, sodass sich der Rest des Formulars neu rendert). -
variables=[…]an einertextarea— rendert klickbare Buttons, die ein Token ({member},{server}, …) an der Cursorposition einfügen, damit Admins deine Platzhalter-Syntax nicht auswendig kennen müssen.
Ein Submit-Handler liefert bei Erfolg SubmitResult.ok("Nachricht") oder bei Validierungsfehlern SubmitResult.fail("Nachricht", errors={"field_key": "Grund"}), um feldspezifische Fehler anzuzeigen.
Dein Cog kann sowohl Deutsch als auch Englisch ausliefern — für seine Dashboard-Texte und seine Discord-Ausgabe. Der Drop-in stellt dafür drei kleine Helfer bereit — importiere sie neben den Decorators; wie alles andere werden sie zu harmlosen No-ops, wenn pdc_webdashboard nicht installiert ist:
from .pdc_dashboard import L, tr, tr_langJeder Helfer deckt eine andere Fläche ab und ermittelt seine Sprache unterschiedlich:
| Helfer | Verwenden für | Folgt |
|---|---|---|
L("Deutsch", "English") |
Einen Decorator-Namen oder eine Beschreibung — den name von @dashboard_panel / @dashboard_widget / @dashboard_list sowie das description= einer @dashboard_list. |
Der Website-UI-Sprache (dem DE/EN-Umschalter oben rechts). Tab-Titel folgen der Seite. |
tr(ctx, "Deutsch", "English") |
Text, der aus einem Handler zurückkommt — PanelSchema(description=…), eine SubmitResult.ok/fail(…)-Nachricht, ein eigenes submit_label oder WidgetData-Text. |
Der Website-UI-Sprache, über ctx.locale. |
tr_lang(lang, "Deutsch", "English") |
Die Discord-Ausgabe des Cogs — Befehlsantworten, Embeds, DMs. | Einer Pro-Gilde-Einstellung language, die der Cog selbst speichert (NICHT der Website-Sprache). |
Übergibst du L nur ein Argument (z. B. L("Gleicher Text")), wird dieser Text für alle Sprachen verwendet.
Kernregel — Feld-LABELS bleiben Englisch. Halte eine einzige Quelle der Wahrheit: Nur Namen/Beschreibungen (Web-Sprache) und Discord-Ausgabe (pro Gilde) werden lokalisiert. Umhülle keine
Field.*-Labels.
L lokalisiert, was das Gateway als Tab-Titel zeigt; tr lokalisiert, was ein Handler zurückgibt, über ctx.locale. Der Website-Umschalter lädt die Modul-Texte live neu, sodass ein DE/EN-Wechsel beide sofort aktualisiert.
@dashboard_panel("welcome", L("Willkommen", "Welcome"), mount="guild_settings",
permission="guild_admin", order=10)
async def welcome_panel(self, ctx):
cfg = await self.config.guild(ctx.guild).welcome()
return PanelSchema(
description=tr(ctx, "Begrüße neue Mitglieder.", "Greet new members."),
fields=[
# LABELS bleiben Englisch — eine Quelle der Wahrheit:
Field.switch("enabled", "Enabled", value=cfg["enabled"]),
Field.textarea("message", "Message", value=cfg["message"], max_length=2000),
],
)
@welcome_panel.on_submit
async def save_welcome(self, ctx, data):
await self.config.guild(ctx.guild).welcome.set(data)
return SubmitResult.ok(tr(ctx, "Gespeichert.", "Saved."))Discord-Antworten folgen einer Sprache, die du pro Gilde speicherst — unabhängig davon, wer gerade das Dashboard betrachtet. In drei Schritten verdrahtet:
-
Standard registrieren in
register_guild(oder deinen Defaults):self.config.register_guild(language="de-DE")
-
Im Einstellungs-Panel anbieten als
select(beachte: dernamenutztL, die Option-Labels sind aber schlicht). Nutzereload_on_change=True, damit der Rest des Formulars reagieren kann:Field.select("language", L("Sprache", "Language"), [{"value": "de-DE", "label": "Deutsch"}, {"value": "en-US", "label": "English"}], value=cfg["language"], reload_on_change=True),
…und persistiere sie in deinem
on_submit. -
Jeden Befehl lokalisieren, indem du diese Einstellung liest und die Ausgabe mit
tr_langumhüllst:@commands.command() async def hello(self, ctx): lang = await self.config.guild(ctx.guild).language() await ctx.send(tr_lang(lang, "Hallo!", "Hello!"))
Die Cogs dashboardtemplate und dashboardexample sind voll funktionsfähige Referenzen für alle drei Helfer — übernimm ihre Muster wortwörtlich.
Das Dashboard listet auch die Befehle deines Cogs auf. Ein paar Muster lassen sie nativ wirken — zweisprachige Beschreibungen, Slash-Formen und die Stolperfallen, die sie still zerlegen.
Ein Befehl kann seine Beschreibung in der im Dashboard gewählten Sprache anzeigen. Ergänze eine i18n_desc-Map im extras= des Befehls:
@commands.hybrid_command(
name="greet",
description="Greet a member.",
extras={"i18n_desc": {"de-DE": "Ein Mitglied begrüßen.", "en-US": "Greet a member."}},
)
async def greet(self, ctx, member: discord.Member):
...Die Befehlsliste des Dashboards löst i18n_desc über den Website-Sprachumschalter (DE/EN, oben rechts) auf. Das schlichte description= bleibt Englisch — das ist der Text, den Discord selbst anzeigt — und auch das Parameter-@app_commands.describe(...) bleibt Englisch. extras={"i18n_desc": …} funktioniert bei @commands.hybrid_command, @commands.command, @commands.group und @app_commands.command.
Um einem Befehl eine Slash-Form zu geben, nutze @commands.hybrid_command / @commands.hybrid_group statt der schlichten Varianten.
Keyword-only-Vorbehalt. Ein Hybrid-Callback darf höchstens einen keyword-only-Parameter haben (das abschließende
*, valueals Consume-Rest). Zwei oder mehr keyword-only-Parameter lassen die Slash-Hälfte still wegfallen — der Befehl bleibt ohne Fehler nur als Prefix-Befehl. Lösung: Verschiebe die zusätzlichen optionalen Parameter zu positional-mit-Default vor das*:async def ban(self, ctx, member: discord.Member, delete_message_days: app_commands.Range[int, 0, 7] = 0, *, reason: str | None = None): ...
Red lässt neu hinzugefügte App-/Hybrid-Slash-Befehle standardmäßig deaktiviert. Aktiviere sie mit [p]slash enablecog <CogClassName> (oder pro Befehl [p]slash enable <name>), danach [p]slash sync. Sie lassen sich auch im Web-Dashboard unter Cogs → Slash umschalten.
-
Importiere
app_commandsausdiscord, nicht ausredbot.core. Nutzefrom discord import app_commands.from redbot.core import app_commandswirft beim Laden einenImportError/AttributeErrorund der gesamte Cog lädt nicht — seine Prefix- und Slash-Befehle verschwinden. - Zwei keyword-only-Parameter an einem Hybrid lassen die Slash-Hälfte still wegfallen (siehe oben).
-
[p]slash enablecog+[p]slash syncnach dem Hinzufügen von Slash-Befehlen vergessen — sie existieren, bleiben aber verborgen, bis sie aktiviert sind.
Jede Beitragsmethode erhält einen DashboardContext (ctx) mit bot, user, guild, member und locale. Der Aufruf ist bereits serverseitig autorisiert — gemäß der deklarierten permission, bevor deine Methode läuft. Du prüfst die Berechtigung also nicht erneut, sondern liest/schreibst nur Daten. In einem globalen Panel (scope="global") ist ctx.guild gleich None; entsprechend absichern.
Setze über permission= an jedem Beitrag die Mindeststufe, die ein Betrachter haben muss. Die Stufen werden serverseitig aus Reds eigenem Rechtesystem abgeleitet:
| Stufe | Wer |
|---|---|
authenticated |
Jeder eingeloggte Discord-Nutzer. |
guild_member |
Mitglied des Servers. |
guild_mod |
Server-Moderator (Reds Mod-Rolle). |
guild_admin |
Server-Admin (Reds Admin-Rolle / manage_guild). |
guild_owner |
Server-Eigentümer. |
bot_owner |
Bot-Owner. |
Wähle die niedrigste sinnvolle Stufe: Widgets oft guild_member, Einstellungs-Panels guild_admin, globale/API-Panels bot_owner.
Du kannst beide Dashboards aus demselben Cog bedienen. Die PDC-Marker (__dashboard_widget__, __dashboard_panel__, __dashboard_page__, __dashboard_list__) unterscheiden sich von denen von AAA3A, sodass sich die beiden Scanner nie stören und keines der Systeme das andere abschaltet. PDC integriert über register_dashboard(self) / den Auto-Scan und ist unabhängig von AAA3As Mixin-Vererbung — du kannst AAA3As Mixin also normal weiter erben. Importierst du beide dashboard_page-Symbole, aliasiere eines, um einen Namenskonflikt zu vermeiden.
- Kopiere
pdc_dashboard.pyin deinen Cog-Ordner und importiere die Decorators von dort. - Dekoriere deine Methoden (
@dashboard_widget/@dashboard_panel/@dashboard_list/@dashboard_page) und liefere Schemas zurück, niemals HTML. - Ergänze
@<panel>.on_submit(und Listen-Edit-/Delete-Handler), um Änderungen über deinConfigzu speichern. - Rufe
register_dashboard(self)incog_loadundunregister_dashboard(self)incog_unloadauf. - Lade den Cog — er funktioniert eigenständig und leuchtet automatisch auf, sobald der WebDashboard-Cog vorhanden ist.
- WebDashboard — der Companion-Cog und das Gateway-Setup.
- Web DashboardStats — ein durchgearbeitetes Beispiel eines stats-getriebenen Dashboard-Beitrags.
- Installation · Start
PDC_Redbot_Cogs · GitHub · Issues · Web Dashboard · Built for Red-DiscordBot · 🌐 English / Deutsch
🇬🇧 English
General
Cogs
Engagement & Utility
- Birthday
- StatChannels
- ScheduledMsg
- EventLog
- SocialFeed
- Giveaway
- Leveling
- TempVoice
- Starboard
- Suggestions
- LiveAlerts
Moderation
Fun
Web Dashboard
🇩🇪 Deutsch
Allgemein
Cogs
Engagement & Utility
- Birthday
- StatChannels
- ScheduledMsg
- EventLog
- SocialFeed
- Giveaway
- Leveling
- TempVoice
- Starboard
- Suggestions
- LiveAlerts
Moderation
Fun
Web-Dashboard