🧩Dashboard-Widgets
Das Dashboard ist ein Raster aus Karten. Zieh die Karten, um sie neu anzuordnen, blende die aus, die du nicht brauchst (Layout-Menü), und aktualisiere eine Karte mit ihrer Aktualisieren-Schaltfläche.
Die Karten bauen sich selbst auf, sobald sie erscheinen: Zahlen zählen bis zu ihrem Wert hoch, Listen und Tabellen erscheinen Zeile für Zeile, Diagrammbalken wachsen und Linien werden gezeichnet, Fortschrittsbalken füllen sich, und Heutiger Zeitplan öffnet die Zeile jedes Jobs und setzt dessen Durchläufe von links nach rechts darauf. Eine Karte weiter unten auf der Seite wartet, bis du zu ihr scrollst, sodass du das ebenfalls siehst. Wenn sich das Benachrichtigungspanel öffnet oder schließt, gleiten die Karten an ihren neuen Platz. Alles bleibt ruhig stehen, wenn dein System reduzierte Bewegung verlangt.
Eine neue Installation zeigt Aktivität, Server, Heutiger Zeitplan, Aufgaben und Mach es zu deinem. Die anderen sind ausgeblendet, bis du sie brauchst (Layout bearbeiten zeigt sie an); Shopify erscheint von selbst, sobald ein Shop verbunden ist.
Mach es zu deinem ist die Karte am Ende: Klicke auf ein Beispiel („Heutige Shopify-Umsätze“, „Besucher auf meiner
Website“ ...) und es öffnet sich eine neue Session in system/harness mit bereits eingetippter Anfrage - schick sie ab, und
Harness schreibt das Widget in deine Erweiterungen. Jede Anfrage funktioniert auf die gleiche Weise aus jeder Session: „Füge ein Widget hinzu,
das ... zeigt“. Blende die Karte mit Layout bearbeiten aus, sobald du dein eigenes hast.
Integrierte Widgets
| Widget | Zeigt |
|---|---|
| Aktivität | letzte Sessions, laufende Durchgänge, ungelesene Antworten (vor der ersten Session: wo du anfangen kannst) |
| Aufgaben | in Bearbeitung, heute fällig, überfällig |
| Heutiger Zeitplan | der Tag deiner geplanten Jobs auf einer Linie von 00:00 - 24:00: eine Zeile pro Job, erledigte Durchläufe (grün, rot bei Fehlschlag), noch anstehende Durchläufe (hohl), der gerade laufende (pulsierend) und eine „Jetzt“-Linie; fahre über einen Punkt, um Uhrzeit und Dauer zu sehen |
| Geplante Jobs | nächste Durchläufe und letzte Fehlschläge |
| Server | CPU, Arbeitsspeicher, Festplatte, Betriebszeit, Gesundheitsprobleme |
| Memory-Backup | letzter Lauf, Alter, Repository |
| Telegram | Status von Bot und Verbindung |
| Shopify | Umsatz heute und über 7 Tage (im Vergleich zur Vorwoche), Bestellungen heute, zu versendende Bestellungen, Umsatzdiagramm der letzten 14 Tage, neueste Bestellungen — sobald ein Shop verbunden ist (siehe unten) |
| Mach es zu deinem | wie du an eigene Widgets kommst, mit Beispielen, die eine versandfertige Session öffnen |
| Claude-Nutzung | standardmäßig aus (Einstellungen -> Funktionen): Nutzungsfenster deines Tarifs, wie die Balken unten in der Seitenleiste (gelesen aus der letzten Claude-Antwort; kam 10 Minuten lang keine, fragt es einen undokumentierten Anthropic-Endpunkt ab, der möglicherweise nicht mehr funktioniert; wenn dieser Endpunkt fehlschlägt oder um Wartezeit bittet, bleiben die letzten von Claude gelesenen Werte sichtbar). |
Einen Shopify-Shop verbinden
Das Shopify-Widget zeigt eine Karte „Verbinde deinen Shop“, bis du zwei Schlüssel hinzufügst:
- Im Shopify-Admin: Einstellungen → Apps und Vertriebskanäle → Apps entwickeln → App erstellen, dann
Admin-API-Bereiche konfigurieren →
read_ordersankreuzen → App installieren → den Admin-API- Zugriffstoken anzeigen (shpat_…, wird nur einmal angezeigt). - In Harness Maxxing: Einstellungen → Schlüssel →
SHOPIFY_STORE=myshop.myshopify.comundSHOPIFY_ADMIN_TOKEN= der Token hinzufügen.
Das Widget aktualisiert sich alle 2 Minuten (oder mit seiner Aktualisieren-Schaltfläche) und liest nur Bestellungen. Ein anderer Shop (WooCommerce, Stripe, PrestaShop…)? Bitte Claude um ein Widget: Es baut auf die gleiche Weise eine Erweiterung (siehe unten).
Dein eigenes Widget schreiben
Ein Widget ist ein Ordner in <workspace>/extensions/<id>/ mit zwei Dateien. Du schreibst Daten, nicht
HTML: Die Karte wird vom Designsystem der App gezeichnet, sodass sie im Dunkel- und im Hellmodus,
auf dem Desktop und auf dem Handy immer gut aussieht. (Brauchst du freies Zeichnen oder Interaktion - ein Spiel, ein Canvas? Siehe
HTML-Widgets weiter unten.)
Der schnellste Weg: Bitte Claude in einer Session: „Erstelle ein Dashboard-Widget, das ... zeigt“. Der
Skill maxxing-extension kennt alles Folgende.
1. Den Ordner anlegen
/root/maxxing/extensions/disk-usage/
manifest.json
api.py
Die ID (Ordnername) ist ein kleingeschriebener Slug: Buchstaben, Ziffern, Bindestriche. Reservierte IDs: activity,
tasks, day, crons, server, backup, telegram, shopify, usage, customize.
2. manifest.json
{"id": "disk-usage", "name": "Disk usage", "type": "widget", "icon": "hard-drive",
"version": "1.0.0", "api": 1, "span": 1, "ttl": 60}
| Schlüssel | Bedeutung |
|---|---|
type |
widget |
api |
Version des Erweiterungsvertrags, für den es geschrieben ist (Standard 1); ein Release, das sie nicht mehr unterstützt, wird vor dem Update vom Update-Preflight angekündigt |
icon |
ein Icon-Name in kebab-case (gauge, chart-line, shopping-cart, users, receipt...) aus dem Satz von lucide-Icons der App; ein Name, den die App nicht mitbringt, zeigt ein Puzzleteil |
span |
Breite in Rasterspalten: 1, 2 oder 3 |
ttl |
Sekunden, für die das Ergebnis zwischengespeichert wird (Standard 300). Die Aktualisieren-Schaltfläche umgeht den Cache. |
3. api.py
import shutil
def card(ctx):
du = shutil.disk_usage("/")
pct = round(100 * (du.total - du.free) / du.total)
return {
"title": "Disk usage",
"status": "error" if pct > 95 else "warn" if pct > 85 else "ok",
"blocks": [
{"type": "stat", "label": "Used", "value": f"{pct}%", "hint": f"{du.free / 1e9:.1f} GB free"},
{"type": "progress", "label": "/", "value": pct, "max": 100},
],
}
card(ctx) läuft im Server: Verwende nur die Python-Standardbibliothek und halte Netzwerkaufrufe kurz
(Timeout ≤ 10 s). Der Code von Erweiterungen läuft auf 4 eigenen Threads: Code, der nie zurückkehrt (ein Netzwerkaufruf ohne
Timeout), blockiert nur andere Erweiterungen („Erweiterungen sind ausgelastet“, bis er zurückkehrt), nie den Rest von Harness. Eine
api.py muss in weniger als 10 s laden (kein Netzwerkaufruf beim Import), sonst zeigt die Erweiterung einen Fehler. ctx enthält:
| Schlüssel | Wert |
|---|---|
id |
die ID der Erweiterung |
workspace |
Pfad des Workspace |
state_dir |
ein für diese Erweiterung reservierter Ordner (Cache, Daten) |
secret(name) |
Wert eines Schlüssels aus Einstellungen -> Schlüssel, oder None |
config |
{"timezone", "language", "public_url"} |
Schreibe niemals einen Token fest in den Code: Speichere ihn unter Einstellungen -> Schlüssel und lies ihn mit ctx["secret"]("MY_TOKEN").
4. Das Karten-Schema
Card = {title?, status?: "ok"|"warn"|"error", link?, blocks: [Block], updated?}
Block =
{type: "stat", label, value, hint?, delta?, tone?: "ok"|"warn"|"error"}
| {type: "stats", items: [stat, ...]} several stats in a row
| {type: "list", items: [{title, subtitle?, value?, href?, status?, icon?, emoji?}], empty?, grid?}
grid: true = tiles side by side (shortcuts)
icon: an icon name (below), drawn like the app's
| {type: "chart", kind: "bar"|"line", series: [{name, points: [[x, y], ...]}], unit?}
| {type: "table", columns: [str], rows: [[cell, ...]]}
| {type: "progress", label, value, max, hint?}
| {type: "markdown", text}
link macht den Kartentitel anklickbar (eine App-Route wie /tasks oder eine https://-URL). Unbekannte
Blocktypen werden ignoriert.
Beispiel Liste + Diagramm:
def card(ctx):
orders = [("#1042", "Alice", 89.0), ("#1041", "Bob", 34.5)]
return {
"title": "Shop",
"link": "https://shop.example.com/admin",
"blocks": [
{"type": "stats", "items": [{"type": "stat", "label": "Today", "value": "123.50 €"},
{"type": "stat", "label": "Orders", "value": "2"}]},
{"type": "chart", "kind": "bar", "unit": "€",
"series": [{"name": "Sales", "points": [["Mon", 80], ["Tue", 120], ["Wed", 123.5]]}]},
{"type": "list", "empty": "No orders yet",
"items": [{"title": o, "subtitle": c, "value": f"{v:.2f} €"} for o, c, v in orders]},
],
}
5. Testen, dann laden
Teste die Funktion direkt auf dem Server:
cd /root/maxxing/extensions/disk-usage && python3 -c "
import json, api, tempfile
ctx = {'id': 'disk-usage', 'workspace': '/root/maxxing', 'state_dir': tempfile.mkdtemp(),
'secret': lambda n: None, 'config': {'timezone': 'UTC', 'language': 'en', 'public_url': ''}}
print(json.dumps(api.card(ctx), indent=2))"
Dann Einstellungen -> Erweiterungen -> Neu laden. Das Widget erscheint auf dem Dashboard. Wenn etwas
nicht stimmt (ungültiges Manifest, Importfehler, Ausnahme in card), wird der Fehler in der Zeile der Erweiterung
und auf der Karte angezeigt; der Rest des Dashboards funktioniert weiter.
Erweiterungen liegen in deinem Workspace: Updates berühren sie nie, und das Backup enthält sie.
HTML-Widgets (Spiele, eigene Zeichnungen)
Wenn eine Karte nicht reicht - ein kleines Spiel, eine Canvas-Zeichnung, eine eigene Diagrammbibliothek - schreibe das
Widget stattdessen in HTML. Gleicher Ordner, mit widget.html statt (oder neben) api.py:
/root/maxxing/extensions/snake-widget/
manifest.json {"type": "widget", "name": "Snake", "icon": "gamepad-2", "height": 300}
widget.html one self-contained file: inline <style> and <script>
api.py optional: handle(ctx, method, path, query, body) for data it saves or fetches
Das Dashboard zeichnet widget.html in einem Sandbox-Frame: Skripte laufen, aber der Frame kann
weder die App noch ihre Cookies oder ihre API lesen - ein Widget kann also nie als du handeln. Es spricht mit seiner eigenen api.py
über eine kleine Brücke, die für dich eingefügt wird:
| In widget.html | Was es tut |
|---|---|
await maxxing.api("best") · maxxing.api("best", {method: "PUT", body: {...}}) |
ruft handle() in der api.py dieser Erweiterung auf (/api/ext/<id>/best) und gibt deren JSON zurück |
maxxing.resize(400) |
setzt die Frame-Höhe in px (die height im Manifest, 120-800, ist der Standard) |
maxxing.theme · maxxing.onTheme(fn) |
"dark" oder "light" und ein Callback, wenn es sich ändert |
class="ui-card", ui-btn, ui-input, ui-badge, ui-stat... |
das UI-Kit der App (siehe Seiten-Erweiterungen), bereits im Frame geladen |
var(--card) --foreground --muted-foreground --border --primary... (und die älteren --mx-fg --mx-muted --mx-border --mx-accent --mx-card --mx-bg) |
die Farben der App, aktualisiert mit dem Theme und der Akzentfarbe |
Regeln: nur eine Datei (keine andere Datei des Ordners wird an den Frame ausgeliefert - binde alles inline ein oder
lade Bibliotheken von einem CDN <script src="https://...">); kein localStorage/keine Cookies (die Sandbox hat
keine: speichere über api.py und ctx["state_dir"]). Die Aktualisieren-Schaltfläche der Karte lädt den
Frame neu. Vollständiges Beispiel: examples/extensions/snake-widget/ im Code-Verzeichnis.