Dashboard Compartilhado com o Cliente no GLPI

Como criar e compartilhar dashboards de atendimento com seus clientes usando Metabase ou Grafana sobre o GLPI, com filtro por entidade (incluindo sub-entidades), SLA correto no banco e acesso à prova de adulteração.

Transparência gera confiança - mas um dashboard mal isolado vira incidente de LGPD. Compartilhar indicadores de atendimento com o cliente reduz cobranças por e-mail e demonstra profissionalismo. O desafio técnico não é desenhar o gráfico: é garantir que cada cliente veja apenas os próprios chamados. Veja como fazer isso com GLPI + Metabase ou Grafana, com o filtro por entidade travado no lugar certo.

Antes de expor: três decisões que definem tudo

Na sustentação de ambientes GLPI multi-cliente, três escolhas decidem se o dashboard será um diferencial ou um problema de segurança:

  • Onde travar o filtro por entidade: no link, na query ou no embed assinado. Só o último resiste à adulteração de URL.
  • Qual data usar no período: criação (date), solução (solvedate) ou fechamento (closedate). Misturar as três distorce todos os indicadores.
  • De onde ler os dados: banco primário ou réplica somente-leitura. Metabase e Grafana disparam consultas pesadas e competem com o GLPI em produção.

Passo 1 - usuário de banco somente-leitura

Nunca aponte a ferramenta de BI para o usuário do GLPI. Crie um usuário dedicado com SELECT e nada além disso. Em MariaDB:

-- Usuário somente-leitura para o BI, restrito à rede interna
CREATE USER 'bi_ro'@'10.20.%' IDENTIFIED BY '...senha_forte...';
GRANT SELECT ON glpi.* TO 'bi_ro'@'10.20.%';

-- Limite de tempo por consulta evita que um dashboard trave o banco (MariaDB)
SET GLOBAL max_statement_time = 15;   -- segundos

FLUSH PRIVILEGES;

Se possível, aponte o BI para uma réplica de leitura em vez do banco primário. Assim, um filtro esquecido em um gráfico não segura transações do GLPI em produção - já vimos um único painel com JOIN mal escrito degradar o tempo de resposta do help desk inteiro.

Passo 2 - isolar por entidade sem vazar sub-entidades

Cada cliente é uma entidade no GLPI, e o instinto é filtrar por entities_id = X. Aqui mora a armadilha mais comum: se o cliente tem filiais ou departamentos como sub-entidades, esse filtro captura só a entidade-raiz e os números ficam menores 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 encerrados
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 exclusão lógica e, sem esse filtro, chamados na lixeira entram na contagem. É o segundo erro que mais corrigimos em dashboards herdados de outra equipe.

Passo 3 - SLA sem inflar o percentual

A taxa de cumprimento de SLA parece trivial: solucionou antes do prazo, contou. O detalhe que engana está no campo time_to_resolve (o prazo de SLA gravado no ticket): ele é NULL quando nenhum SLA está anexado ao chamado. Se você comparar solvedate <= time_to_resolve sem tratar o NULL, esses chamados somem do denominador e o percentual 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ó chamados com SLA
  AND t.solvedate IS NOT NULL            -- já solucionados
  AND t.date >= '2026-06-01'
  AND t.date <  '2026-07-01';

Quais indicadores fazem sentido para o cliente

Nem tudo que a equipe interna acompanha interessa - ou deve ser exposto - ao cliente. Um conjunto enxuto funciona melhor:

  • Operacional: chamados abertos por status e prioridade, abertos vs. encerrados no período, tempo médio de resposta e de resolução.
  • SLA: percentual dentro do prazo e chamados em risco (próximos do vencimento de time_to_resolve).
  • Satisfação: nota média da pesquisa (glpi_ticketsatisfactions) e tendência mensal.
  • Contrato: horas consumidas vs. contratadas e saldo, quando há bolsa de horas.

Onde travar o filtro: matriz de decisão

O ponto mais delicado é impedir que o cliente troque o parâmetro na URL e enxergue dados de outro. Cada método resolve o filtro em um lugar diferente:

MétodoOnde o filtro é aplicadoRisco de vazamentoEsforçoQuando usar
Metabase - Link públicoParâmetro editável na URLAltoBaixoSó demonstração interna, nunca com dados de cliente
Metabase - Embed assinado (JWT)Parâmetro travado no servidor (token)BaixoMédioPadrão recomendado para cliente
Grafana - Acesso anônimoExpõe toda a organizaçãoAltoBaixoEvitar
Grafana - Public Dashboards (9.1+)Um dashboard por link, sem variável editávelMédioBaixoCliente único, sem sub-entidades sensíveis
Grafana - organização por clienteIsolado por org + proxyBaixoAltoMuitos clientes, isolamento forte

A armadilha que mais custou caro (na sustentação)

Em um ambiente com dezenas de entidades, o dashboard de um cliente exibia cerca de metade dos chamados que o relatório manual apontava. A causa não era a consulta de contagem - era o link público do Metabase. O filtro por entidade estava como parâmetro de dashboard, e qualquer pessoa com o link conseguia editar o valor na URL e navegar por outras entidades. Trocamos por 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 batia com o contrato. A lição que levamos para todo projeto: a segurança do dashboard não mora na query, mora em onde o filtro é resolvido.

Checklist de publicação

  1. Usuário de banco somente-leitura, de preferência apontando para réplica.
  2. Filtro por entidade incluindo a subárvore (CTE recursiva ou lista de IDs).
  3. is_deleted = 0 em toda consulta sobre glpi_tickets.
  4. Período coerente: escolha uma data (criação, solução ou fechamento) e use a mesma em todos os cartões.
  5. Filtro travado no servidor (embed assinado), nunca apenas na URL.
  6. Teste do link com um usuário externo antes de enviar ao cliente.

Precisa de dashboards de cliente com isolamento por entidade à prova de vazamento? A NexTool implanta e sustenta ambientes GLPI com BI integrado (Metabase e Grafana) e governança de dados alinhada à LGPD. 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 auxílio de inteligência artificial e revisado pela equipe Nextool Solutions.

Perguntas Frequentes

Filtrar apenas por entities_id = X pega só a entidade-raiz. Para incluir filiais e departamentos, monte a subárvore com uma CTE recursiva sobre glpi_entities (MariaDB 10.2.2+) e use entities_id IN (SELECT id FROM arvore_cliente). Como alternativa, gere a lista de IDs no aplicativo e passe como parâmetro.

Porque time_to_resolve é NULL quando o chamado não tem SLA anexado. Ao comparar solvedate <= time_to_resolve sem filtrar esses casos, os chamados sem SLA saem do denominador e inflam o percentual. Restrinja a população com time_to_resolve IS NOT NULL.

Não para dados sensíveis. No link público, o filtro por entidade fica na URL e pode ser editado, expondo dados de outros clientes. Use embed assinado (JWT) com o parâmetro marcado como locked, resolvido no servidor. O link público serve só para demonstração interna.

Prefira uma réplica somente-leitura. Se não houver réplica, crie ao menos um usuário com GRANT SELECT e defina max_statement_time para evitar que uma consulta pesada de dashboard prenda transações do GLPI em produção.

Depende do indicador. Para volume de entrada, use date (criação). Para produtividade e SLA, use solvedate (solução) ou closedate (fechamento). O erro comum é misturar: contar resolvidos pela data de criação subestima o resultado do mês.

Compare solvedate com time_to_resolve no chamado: SUM(solvedate <= time_to_resolve) sobre o total com SLA. Filtre is_deleted = 0, time_to_resolve IS NOT NULL e solvedate IS NOT NULL para o denominador ficar correto.

Precisa de ajuda?