Tableau de Bord Partagé avec le Client dans GLPI

Comment créer et partager des tableaux de bord d'assistance avec vos clients à l'aide de Metabase ou Grafana sur GLPI, avec un filtrage par entité (y compris les sous-entités), un calcul de SLA correct en base et un accès inviolable.

La transparence crée la confiance, mais un tableau de bord mal isolé se transforme en incident de protection des données. Partager des indicateurs d'assistance avec le client réduit les relances par e-mail et traduit du professionnalisme. Le défi technique n'est pas de dessiner le graphique : c'est de garantir que chaque client ne voie que ses propres tickets. Voici comment le faire avec GLPI + Metabase ou Grafana, avec le filtre par entité verrouillé au bon endroit.

Avant de publier : trois décisions qui définissent tout

Dans l'exploitation d'environnements GLPI multiclients, trois choix déterminent si le tableau de bord sera un atout ou un problème de sécurité :

  • Où verrouiller le filtre par entité : dans le lien, dans la requête ou dans l'embed signé. Seul le dernier résiste à la manipulation de l'URL.
  • Quelle date utiliser pour la période : création (date), résolution (solvedate) ou clôture (closedate). Mélanger les trois fausse tous les indicateurs.
  • D'où lire les données : la base primaire ou une réplique en lecture seule. Metabase et Grafana déclenchent des requêtes lourdes et concurrencent GLPI en production.

Étape 1 - un utilisateur de base de données en lecture seule

Ne pointez jamais l'outil de BI vers l'utilisateur GLPI. Créez un utilisateur dédié avec SELECT et rien d'autre. Sous MariaDB :

-- Utilisateur en lecture seule pour la BI, limité au réseau interne
CREATE USER 'bi_ro'@'10.20.%' IDENTIFIED BY '...mot_de_passe_fort...';
GRANT SELECT ON glpi.* TO 'bi_ro'@'10.20.%';

-- La limite de temps par requête évite qu'un tableau de bord bloque la base (MariaDB)
SET GLOBAL max_statement_time = 15;   -- secondes

FLUSH PRIVILEGES;

Si possible, pointez la BI vers une réplique de lecture plutôt que vers la base primaire. Ainsi, un filtre oublié dans un graphique ne retient pas les transactions GLPI en production ; nous avons vu un seul panneau avec un JOIN mal écrit dégrader le temps de réponse de tout le centre de support.

Étape 2 - isoler par entité sans divulguer les sous-entités

Chaque client est une entité dans GLPI, et l'instinct est de filtrer par entities_id = X. C'est là que se cache le piège le plus courant : si le client a des filiales ou des services en tant que sous-entités, ce filtre ne capture que l'entité racine et les chiffres ressortent plus bas que la réalité. Il faut inclure tout le sous-arbre. Sous MariaDB 10.2.2+, une CTE récursive fait l'affaire :

-- Toutes les entités du client (racine + descendants)
WITH RECURSIVE arbre_client AS (
    SELECT id FROM glpi_entities WHERE id = 4        -- entité racine du client
    UNION ALL
    SELECT e.id
    FROM glpi_entities e
    JOIN arbre_client a ON e.entities_id = a.id
)
SELECT
    COUNT(*)                       AS total,
    SUM(t.status IN (1,2,3,4))     AS ouverts,
    SUM(t.status IN (5,6))         AS clotures
FROM glpi_tickets t
WHERE t.entities_id IN (SELECT id FROM arbre_client)
  AND t.is_deleted = 0;

Notez le is_deleted = 0 : GLPI utilise la suppression logique et, sans ce filtre, les tickets à la corbeille sont quand même comptés. C'est la deuxième correction la plus fréquente que nous appliquons aux tableaux de bord hérités d'une autre équipe.

Étape 3 - le SLA sans gonfler le pourcentage

Le taux de respect du SLA semble trivial : résolu avant l'échéance, compté. Le détail trompeur se trouve dans le champ time_to_resolve (l'échéance de SLA enregistrée sur le ticket) : il vaut NULL quand aucun SLA n'est attaché au ticket. Si vous comparez solvedate <= time_to_resolve sans traiter le NULL, ces tickets disparaissent du dénominateur et le pourcentage monte artificiellement. Rendez la population explicite :

WITH RECURSIVE arbre_client AS (
    SELECT id FROM glpi_entities WHERE id = 4
    UNION ALL
    SELECT e.id FROM glpi_entities e
    JOIN arbre_client a ON e.entities_id = a.id
)
SELECT
    COUNT(*)                                                          AS avec_sla,
    SUM(t.solvedate <= t.time_to_resolve)                          AS dans_delais,
    ROUND(100 * SUM(t.solvedate <= t.time_to_resolve) / COUNT(*), 1) AS pct_sla
FROM glpi_tickets t
WHERE t.entities_id IN (SELECT id FROM arbre_client)
  AND t.is_deleted = 0
  AND t.time_to_resolve IS NOT NULL      -- uniquement les tickets avec SLA
  AND t.solvedate IS NOT NULL            -- déjà résolus
  AND t.date >= '2026-06-01'
  AND t.date <  '2026-07-01';

Quels indicateurs ont du sens pour le client

Tout ce que suit l'équipe interne n'intéresse pas - et ne doit pas être exposé - au client. Un ensemble restreint fonctionne mieux :

  • Opérationnel : tickets ouverts par statut et priorité, ouverts vs. clôturés sur la période, temps moyen de réponse et de résolution.
  • SLA : pourcentage dans les délais et tickets à risque (proches de l'échéance time_to_resolve).
  • Satisfaction : note moyenne de l'enquête (glpi_ticketsatisfactions) et tendance mensuelle.
  • Contrat : heures consommées vs. contractées et solde restant, lorsqu'il y a un forfait d'heures.

Où verrouiller le filtre : matrice de décision

Le point le plus délicat est d'empêcher le client de modifier le paramètre dans l'URL et de voir les données d'un autre. Chaque méthode applique le filtre à un endroit différent :

MéthodeOù le filtre est appliquéRisque de fuiteEffortQuand l'utiliser
Metabase - Lien publicParamètre modifiable dans l'URLÉlevéFaibleDémonstration interne uniquement, jamais avec des données client
Metabase - Embed signé (JWT)Paramètre verrouillé côté serveur (jeton)FaibleMoyenOption recommandée pour les clients
Grafana - Accès anonymeExpose toute l'organisationÉlevéFaibleÀ éviter
Grafana - Public Dashboards (9.1+)Un tableau de bord par lien, sans variable modifiableMoyenFaibleClient unique, sans sous-entités sensibles
Grafana - organisation par clientIsolé par org + proxyFaibleÉlevéNombreux clients, isolation forte

Le piège qui nous a coûté le plus cher (en exploitation)

Dans un environnement comptant des dizaines d'entités, le tableau de bord d'un client affichait environ la moitié des tickets indiqués par le rapport manuel. La cause n'était pas la requête de comptage : c'était le lien public de Metabase. Le filtre par entité était un paramètre de tableau de bord, et quiconque possédait le lien pouvait modifier la valeur dans l'URL et parcourir d'autres entités. Nous sommes passés à un embed signé (JWT), avec entities_id injecté dans le jeton et le paramètre marqué comme locked. Le client a perdu la possibilité de toucher au filtre et a obtenu un chiffre qui correspondait enfin au contrat. La leçon que nous emportons dans chaque projet : la sécurité du tableau de bord ne réside pas dans la requête, mais dans l'endroit où le filtre est appliqué.

Liste de contrôle de publication

  1. Utilisateur de base de données en lecture seule, de préférence pointant vers une réplique.
  2. Filtre par entité incluant le sous-arbre (CTE récursive ou liste d'ID).
  3. is_deleted = 0 sur chaque requête portant sur glpi_tickets.
  4. Période cohérente : choisissez une date (création, résolution ou clôture) et utilisez la même sur toutes les cartes.
  5. Filtre verrouillé côté serveur (embed signé), jamais uniquement dans l'URL.
  6. Testez le lien avec un utilisateur externe avant de l'envoyer au client.

Besoin de tableaux de bord client avec une isolation par entité inviolable ? NexTool déploie et maintient des environnements GLPI avec une BI intégrée (Metabase et Grafana) et une gouvernance des données conforme à la réglementation sur la protection des données. Découvrez notre service de support et de maintenance, ou consultez le comparatif Metabase vs Grafana et les KPI essentiels pour le service desk.


Ce contenu a été produit avec l'aide de l'intelligence artificielle et révisé par l'équipe Nextool Solutions.

Questions fréquentes

Filtrer uniquement par entities_id = X ne capture que l'entité racine. Pour inclure les filiales et les services, construisez le sous-arbre avec une CTE récursive sur glpi_entities (MariaDB 10.2.2+) et utilisez entities_id IN (SELECT id FROM arbre_client). Autre option : générer la liste d'ID dans l'application et la passer en paramètre.

Parce que time_to_resolve vaut NULL lorsque le ticket n'a pas de SLA attaché. Comparer solvedate <= time_to_resolve sans filtrer ces cas retire les tickets sans SLA du dénominateur et gonfle le pourcentage. Restreignez la population avec time_to_resolve IS NOT NULL.

Pas pour des données sensibles. Avec un lien public, le filtre par entité se trouve dans l'URL et peut être modifié, exposant les données d'autres clients. Utilisez un embed signé (JWT) avec le paramètre marqué comme locked et appliqué côté serveur. Le lien public ne convient qu'aux démonstrations internes.

Préférez une réplique en lecture seule. À défaut de réplique, créez au moins un utilisateur avec GRANT SELECT et définissez max_statement_time pour qu'une requête lourde de tableau de bord ne retienne pas les transactions GLPI en production.

Cela dépend de l'indicateur. Pour le volume entrant, utilisez date (création). Pour la productivité et le SLA, utilisez solvedate (résolution) ou closedate (clôture). L'erreur courante est de les mélanger : compter les résolus par date de création sous-estime le résultat du mois.

Comparez solvedate à time_to_resolve sur le ticket : SUM(solvedate <= time_to_resolve) sur le total avec SLA. Filtrez is_deleted = 0, time_to_resolve IS NOT NULL et solvedate IS NOT NULL pour que le dénominateur soit correct.

Besoin d'aide ?