Harness MaxxingDocumentation
📚 Toutes les pages

🧩 Étendre · 8 min de lecture

🧩Widgets du tableau de bord

Le Tableau de bord est une grille de cartes. Faites-les glisser pour les réorganiser, masquez celles dont vous n'avez pas besoin (menu de disposition), actualisez une carte avec son bouton d'actualisation.

Les cartes se construisent au moment où elles apparaissent : les chiffres montent jusqu'à leur valeur, les listes et les tableaux apparaissent ligne par ligne, les barres des graphiques grandissent et les courbes se tracent, les barres de progression se remplissent, et Planning du jour ouvre la ligne de chaque tâche et y dépose ses exécutions de gauche à droite. Une carte située plus bas dans la page attend que vous y arriviez en faisant défiler, pour que vous en voyiez aussi l'animation. Quand le panneau des notifications s'ouvre ou se ferme, les cartes glissent vers leur nouvelle place. Tout reste immobile lorsque votre système demande de réduire les animations.

Une nouvelle installation affiche Activité, Serveur, Planning du jour, Tâches et Personnalisez votre espace. Les autres sont masquées jusqu'à ce que vous en ayez besoin (Modifier la disposition les affiche) ; Shopify apparaît tout seul dès qu'une boutique est connectée.

Personnalisez votre espace est la carte placée à la fin : cliquez sur un exemple (« Ventes Shopify du jour », « Visiteurs sur mon site »...) et une nouvelle session s'ouvre dans system/harness avec la demande déjà saisie - envoyez-la, et Harness écrit le widget dans vos extensions. N'importe quelle demande fonctionne de la même façon depuis n'importe quelle session : « ajoute un widget qui affiche ... ». Masquez la carte avec Modifier la disposition une fois que vous avez le vôtre.

Widgets intégrés

Widget Affiche
Activité les sessions récentes, les tours en cours, les réponses non lues (avant la première session : par où commencer)
Tâches en cours, à faire aujourd'hui, en retard
Planning du jour la journée de vos tâches planifiées sur une ligne de 00:00 à 24:00 : une rangée par tâche, les exécutions terminées (en vert, en rouge en cas d'échec), les exécutions à venir (creuses), celle en cours (qui pulse) et une ligne « maintenant » ; survolez un point pour voir son heure et sa durée
Tâches planifiées les prochaines exécutions et les échecs récents
Serveur processeur, mémoire, disque, durée de fonctionnement, problèmes de santé
Sauvegarde de la mémoire dernière exécution, ancienneté, dépôt
Telegram état du bot et de la liaison
Shopify chiffre d'affaires du jour et sur 7 jours (par rapport à la semaine précédente), commandes du jour, commandes à expédier, graphique du chiffre d'affaires sur 14 jours, dernières commandes — une fois qu'une boutique est connectée (voir ci-dessous)
Personnalisez votre espace comment obtenir vos propres widgets, avec des exemples qui ouvrent une session prête à envoyer
Utilisation de Claude désactivé par défaut (Paramètres -> Fonctionnalités) : les fenêtres d'utilisation de votre forfait, identiques aux barres en bas de la barre latérale (lues dans la dernière réponse de Claude ; quand aucune n'est arrivée depuis 10 minutes, il interroge un point d'accès Anthropic non documenté, qui peut cesser de fonctionner ; quand ce point d'accès échoue ou demande d'attendre, les derniers chiffres lus dans Claude restent affichés).

Connecter une boutique Shopify

Le widget Shopify affiche une carte « Connectez votre boutique » tant que vous n'avez pas ajouté deux clés :

  1. Dans l'administration Shopify : Paramètres → Applications et canaux de vente → Développer des applications → Créer une application, puis Configurer les portées de l'API Admin → cochez read_orders → Installer l'application → affichez le jeton d'accès de l'API Admin (shpat_…, affiché une seule fois).
  2. Dans Harness Maxxing : Paramètres → Clés → ajoutez SHOPIFY_STORE = myshop.myshopify.com et SHOPIFY_ADMIN_TOKEN = le jeton.

Le widget s'actualise toutes les 2 minutes (ou avec son bouton d'actualisation) et ne fait que lire les commandes. Une autre boutique (WooCommerce, Stripe, PrestaShop…) ? Demandez un widget à Claude : il construit une extension de la même façon (voir ci-dessous).

Écrire votre propre widget

Un widget est un dossier dans <workspace>/extensions/<id>/ contenant deux fichiers. Vous écrivez des données, pas du HTML : la carte est dessinée par le système de design de l'app, elle est donc toujours belle en mode sombre comme en mode clair, sur ordinateur comme sur téléphone. (Besoin d'un dessin libre ou d'une interaction - un jeu, un canvas ? Voir Widgets HTML ci-dessous.)

Le plus rapide : demandez à Claude dans une session, « crée un widget de tableau de bord qui affiche ... ». La compétence maxxing-extension connaît tout ce qui suit.

1. Créer le dossier

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

L'identifiant (le nom du dossier) est un slug en minuscules : lettres, chiffres, tirets. Identifiants réservés : 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}
Clé Signification
type widget
api version du contrat d'extension pour laquelle il est écrit (1 par défaut) ; une version qui cesse de la prendre en charge est annoncée par la vérification préalable à la mise à jour, avant que vous ne mettiez à jour
icon un nom d'icône en kebab-case (gauge, chart-line, shopping-cart, users, receipt...) parmi les icônes lucide fournies avec l'app ; un nom que l'app ne fournit pas affiche une pièce de puzzle
span largeur en colonnes de la grille : 1, 2 ou 3
ttl nombre de secondes pendant lesquelles le résultat est mis en cache (300 par défaut). Le bouton d'actualisation l'ignore.

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) s'exécute à l'intérieur du serveur : utilisez uniquement la bibliothèque standard de Python et gardez les appels réseau courts (délai d'attente ≤ 10 s). Le code des extensions s'exécute sur 4 threads qui leur sont propres : un code qui ne rend jamais la main (un appel réseau sans délai d'attente) ne bloque que les autres extensions (« les extensions sont occupées » jusqu'à ce qu'il rende la main), jamais le reste de Harness. Un api.py doit se charger en moins de 10 s (aucun appel réseau à l'import), sinon l'extension affiche une erreur. ctx contient :

Clé Valeur
id l'identifiant de l'extension
workspace le chemin de l'espace de travail
state_dir un dossier réservé à cette extension (cache, données)
secret(name) la valeur d'une clé de Paramètres -> Clés, ou None
config {"timezone", "language", "public_url"}

N'écrivez jamais un jeton en dur : enregistrez-le dans Paramètres -> Clés et lisez-le avec ctx["secret"]("MY_TOKEN").

4. Le schéma de 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 rend le titre de la carte cliquable (une route de l'app comme /tasks ou une URL https://). Les types de blocs inconnus sont ignorés.

Exemple de liste + graphique :

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. Tester, puis charger

Testez la fonction directement sur le serveur :

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))"

Puis Paramètres -> Extensions -> Recharger. Le widget apparaît sur le tableau de bord. Si quelque chose ne va pas (manifeste invalide, erreur d'import, exception dans card), l'erreur s'affiche sur la ligne de l'extension et sur la carte ; le reste du tableau de bord continue de fonctionner.

Les extensions vivent dans votre espace de travail : les mises à jour n'y touchent jamais et la sauvegarde les inclut.

Widgets HTML (jeux, dessins personnalisés)

Quand une Card ne suffit pas - un petit jeu, un dessin sur canvas, une bibliothèque de graphiques personnalisée - écrivez plutôt le widget en HTML. Même dossier, avec widget.html à la place de api.py (ou en plus) :

/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

Le tableau de bord affiche widget.html dans un cadre isolé (sandbox) : les scripts s'exécutent, mais le cadre ne peut pas lire l'app, ses cookies ni son API - un widget ne peut donc jamais agir à votre place. Il communique avec son propre api.py grâce à un petit pont qui est injecté pour vous :

Dans widget.html Ce que ça fait
await maxxing.api("best") · maxxing.api("best", {method: "PUT", body: {...}}) appelle handle() dans le api.py de cette extension (/api/ext/<id>/best) et renvoie son JSON
maxxing.resize(400) règle la hauteur du cadre en px (la valeur height du manifeste, 120-800, est la valeur par défaut)
maxxing.theme · maxxing.onTheme(fn) "dark" ou "light", et une fonction de rappel quand il change
class="ui-card", ui-btn, ui-input, ui-badge, ui-stat... le kit d'interface de l'app (voir Extensions de page), déjà chargé dans le cadre
var(--card) --foreground --muted-foreground --border --primary... (et les anciennes --mx-fg --mx-muted --mx-border --mx-accent --mx-card --mx-bg) les couleurs de l'app, mises à jour avec le thème et la couleur d'accent

Règles : un seul fichier (aucun autre fichier du dossier n'est servi au cadre - mettez tout en ligne, ou chargez les bibliothèques depuis un CDN avec <script src="https://...">) ; pas de localStorage ni de cookies (le bac à sable n'en a pas : enregistrez via api.py et ctx["state_dir"]). Le bouton d'actualisation de la carte recharge le cadre. Exemple complet : examples/extensions/snake-widget/ dans le dossier du code.

💡 Bloqué ? Ouvrez une session dans le topic system › help et posez votre question avec vos mots : Claude lit cette doc sur votre serveur et vous répond.✏️ Améliorer cette page