Dashboard Partilhado com o Cliente no GLPI

Como criar e partilhar dashboards de atendimento com os seus clientes usando Metabase ou Grafana sobre o GLPI, com filtro por entidade (incluindo sub-entidades), cálculo correto do SLA na base de dados e acesso à prova de adulteração.

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étodoOnde o filtro é aplicadoRisco de fugaEsforçoQuando usar
Metabase - Ligação públicaParâmetro editável no URLAltoBaixoSó demonstração interna, nunca com dados de cliente
Metabase - Embed assinado (JWT)Parâmetro bloqueado no servidor (token)BaixoMédioOpção recomendada para cliente
Grafana - Acesso anónimoExpõe toda a organizaçãoAltoBaixoEvitar
Grafana - Public Dashboards (9.1+)Um dashboard por ligação, 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 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

  1. Utilizador de base de dados só-leitura, de preferência a apontar para uma réplica.
  2. Filtro por entidade a incluir a subárvore (CTE recursiva ou lista de IDs).
  3. is_deleted = 0 em toda a consulta sobre glpi_tickets.
  4. Período coerente: escolha uma data (criação, resolução ou fecho) e use a mesma em todos os cartões.
  5. Filtro bloqueado no servidor (embed assinado), nunca apenas no URL.
  6. 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.

Perguntas Frequentes

Filtrar apenas por entities_id = X capta 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). Em alternativa, gere a lista de IDs na aplicação e passe-a como parâmetro.

Porque time_to_resolve é NULL quando o pedido não tem SLA associado. Ao comparar solvedate <= time_to_resolve sem filtrar esses casos, os pedidos sem SLA saem do denominador e inflacionam a percentagem. Restrinja a população com time_to_resolve IS NOT NULL.

Não para dados sensíveis. Na ligação pública, o filtro por entidade fica no URL e pode ser editado, expondo dados de outros clientes. Use um embed assinado (JWT) com o parâmetro marcado como locked e aplicado no servidor. A ligação pública serve apenas para demonstração interna.

Prefira uma réplica só-leitura. Se não houver réplica, crie pelo menos um utilizador com GRANT SELECT e defina max_statement_time para que uma consulta pesada de dashboard não 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 (resolução) ou closedate (fecho). O erro comum é misturar: contar resolvidos pela data de criação subestima o resultado do mês.

Compare solvedate com time_to_resolve no pedido: 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?