vendredi 25 septembre 2026

À propos

Contact

Outils & workflow

Un tableau de bord maison pour centraliser l’état de tous vos sites

Agréger version du cœur, des extensions et disponibilité de plusieurs sites dans un écran unique construit sur mesure, plutôt qu'un outil commercial.

Par Clément Hadrot • 4 mai 2025 • 5 min de lecture • Aucun commentaire
Un tableau de bord maison pour centraliser l'état de tous vos sites

Les outils de supervision commerciaux existants, déjà couverts sur ce blog, conviennent parfaitement à la majorité des agences. Celle qui a construit son propre tableau de bord avait une contrainte particulière : plusieurs de ses clients refusaient catégoriquement qu’un service tiers américain reçoive, même de façon anonymisée, la liste des extensions installées sur leurs sites, jugée sensible dans leur secteur d’activité. La solution retenue a consisté à construire un outil maison, minimal mais suffisant, hébergé entièrement en interne.

Ce tutoriel détaille la construction de cet outil, en trois briques : un point d’API exposé par chaque site à superviser, un script central qui interroge tous les sites et stocke les résultats, et un écran web unique qui affiche la synthèse.

Étape 1 : exposer l’état sur chaque site

Chaque site du parc reçoit une extension minimale, mutualisée sur mesure (un fichier unique dans mu-plugins), qui enregistre un point de terminaison REST personnalisé retournant les informations utiles :

<?php
add_action('rest_api_init', function () {
    register_rest_route('agence-supervision/v1', '/etat', [
        'methods'  => 'GET',
        'callback' => 'agence_etat_site',
        'permission_callback' => 'agence_verifier_jeton',
    ]);
});

function agence_verifier_jeton(\WP_REST_Request $requete) {
    $jeton_recu = $requete->get_header('X-Agence-Jeton');
    return hash_equals(getenv('AGENCE_SUPERVISION_JETON'), (string) $jeton_recu);
}

function agence_etat_site() {
    global $wp_version;

    $extensions = get_plugins();
    $actives = get_option('active_plugins', []);
    $a_mettre_a_jour = get_plugin_updates();

    return [
        'core_version' => $wp_version,
        'core_a_jour'  => empty(get_core_updates()),
        'extensions_actives' => count($actives),
        'extensions_a_mettre_a_jour' => count($a_mettre_a_jour),
        'php_version' => PHP_VERSION,
        'horodatage' => time(),
    ];
}

L’authentification par jeton partagé, transmis dans un en-tête personnalisé et comparé avec hash_equals pour éviter une faille de comparaison temporelle, reste volontairement simple : ce point d’API ne fait que lire des informations déjà publiques pour un administrateur connecté, sans exposer de secret.

Étape 2 : centraliser la collecte

Un script Python, exécuté toutes les dix minutes via un job cron sur un petit serveur central, interroge chaque site listé dans un fichier de configuration et stocke le résultat dans une base SQLite légère :

L'essentiel à retenir : Un point d'API léger sur chaque site expose son propre état ; Un job planifié centralise les réponses dans une base unique ; L'écran final tient sur une seule page sans dépendance externe
import sqlite3
import requests
import yaml
from datetime import datetime

with open("sites.yaml") as f:
    sites = yaml.safe_load(f)["sites"]

conn = sqlite3.connect("supervision.db")
conn.execute("""
CREATE TABLE IF NOT EXISTS etats (
    site TEXT, core_version TEXT, core_a_jour INTEGER,
    extensions_actives INTEGER, extensions_a_mettre_a_jour INTEGER,
    php_version TEXT, disponible INTEGER, verifie_le TEXT
)
""")

for site in sites:
    try:
        reponse = requests.get(
            f"{site['url']}/wp-json/agence-supervision/v1/etat",
            headers={"X-Agence-Jeton": site["jeton"]},
            timeout=8,
        )
        donnees = reponse.json()
        disponible = 1
    except Exception:
        donnees = {}
        disponible = 0

    conn.execute(
        "INSERT INTO etats VALUES (?,?,?,?,?,?,?,?)",
        (
            site["nom"],
            donnees.get("core_version"),
            int(donnees.get("core_a_jour", False)),
            donnees.get("extensions_actives"),
            donnees.get("extensions_a_mettre_a_jour"),
            donnees.get("php_version"),
            disponible,
            datetime.utcnow().isoformat(),
        ),
    )

conn.commit()

Étape 3 : l’écran de synthèse

Une simple page PHP, protégée par une authentification basique en amont via le serveur web, lit la dernière ligne de chaque site dans la base SQLite et affiche un tableau coloré : vert pour un site disponible et à jour, orange pour des mises à jour en attente, rouge pour un site injoignable.

<?php
$pdo = new PDO('sqlite:supervision.db');
$sites = $pdo->query("
    SELECT * FROM etats
    WHERE verifie_le = (SELECT MAX(verifie_le) FROM etats AS e2 WHERE e2.site = etats.site)
    ORDER BY site
")->fetchAll(PDO::FETCH_ASSOC);

foreach ($sites as $site) {
    $classe = !$site['disponible'] ? 'rouge'
        : ($site['extensions_a_mettre_a_jour'] > 0 ? 'orange' : 'vert');
    echo "<tr class=\"$classe\"><td>{$site['site']}</td>"
       . "<td>{$site['core_version']}</td>"
       . "<td>{$site['extensions_a_mettre_a_jour']} à jour</td></tr>";
}

Ce que cet outil ne fait pas

Ce tableau de bord maison reste volontairement limité : pas d’historique de tendance au-delà de quelques jours, pas d’alerte push automatique, pas de gestion fine des seuils par client. Sur ces aspects, un outil commercial reste largement supérieur. La motivation de départ n’était pas de rivaliser en fonctionnalités, mais de répondre à une exigence contractuelle précise de confidentialité pour une poignée de clients sensibles, tout en gardant le reste du parc sur un outil plus complet par ailleurs.

Un outil maison ne doit jamais chercher à égaler un produit commercial fonctionnalité par fonctionnalité : il doit répondre à un besoin précis que le produit commercial ne couvre pas, et s’arrêter là.

En résumé

Ce tableau de bord tient en environ 200 lignes de code réparties sur trois fichiers, s’exécute sur un serveur modeste, et suffit à donner une vue d’ensemble fiable sur 45 sites sans transmettre la moindre donnée à un tiers. Il ne remplace pas un outil commercial pour la majorité des besoins de l’agence, mais comble un vide précis, celui de la confidentialité totale, pour les quelques clients qui l’exigeaient contractuellement.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi