Harness MaxxingDocumentazione
📚 Tutte le pagine

🧩 Estendere · 8 min di lettura

🧩Widget della Dashboard

La Dashboard è una griglia di schede. Trascina per riordinarle, nascondi quelle che non ti servono (menu del layout), aggiorna una scheda con il suo pulsante di aggiornamento.

Le schede si costruiscono da sole mentre compaiono: i numeri salgono fino al loro valore, elenchi e tabelle appaiono riga per riga, le barre dei grafici crescono e le linee si disegnano, le barre di avanzamento si riempiono, e Programma di oggi apre la riga di ogni job e vi fa cadere sopra le sue esecuzioni da sinistra a destra. Una scheda più in basso nella pagina aspetta che tu ci arrivi con lo scorrimento, così la vedi anche tu. Quando il pannello delle notifiche si apre o si chiude, le schede scorrono verso la loro nuova posizione. Tutto resta fermo quando il tuo sistema chiede di ridurre le animazioni.

Una nuova installazione mostra Attività, Server, Programma di oggi, Attività da fare e Rendila tua. Le altre sono nascoste finché non ti servono (Modifica layout le mostra); Shopify compare da solo non appena un negozio è collegato.

Rendila tua è la scheda alla fine: clicca su un esempio ("Vendite Shopify di oggi", "Visitatori del mio sito"...) e si apre una nuova sessione in system/harness con la richiesta già scritta - inviala, e Harness scrive il widget nelle tue estensioni. Qualsiasi richiesta funziona allo stesso modo da qualsiasi sessione: "aggiungi un widget che mostra ...". Nascondi la scheda con Modifica layout quando hai il tuo widget.

Widget integrati

Widget Cosa mostra
Attività sessioni recenti, turni in corso, risposte non lette (prima della prima sessione: da dove iniziare)
Attività da fare in corso, in scadenza oggi, in ritardo
Programma di oggi la giornata dei tuoi job pianificati su una linea 00:00 - 24:00: una riga per job, esecuzioni completate (verdi, rosse se fallite), esecuzioni ancora da venire (vuote), quella in corso adesso (pulsante) e una linea "adesso"; passa il mouse su un punto per vederne l'orario e la durata
Job pianificati prossime esecuzioni e fallimenti recenti
Server CPU, memoria, disco, uptime, problemi di salute
Backup della memoria ultima esecuzione, età, repository
Telegram stato del bot e del collegamento
Shopify ricavi di oggi e degli ultimi 7 giorni (rispetto alla settimana prima), ordini di oggi, ordini da spedire, grafico dei ricavi degli ultimi 14 giorni, ultimi ordini — una volta collegato un negozio (vedi sotto)
Rendila tua come ottenere i tuoi widget, con esempi che aprono una sessione pronta da inviare
Utilizzo di Claude disattivato di default (Impostazioni -> Funzionalità): finestre di utilizzo del piano, le stesse delle barre in fondo alla barra laterale (lette dall'ultima risposta di Claude; quando non ne arriva nessuna per 10 minuti, interroga un endpoint Anthropic non documentato, che potrebbe smettere di funzionare; quando quell'endpoint fallisce o chiede di attendere, restano visibili gli ultimi dati letti da Claude).

Collegare un negozio Shopify

Il widget Shopify mostra una scheda "Collega il tuo negozio" finché non aggiungi due chiavi:

  1. Nell'admin di Shopify: Impostazioni → App e canali di vendita → Sviluppa app → Crea un'app, poi Configura gli ambiti dell'API Admin → seleziona read_orders → Installa app → mostra il token di accesso dell'API Admin (shpat_…, mostrato una sola volta).
  2. In Harness Maxxing: Impostazioni → Chiavi → aggiungi SHOPIFY_STORE = myshop.myshopify.com e SHOPIFY_ADMIN_TOKEN = il token.

Il widget si aggiorna ogni 2 minuti (o con il suo pulsante di aggiornamento) e legge soltanto gli ordini. Hai un altro negozio (WooCommerce, Stripe, PrestaShop…)? Chiedi a Claude un widget: costruisce un'estensione nello stesso modo (vedi sotto).

Scrivere il tuo widget

Un widget è una cartella in <workspace>/extensions/<id>/ con due file. Scrivi dati, non HTML: la scheda viene disegnata dal design system dell'app, quindi ha sempre un bell'aspetto in modalità scura e chiara, su desktop e su telefono. (Ti serve un disegno libero o un'interazione - un gioco, un canvas? Vedi Widget HTML più sotto.)

Il modo più veloce: chiedi a Claude in una sessione, "crea un widget per la dashboard che mostra ...". La skill maxxing-extension conosce tutto quello che segue.

1. Crea la cartella

/root/maxxing/extensions/disk-usage/
  manifest.json
  api.py

L'id (il nome della cartella) è uno slug in minuscolo: lettere, cifre, trattini. Id riservati: 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}
Chiave Significato
type widget
api versione del contratto delle estensioni per cui è scritta (predefinita 1); una versione che smette di supportarla viene annunciata dal controllo preliminare dell'aggiornamento prima che tu aggiorni
icon il nome di un'icona in kebab-case (gauge, chart-line, shopping-cart, users, receipt...) tra le icone lucide incluse nell'app; un nome che l'app non include mostra un pezzo di puzzle
span larghezza in colonne della griglia: 1, 2 o 3
ttl secondi di permanenza del risultato nella cache (predefinito 300). Il pulsante di aggiornamento la ignora.

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) viene eseguita dentro il server: usa solo la libreria standard di Python e tieni brevi le chiamate di rete (timeout ≤ 10 s). Il codice delle estensioni gira su 4 thread dedicati: un codice che non ritorna mai (una chiamata di rete senza timeout) blocca soltanto le altre estensioni ("extensions are busy" finché non ritorna), mai il resto di Harness. Un api.py deve caricarsi in meno di 10 s (nessuna chiamata di rete al momento dell'import), altrimenti l'estensione mostra un errore. ctx contiene:

Chiave Valore
id l'id dell'estensione
workspace percorso del workspace
state_dir una cartella riservata a questa estensione (cache, dati)
secret(name) valore di una chiave di Impostazioni -> Chiavi, oppure None
config {"timezone", "language", "public_url"}

Non scrivere mai un token direttamente nel codice: salvalo in Impostazioni -> Chiavi e leggilo con ctx["secret"]("MY_TOKEN").

4. Lo schema della Card

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 rende cliccabile il titolo della scheda (una rotta dell'app come /tasks o un URL https://). I tipi di blocco sconosciuti vengono ignorati.

Esempio di elenco + grafico:

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. Prova, poi carica

Prova la funzione direttamente sul 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))"

Poi Impostazioni -> Estensioni -> Ricarica. Il widget compare nella dashboard. Se qualcosa non va (manifest non valido, errore di import, eccezione in card), l'errore viene mostrato sulla riga dell'estensione e sulla scheda; il resto della dashboard continua a funzionare.

Le estensioni vivono nel tuo workspace: gli aggiornamenti non le toccano mai e il backup le include.

Widget HTML (giochi, disegni personalizzati)

Quando una Card non basta - un piccolo gioco, un disegno su canvas, una libreria di grafici personalizzata - scrivi il widget in HTML. Stessa cartella, con widget.html al posto di api.py (o accanto):

/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

La dashboard disegna widget.html in un frame isolato (sandbox): gli script vengono eseguiti, ma il frame non può leggere l'app, i suoi cookie o la sua API - un widget non può mai agire al posto tuo. Comunica con il proprio api.py attraverso un piccolo ponte che viene iniettato per te:

In widget.html Cosa fa
await maxxing.api("best") · maxxing.api("best", {method: "PUT", body: {...}}) chiama handle() nell'api.py di questa estensione (/api/ext/<id>/best), restituisce il suo JSON
maxxing.resize(400) imposta l'altezza del frame in px (il height del manifest, 120-800, è il valore predefinito)
maxxing.theme · maxxing.onTheme(fn) "dark" o "light", e una callback quando cambia
class="ui-card", ui-btn, ui-input, ui-badge, ui-stat... l'UI kit dell'app (vedi Estensioni di pagina), già caricato nel frame
var(--card) --foreground --muted-foreground --border --primary... (e i più vecchi --mx-fg --mx-muted --mx-border --mx-accent --mx-card --mx-bg) i colori dell'app, aggiornati con il tema e il colore d'accento

Regole: un solo file (nessun altro file della cartella viene servito al frame - metti tutto inline, oppure carica le librerie da una CDN <script src="https://...">); niente localStorage/cookie (la sandbox non ne ha: salva tramite api.py e ctx["state_dir"]). Il pulsante di aggiornamento della scheda ricarica il frame. Esempio completo: examples/extensions/snake-widget/ nella cartella del codice.

💡 Bloccato? Apri una sessione nel topic system › help e chiedi con parole tue: Claude legge questa documentazione sul tuo server e ti risponde.✏️ Migliora questa pagina