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éthode | Où le filtre est appliqué | Risque de fuite | Effort | Quand l'utiliser |
|---|---|---|---|---|
| Metabase - Lien public | Paramètre modifiable dans l'URL | Élevé | Faible | Démonstration interne uniquement, jamais avec des données client |
| Metabase - Embed signé (JWT) | Paramètre verrouillé côté serveur (jeton) | Faible | Moyen | Option recommandée pour les clients |
| Grafana - Accès anonyme | Expose toute l'organisation | Élevé | Faible | À éviter |
| Grafana - Public Dashboards (9.1+) | Un tableau de bord par lien, sans variable modifiable | Moyen | Faible | Client unique, sans sous-entités sensibles |
| Grafana - organisation par client | Isolé par org + proxy | Faible | É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
- Utilisateur de base de données en lecture seule, de préférence pointant vers une réplique.
- Filtre par entité incluant le sous-arbre (CTE récursive ou liste d'ID).
is_deleted = 0sur chaque requête portant surglpi_tickets.- Période cohérente : choisissez une date (création, résolution ou clôture) et utilisez la même sur toutes les cartes.
- Filtre verrouillé côté serveur (embed signé), jamais uniquement dans l'URL.
- 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.