Reporting

Le reporting du Labo du Yeti agrège trois flux distincts : la consommation IA (tokens et coût estimé), les logs d'audit de toutes les actions sensibles, et les snapshots de stats du site exposés via API pour alimenter une plateforme cross-sites externe. Cette page décrit l'état actuel du dispositif, les endpoints disponibles, le modèle de données associé, et les pistes d'intégration externes (Search Console, Analytics) prévues à la roadmap.

Philosophie. Le CMS ne stocke que les données nécessaires à son propre fonctionnement. Les agrégations cross-instances (vue agence multi-clients) sont déléguées à une plateforme externe qui consomme /api/stats, /api/version et /api/ai/usage via tokens API long-lived.

Dashboard de consommation IA

L'écran /admin/ia (capability settings_ai, accès owner et admin uniquement) expose un dashboard complet de la consommation IA du CMS. Chaque appel sortant vers un fournisseur (Anthropic, OpenAI) est instrumenté dans backend/src/services/ai-service.js et persisté dans la table ai_usage au moyen du helper aiUsageInsert().

Modèle ai_usage

Chaque ligne de la table représente un appel IA unique. Les colonnes sont prévues pour permettre des agrégats par jour, par type de génération et par fournisseur sans recourir à un OLAP externe.

ColonneTypeDescription
idINTEGERAuto-increment, clé primaire
timestampTEXT (ISO 8601)Date/heure de l'appel
kindTEXTcocon, page, article, blog, other
actionTEXTLibellé fonctionnel (ex. cocon_html_generated, resolve_inline)
providerTEXTanthropic, openai
modelTEXTModèle exact au moment de l'appel (ex. claude-opus, gpt-4o-mini)
input_tokensINTEGERTokens entrants remontés par l'API (0 si non disponible)
output_tokensINTEGERTokens sortants remontés par l'API
successINTEGER1 = OK, 0 = erreur (réseau, quota, validation)
error_codeTEXTStatus HTTP ou mot-clé si success = 0
duration_msINTEGERLatence totale en millisecondes
resource_idTEXTID de la page/article/cocon associé (corrélation)

Trois indices accélèrent les requêtes : ai_usage_ts (DESC sur timestamp), ai_usage_kind et ai_usage_success. Cf. la page Base de données pour le schéma SQL complet.

Helpers d'agrégation

Le module backend/src/utils/db-store.js expose plusieurs fonctions d'agrégation calculées en SQL natif (pas de chargement complet de la table en mémoire) :

Coût estimé via global.ai.pricing

Le coût n'est pas stocké en base : il est recalculé à chaque rendu du dashboard à partir du tarif courant. Cela permet de mettre à jour les prix sans rejouer l'historique. Le store global contient le bloc ai.pricing :

{
  "ai": {
    "pricing": {
      "openai":    { "input_per_1k": 0.00015, "output_per_1k": 0.0006 },
      "anthropic": { "input_per_1k": 0.003,   "output_per_1k": 0.015  }
    }
  }
}

La formule appliquée pour chaque ligne d'agrégat est :

cout_usd = (input_tokens / 1000) * pricing[provider].input_per_1k
         + (output_tokens / 1000) * pricing[provider].output_per_1k;
Approximation. Les tarifs input_per_1k / output_per_1k sont par défaut ceux des modèles d'entrée de gamme. Si la production utilise des modèles premium (Claude Opus, GPT-4 Turbo), un owner doit ajuster manuellement global.ai.pricing via /admin/ia. Le coût affiché reste indicatif et ne remplace pas la facturation officielle du fournisseur.

Filtres & export du dashboard

L'écran propose une interface dense pour explorer les données :

Endpoints /api/ai/*

MéthodeEndpointAccèsRôle
GET/api/ai/statusrequireAuthTous
GET/api/ai/configcapability settings_aiowner, admin
PUT/api/ai/configcapability settings_aiowner, admin
GET/api/ai/usagecapability settings_aiowner, admin
GET/api/ai/usage/export.csvcapability settings_ai + aiCallLimiterowner, admin

Voir aussi Moteur IA pour le détail des appels instrumentés et Authentification & RBAC pour la matrice des capabilities.

Logs d'audit

Toutes les actions sensibles du CMS sont tracées dans un journal append-only. Le helper logger.js (alias logsInsert()) écrit dans la table SQLite logs, optimisée par quatre indices (timestamp DESC, type, action, user_id) pour rester performante sur de très gros volumes sans purge.

Modèle logs

ColonneTypeDescription
idINTEGERAuto-increment
timestampTEXTISO 8601
typeTEXTCatégorie (ex. page_created, user_login, extension_installed)
actionTEXTLibellé fonctionnel libre
resource_idTEXTID de la ressource impactée (page, user, extension, token…)
user_idTEXTAuteur de l'action (référence users.id, sans FK pour append-only)
messageTEXTDescription libre
metaTEXTJSON sérialisé pour contexte structuré
Append-only. Aucune contrainte FK sur logs.user_id pour garantir la performance d'insertion et la conservation des entrées même après suppression d'un compte utilisateur. La table n'est jamais purgée par le code ; un owner peut la tronquer manuellement via un export + restore si nécessaire.

UI /admin/logs

L'écran /admin/logs (capability logs, accès owner, admin, webmaster) consomme GET /api/logs avec pagination indexée DB. Il propose :

Événements tracés (non exhaustif)

API tokens long-lived

Les tokens API sont destinés aux intégrations machine-to-machine : reporting cross-sites, serveurs MCP, scripts CI/CD, agrégateurs externes. Ils contournent JWT/2FA mais s'authentifient via un Authorization: Bearer cmstok_… et héritent du rôle de l'utilisateur créateur.

Modèle api_tokens

ColonneTypeDescription
idTEXT (UUID)Clé primaire
user_idTEXTFK users.id ON DELETE CASCADE (le token meurt avec son créateur)
nameTEXTLibellé utilisateur (ex. Reporting agence, MCP desktop)
token_hashTEXT UNIQUEHash SHA-256 du token brut
scopesTEXTread par défaut
created_atTEXTISO 8601
expires_atTEXT (nullable)Expiration optionnelle
last_used_atTEXT (nullable)Mis à jour à chaque requête authentifiée (helper apiTokensTouchLastUsed())
revoked_atTEXT (nullable)Soft delete
Format du token. Le token retourné en clair une seule fois lors de la création a le préfixe cmstok_ suivi d'un secret aléatoire de 32 octets encodés. Seul le hash SHA-256 est stocké en base — si l'utilisateur perd le token brut, il doit en régénérer un nouveau.

Héritage de rôle et scope

À chaque requête authentifiée par token, le middleware requireAuth :

  1. Détecte le préfixe cmstok_ dans l'en-tête Authorization.
  2. Hash SHA-256 du token reçu, lookup via apiTokensFindByHash().
  3. Vérifie expires_at et revoked_at.
  4. Charge le user via usersFindById(token.user_id).
  5. Injecte req.user (avec le rôle du créateur) puis apiTokensTouchLastUsed(token.id) en arrière-plan.

Conséquence : un token créé par un editor n'aura jamais accès à /api/ai/usage (capability settings_ai non possédée). Pour un reporting agence, on crée le token sous un compte admin ou owner dédié à la machine.

Endpoints de gestion

MéthodeEndpointAccès
GET/api/auth/tokenscapability settings_tokens
POST/api/auth/tokenscapability settings_tokens
DELETE/api/auth/tokens/:idcapability settings_tokens

La capability settings_tokens est par défaut accordée à owner et admin seulement (cf. Authentification & RBAC).

Snapshot /api/stats

L'endpoint GET /api/stats retourne un snapshot synthétique du site, conçu pour être consommé par une plateforme de reporting cross-sites en mode pull. Il est protégé par requireAuth et accepte aussi bien un JWT qu'un token API.

Payload

{
  "site": {
    "name": "Le Labo du Yeti",
    "url": "https://example.com",
    "default_locale": "fr"
  },
  "counts": {
    "pages": 42,
    "articles": 17,
    "cocon_groups": 5,
    "cocon_pages": 240,
    "media": 312,
    "users": 4
  },
  "version": "0.1.0",
  "generated_at": "2026-06-01T08:30:00Z"
}

Ce snapshot est calculé en SQL direct (SELECT COUNT(*)) et peut être interrogé à haute fréquence sans coût. Aucune information sensible (emails, hashes, clés IA) n'est exposée.

Endpoint /api/version

GET /api/version retourne la version du CMS (lue depuis package.json) ainsi qu'un snapshot statistiques. Utile pour qu'une plateforme cross-sites détecte d'un coup d'œil les instances en retard de mise à jour.

{ "version": "0.1.0", "snapshot": { "pages": 42, "articles": 17 } }

Endpoints publics (sans auth)

MéthodeEndpointUsage
GET/api/healthPing uptime (monitoring externe type UptimeRobot)
GET/api/brandingDonnées branding filtrées pour l'écran Login

Architecture reporting cross-sites

Le modèle SaaS du Labo du Yeti est hybride : 1 client = 1 instance Plesk + DB + domaine. Pour offrir une vue agence centralisée, on déploie une plateforme externe qui interroge périodiquement chaque instance.

Schéma pull (cron côté plateforme)

  1. Sur chaque instance CMS client, un owner crée un token API dédié (nom : Reporting agence), expiration 12 mois.
  2. La plateforme stocke { instance_url, token } par client.
  3. Un cron (5 min, 1 h, ou 1 j selon la criticité) appelle en parallèle /api/health, /api/version, /api/stats et /api/ai/usage sur toutes les instances.
  4. Les payloads sont stockés dans une base centrale (PostgreSQL, ClickHouse, BigQuery selon le volume).
  5. Un dashboard agence consomme cette base avec graphiques croisés (coût IA agrégé, sites en panne, instances obsolètes).
Avantage pull. Pas de webhook à configurer par instance, pas de file d'attente à maintenir, pas de fuite de données si une instance plante (la plateforme constate simplement l'absence de réponse). Les endpoints CMS restent stateless.

Sécurité du dispositif

Intégrations futures (roadmap)

Trois axes sont prévus à la roadmap pour enrichir le reporting au-delà des données internes au CMS.

Google Search Console

Branchement d'API searchconsole.googleapis.com avec compte de service Google Cloud. Stockage du service_account.json dans global.integrations.search_console (chiffré, non exposé aux extensions). Données ciblées :

Le dashboard /admin/seo affichera ces données par page CMS via corrélation page.slug ↔ URL Search Console.

Google Analytics 4

Branchement de la Data API v1 avec le même compte de service. Données ciblées :

Plateforme reporting agence

Application séparée (hors monorepo) qui implémente le schéma pull décrit plus haut. Au programme :

Statut. L'infrastructure côté CMS est prête (endpoints stables, tokens API en place, helpers d'agrégation testés). La plateforme agence elle-même n'est pas encore développée — c'est le point 8 de la roadmap, après MAJ Blog, pipeline CI et provisioning Plesk.

Bonnes pratiques

Références code