A transparência gera confiança, mas um dashboard mal isolado transforma-se num incidente de proteção de dados. Partilhar indicadores de atendimento com o cliente reduz os e-mails de cobrança e demonstra profissionalismo. O desafio técnico não é desenhar o gráfico: é garantir que cada cliente veja apenas os próprios pedidos. Veja como o fazer com GLPI + Metabase ou Grafana, com o filtro por entidade bloqueado no sítio certo.
Antes de expor: três decisões que definem tudo
Na sustentação de ambientes GLPI multicliente, três escolhas decidem se o dashboard será uma vantagem ou um problema de segurança:
- Onde bloquear o filtro por entidade: na ligação, na consulta ou no embed assinado. Só o último resiste à adulteração do URL.
- Que data usar no período: criação (
date), resolução (solvedate) ou fecho (closedate). Misturar as três distorce todos os indicadores. - De onde ler os dados: a base primária ou uma réplica só-leitura. O Metabase e o Grafana disparam consultas pesadas e competem com o GLPI em produção.
Passo 1 - utilizador de base de dados só-leitura
Nunca aponte a ferramenta de BI para o utilizador do GLPI. Crie um utilizador dedicado com SELECT e nada mais. Em MariaDB:
-- Utilizador só-leitura para o BI, restrito à rede interna
CREATE USER 'bi_ro'@'10.20.%' IDENTIFIED BY '...palavra_passe_forte...';
GRANT SELECT ON glpi.* TO 'bi_ro'@'10.20.%';
-- O limite de tempo por consulta evita que um dashboard bloqueie a base (MariaDB)
SET GLOBAL max_statement_time = 15; -- segundos
FLUSH PRIVILEGES;
Se possível, aponte o BI para uma réplica de leitura em vez da base primária. Assim, um filtro esquecido num gráfico não prende transações do GLPI em produção; já vimos um único painel com um JOIN mal escrito degradar o tempo de resposta de todo o help desk.
Passo 2 - isolar por entidade sem expor sub-entidades
Cada cliente é uma entidade no GLPI, e o instinto é filtrar por entities_id = X. Aqui está a armadilha mais comum: se o cliente tem filiais ou departamentos como sub-entidades, esse filtro capta apenas a entidade-raiz e os números ficam menores do que a realidade. É preciso incluir toda a subárvore. Em MariaDB 10.2.2+ resolve-se com uma CTE recursiva:
-- Todas as entidades do cliente (raiz + descendentes)
WITH RECURSIVE arvore_cliente AS (
SELECT id FROM glpi_entities WHERE id = 4 -- entidade-raiz do cliente
UNION ALL
SELECT e.id
FROM glpi_entities e
JOIN arvore_cliente a ON e.entities_id = a.id
)
SELECT
COUNT(*) AS total,
SUM(t.status IN (1,2,3,4)) AS abertos,
SUM(t.status IN (5,6)) AS fechados
FROM glpi_tickets t
WHERE t.entities_id IN (SELECT id FROM arvore_cliente)
AND t.is_deleted = 0;
Repare no is_deleted = 0: o GLPI usa eliminação lógica e, sem esse filtro, os pedidos no lixo entram na contagem. É o segundo erro que mais corrigimos em dashboards herdados de outra equipa.
Passo 3 - SLA sem inflacionar a percentagem
A taxa de cumprimento do SLA parece trivial: resolveu antes do prazo, contou. O detalhe que engana está no campo time_to_resolve (o prazo de SLA gravado no pedido): é NULL quando nenhum SLA está associado ao pedido. Se comparar solvedate <= time_to_resolve sem tratar o NULL, esses pedidos desaparecem do denominador e a percentagem sobe artificialmente. Deixe a população explícita:
WITH RECURSIVE arvore_cliente AS (
SELECT id FROM glpi_entities WHERE id = 4
UNION ALL
SELECT e.id FROM glpi_entities e
JOIN arvore_cliente a ON e.entities_id = a.id
)
SELECT
COUNT(*) AS com_sla,
SUM(t.solvedate <= t.time_to_resolve) AS no_prazo,
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 arvore_cliente)
AND t.is_deleted = 0
AND t.time_to_resolve IS NOT NULL -- só pedidos com SLA
AND t.solvedate IS NOT NULL -- já resolvidos
AND t.date >= '2026-06-01'
AND t.date < '2026-07-01';
Que indicadores fazem sentido para o cliente
Nem tudo o que a equipa interna acompanha interessa - ou deve ser exposto - ao cliente. Um conjunto enxuto funciona melhor:
- Operacional: pedidos abertos por estado e prioridade, abertos vs. fechados no período, tempo médio de resposta e de resolução.
- SLA: percentagem dentro do prazo e pedidos em risco (próximos do vencimento de
time_to_resolve). - Satisfação: nota média do inquérito (
glpi_ticketsatisfactions) e tendência mensal. - Contrato: horas consumidas vs. contratadas e saldo, quando há bolsa de horas.
Onde bloquear o filtro: matriz de decisão
O ponto mais delicado é impedir que o cliente troque o parâmetro no URL e veja dados de outro. Cada método aplica o filtro num sítio diferente:
| Método | Onde o filtro é aplicado | Risco de fuga | Esforço | Quando usar |
|---|---|---|---|---|
| Metabase - Ligação pública | Parâmetro editável no URL | Alto | Baixo | Só demonstração interna, nunca com dados de cliente |
| Metabase - Embed assinado (JWT) | Parâmetro bloqueado no servidor (token) | Baixo | Médio | Opção recomendada para cliente |
| Grafana - Acesso anónimo | Expõe toda a organização | Alto | Baixo | Evitar |
| Grafana - Public Dashboards (9.1+) | Um dashboard por ligação, sem variável editável | Médio | Baixo | Cliente único, sem sub-entidades sensíveis |
| Grafana - organização por cliente | Isolado por org + proxy | Baixo | Alto | Muitos clientes, isolamento forte |
A armadilha que mais nos custou (na sustentação)
Num ambiente com dezenas de entidades, o dashboard de um cliente mostrava cerca de metade dos pedidos que o relatório manual apontava. A causa não era a consulta de contagem: era a ligação pública do Metabase. O filtro por entidade estava como parâmetro de dashboard, e qualquer pessoa com a ligação conseguia editar o valor no URL e navegar por outras entidades. Passámos para um embed assinado (JWT), com o entities_id injetado no token e o parâmetro marcado como locked. O cliente perdeu a capacidade de mexer no filtro e ganhou um número que finalmente coincidia com o contrato. A lição que levamos para todos os projetos: a segurança do dashboard não vive na consulta, vive em onde o filtro é aplicado.
Lista de verificação de publicação
- Utilizador de base de dados só-leitura, de preferência a apontar para uma réplica.
- Filtro por entidade a incluir a subárvore (CTE recursiva ou lista de IDs).
is_deleted = 0em toda a consulta sobreglpi_tickets.- Período coerente: escolha uma data (criação, resolução ou fecho) e use a mesma em todos os cartões.
- Filtro bloqueado no servidor (embed assinado), nunca apenas no URL.
- Teste da ligação com um utilizador externo antes de a enviar ao cliente.
Precisa de dashboards de cliente com isolamento por entidade à prova de adulteração? A NexTool implementa e sustenta ambientes GLPI com BI integrado (Metabase e Grafana) e governação de dados alinhada com a legislação de proteção de dados. Conheça o serviço de suporte e sustentação, ou veja o comparativo Metabase vs Grafana e os KPIs essenciais para Service Desk.
Este conteúdo foi produzido com o auxílio de inteligência artificial e revisto pela equipa Nextool Solutions.