Workspaces: dashboards do DATTABI e filtro por contexto
Visão geral
Cada workspace do DATTA agrupa um conjunto de funcionalidades habilitadas, uma lista de painéis vinculados e uma lista de contextos (domínios/bases). Esta entrega adiciona duas capacidades ao workspace:
- Dashboards do DATTABI no gerenciador e no menu — os dashboards de BI criados no DATTABI passam a aparecer no catálogo de painéis, podem ser vinculados a um workspace pelo gerenciador e viram botões no grupo do workspace no menu lateral.
- Contextos filtrando os dados das funcionalidades — ao abrir uma funcionalidade ciente de contexto a partir do grupo de um workspace, o shell propaga os contextos do workspace para a funcionalidade, que passa a filtrar seus dados por esses contextos.
Problema
- Os dashboards do DATTABI viviam isolados na página a tela do DATTABI; não havia como expô-los como atalho dentro do menu de um workspace nem reutilizá-los junto das demais funcionalidades (chat, painel, investigação, triagem).
- Um workspace pode ter vários contextos, mas as funcionalidades abertas a partir dele mostravam todos os dados de todos os contextos. O usuário precisava filtrar manualmente o domínio em cada tela, sem ganho do agrupamento por workspace.
Solução
- O catálogo de painéis (
GET /api/config/panels) passou a incluir, além dos painéis nativos e de canvas, os dashboards do DATTABI de cada workspace, obtidos via chamada service-to-service (S2S) aodattabi-service. - O gerenciador de workspaces (
/configurar/workspaces) exibe esses dashboards na ação Vincular existentes, e o menu lateral renderiza como botões os dashboards cujopanelRefestá persistido (abrindo a tela do DATTABI). A persistência do vínculo do dashboard viapanelReftem uma limitação de backend conhecida — ver Limitações conhecidas. - O shell propaga os contextos do workspace via querystring
?domain=ctx1,ctx2ao abrir uma funcionalidade ciente de contexto, e cada funcionalidade lê esse parâmetro para filtrar/pré-selecionar seus dados.
Como funciona (arquitetura + fluxo de dados)
Item 2 — Dashboards do DATTABI no catálogo, no gerenciador e no menu
Backend (catálogo de painéis). A API do catálogo de painéis do config-service serve GET /api/config/panels — um catálogo achatado dos painéis da plataforma visíveis ao usuário (ver Visibilidade por membros). Além dos painéis nativos (native-view) e de canvas de cada workspace, ele agora agrega os dashboards do DATTABI:
- Para cada workspace, faz uma chamada S2S ao
dattabi-serviceemGET /api/dattabi/dashboards?workspaceId=<id>usandoWebClient. - As chamadas por workspace rodam em paralelo, com concorrência 6 (
Flux.fromIterable(workspaces).flatMap(this::dattabiDashboards, 6), seguindo as Diretrizes do Projeto §19. - Cada chamada é best-effort: timeout de 6s e
onErrorResumeque devolve lista vazia, de modo que a indisponibilidade dos dashboards de um workspace não derruba o catálogo inteiro. - Cada dashboard não-arquivado vira uma entrada do catálogo no formato:
{
"workspaceId": "<id-do-workspace>",
"workspaceName": "<nome>",
"panelId": "<dashboardId>",
"title": "<titulo>",
"description": "<opcional>",
"kind": "dattabi-dashboard",
"dashboardId": "<dashboardId>",
"url": "/dattabi.html?dashId=<dashboardId>",
"native": false
}.
Backend (fonte dos dashboards). O dattabi-service expõe GET /api/dattabi/dashboards?workspaceId=<id>. O endpoint exige DATTABI_VIEW, lista os dashboards do workspace (parâmetro includeArchived=false por padrão) e, defensivamente, retorna lista vazia quando o workspaceId vem ausente/em branco.
Frontend (gerenciador). No gerenciador de workspaces (screens/settings-workspaces.html, aba Painéis → Vincular existentes), o catálogo é carregado e filtrado para a lista de opções vinculáveis:
// screens/settings-workspaces.html
const options = catalog.filter(
c => c.workspaceId !== selected.id || c.kind === 'dattabi-dashboard'
);Ou seja: painéis de canvas continuam linkáveis apenas de outros workspaces (o próprio já aparece), mas dashboards do DATTABI (kind === 'dattabi-dashboard') ficam linkáveis de qualquer workspace, inclusive o próprio. A seleção é salva como panelRef via PUT /api/config/workspaces/{id}/panel-refs (screens/settings-workspaces.html).
Como esses vínculos são referências (panelRef), eles aparecem na seção Painéis vinculados da lista, com o badge genérico Vinculado e o botão Abrir (que chama openRef → abre /dattabi.html?dashId=<id> em nova aba) (screens/settings-workspaces.html). O badge Dashboard BI e o botão Abrir dashboard (screens/settings-workspaces.html) são usados apenas para painéis próprios do workspace cujo config.kind já é dattabi-dashboard — um caminho distinto do vínculo por panelRef.
Frontend (menu lateral). O shell (compiled/index.js) carrega /api/config/workspaces e /api/config/panels em paralelo com Promise.all (index.js:746-756) e indexa o catálogo por chave workspaceId::panelId. Ao montar o grupo de cada workspace, resolve os panelRefs no catálogo e renderiza os do tipo dattabi-dashboard como botões:
// index.js:2105-2107
const dashRefs = (ws.panelRefs || [])
.map(r => panelCatalog[r.workspaceId + '::' + r.panelId])
.filter(p => p && p.kind === 'dattabi-dashboard');Clicar no botão abre /dattabi.html?dashId=<dashboardId> em uma nova aba (index.js:2117-2126).
Item 9 — Contextos do workspace filtram as funcionalidades
Propagação no shell. O shell mantém um estado panelDomain (index.js:705). Ao clicar em uma funcionalidade dentro do grupo de um workspace, ele define panelDomain com os contextos do workspace concatenados por vírgula e navega para a funcionalidade:
// index.js:2115
onClick: () => { setPanelDomain((ws.contexts || []).join(',')); navigateTo(fk); setSidebarOpen(false); }Quando a funcionalidade é renderizada como iframe, o shell adiciona o parâmetro domain na src apenas se a funcionalidade for ciente de contexto e houver panelDomain:
// index.js:2602
src: WS_CONTEXT_AWARE.has(key) && panelDomain
? src + (src.includes('?') ? '&' : '?') + 'domain=' + encodeURIComponent(panelDomain)
: src,O conjunto de funcionalidades cientes de contexto é fixo (index.js:639-642):
var WS_CONTEXT_AWARE = new Set([
'dashboard', 'chat', 'investigate', 'upload', 'analytics',
'triagem-lote', 'regras-triagem'
]);Consumo nas funcionalidades. Cada funcionalidade lê ?domain da URL e usa os contextos para filtrar seus dados:
- Painel/Processos (
dashboard), Chat (chat), Investigação (investigate), Upload (upload) e Casos (analytics) já liam?domaine passaram a filtrar/usar os contextos do workspace. Desde 2026-09-28 a Investigação é o DATTA Investigation, o Chat no modo da pesquisa profunda: o escopo sai do seletor de workspace da própria barra, como no Chat. - Regras (
regras-triagem): filtra as regras pelo primeiro contexto do workspace; sem?domainmostraTodos(compiled/regras_triagem.js:788-791):
const wsCtx = (() => { try {
return (new URLSearchParams(window.location.search).get('domain') || '').split(',')[0].trim();
} catch (e) { return ''; } })();
const [contexto, setContexto] = useState(wsCtx || 'Todos');- Triagem em Lote (
triagem-lote): pré-seleciona apenas os contextos do workspace (intersecção com os contextos disponíveis); sem?domainseleciona todos, mantendo o comportamento padrão (compiled/triagem_lote.js:132-137):
const ws = (new URLSearchParams(window.location.search).get('domain') || '')
.split(',').map(s => s.trim()).filter(Boolean);
const wsValid = ws.filter(c => ctxs.includes(c));
setSelectedContexts(wsValid.length ? wsValid : ctxs);APIs / contratos
GET /api/config/panels (config-service)
Catálogo achatado dos painéis da plataforma visíveis ao usuário (workspaces públicos + aqueles de que ele é membro; admin vê todos — ver Visibilidade por membros). Resposta: array de objetos. As entradas de dashboard do DATTABI têm kind: "dattabi-dashboard" com dashboardId e url (ver formato acima). Permissão: PANEL_VIEW ou CONFIG_VIEW (any-of).
GET /api/dattabi/dashboards?workspaceId=<id> (dattabi-service)
Lista os dashboards de um workspace. Parâmetros:
| Parâmetro | Tipo | Default | Descrição |
|---|---|---|---|
workspaceId | string | — | Obrigatório (semanticamente); vazio → [] |
includeArchived | boolean | false | Inclui dashboards arquivados |
Permissão: DATTABI_VIEW. Chamado em S2S pelo config-service.
PUT /api/config/workspaces/{id}/panel-refs (config-service)
Define a lista de painéis existentes vinculados ao workspace. Corpo:
{ "panelRefs": [ { "workspaceId": "<id>", "panelId": "<id>" } ] }Permissão: CONFIG_EDIT. O serviço valida cada referência: aceita apenas painel nativo (API do catálogo de painéis) ou painel canvas existente em outro workspace (store.find(workspaceId).panels()); qualquer outra referência retorna 400 Bad Request com {"error":"Painel referenciado não existe: ..."}. Além disso, descarta silenciosamente referências cujo workspaceId seja o do próprio workspace.
Atenção (gap atual): dashboards do DATTABI vêm do catálogo via S2S e não são
Workspace.Panelpersistidos — logo osetPanelRefsatual não os reconhece como referência válida (rejeita os de outro workspace com 400 e descarta os do próprio). Veja Limitações conhecidas.
RBAC / permissões
| Ação | Permissão exigida | Onde é checada |
|---|---|---|
| Ler o catálogo de painéis | PANEL_VIEW ou CONFIG_VIEW | API do catálogo de painéis — @RequirePermission via checagem de permissão declarada (retorno Mono, ramo reativo). O SecurityWebFilterChain só exige .authenticated() para esse GET. |
| Listar dashboards de um workspace | DATTABI_VIEW | API de dashboards — @RequirePermission via checagem de permissão declarada (retorno Mono). O filtro do dattabi-service só exige .authenticated() em /api/dattabi/**. |
| Vincular painéis/dashboards ao workspace | CONFIG_EDIT | Dupla camada: o filtro de segurança exige CONFIG_EDIT ou ROLE_ADMIN em todo PUT /api/config/**, e a permissão declarada CONFIG_EDIT é cobrada de novo na vinculação de painéis. |
| Alterar um workspace (nome, funcionalidades, contextos, painéis, vínculos) | CONFIG_EDIT e nível Pode editar ou acima no workspace | CONFIG_EDIT pela permissão declarada; o nível do membro pelo serviço de configuração, em cada rota (ver Níveis de permissão do membro). |
| Gerenciar membros e excluir o workspace | CONFIG_EDIT e nível Pode gerenciar membros no workspace | Idem. |
| Listar os usuários cadastrados (escolha do membro) | CONFIG_EDIT | GET /api/config/workspaces/usuarios-cadastrados — permissão declarada (retorno Mono). |
Nota sobre o ponto de enforcement. Nesses três endpoints a checagem granular de permissão roda na checagem de permissão declarada (
datta-common), que só enforça quando o método retornaMono/Flux(ramo reativo, viaReactiveSecurityContextHolder). Os três métodos retornamMono, então a anotação vale. Para métodos de retorno blocking em WebFlux o aspecto não bloqueia (thread-local vazio) e a autorização recai noSecurityWebFilterChain— por isso as mutações de config também têm o path-matcher por permissão noSecurityConfig.
A chamada S2S do config-service ao dattabi-service usa um JWT interno (InternalJwtProvider) emitido com as authorities DATTABI_VIEW e ADMIN, satisfazendo tanto o .authenticated() do filtro do dattabi-service quanto o @RequirePermission("DATTABI_VIEW") checado pelo aspecto. O frontend é apenas dica visual; a barreira de segurança permanece no backend (Diretrizes do Projeto §13).
Visibilidade por membros (escopo, Diretrizes do Projeto §13)
Ter CONFIG_VIEW ou PANEL_VIEW autoriza ler workspaces, não autoriza ler todos os workspaces. Além da permissão, todo acesso passa pelo escopo de membership (WorkspaceAccess, config-service):
| Quem | O que enxerga |
|---|---|
Administrador (ADMIN/SYSTEM_ADMIN) e serviços internos (S2S) | Todos os workspaces |
| Qualquer usuário autenticado com permissão de leitura | Workspaces públicos: os sem membros (é o caso dos seeds Processos e Assistência Social) e os cujos membros são todos administradores da plataforma |
| Usuário listado na aba Membros, e o dono do workspace | Os workspaces restritos de que participa, além dos públicos |
Administrador na lista não restringe (decisão de 2026-09-27). O administrador da plataforma já enxerga todos os workspaces; listá-lo como membro não dá acesso a ninguém, e antes disso só fechava o workspace para o resto da plataforma — foi o que aconteceu com "Processo Trabalhista" e "Contratos", cujo único membro era o usuário admin. Um workspace passa a ser restrito quando tem ao menos um membro que não é administrador.
Quem sabe se o membro é administrador é o config-service: ao adicionar o membro, ele procura o usuário no cadastro (o mesmo grafo de usuários de onde sai o token) e grava a marcação adminPlataforma no próprio registro do membro. A marcação é reconciliada na subida do serviço e a cada minuto, e é assim que o dado já gravado — como os dois workspaces acima — e as promoções e rebaixamentos de papel chegam à regra sem ninguém editar o workspace. Um rebaixamento leva até esse intervalo (mais o cache de 60 s do bpmn-service) para voltar a restringir. Com o cadastro indisponível, nenhuma marcação muda.
A MESMA decisão vale em todos os lugares que recortam por workspace: o config-service a aplica na lista de workspaces e no catálogo de painéis; o bpmn-service lê o mesmo JSON, com a mesma marcação, e aplica a mesma regra (VisibilidadeWorkspace, em datta-common); o graph-data-service e o chat-service pedem o workspace (ou a lista) com o token do usuário e recebem a decisão pronta.
O membro é gravado pelo e-mail do usuário cadastrado: a API recusa (400) um identificador que não corresponde a usuário nenhum — antes, qualquer texto era gravado e fechava o workspace sem dar acesso a ninguém — e também o que corresponde a mais de um usuário (o e-mail de um e o login de outro): não há escolha silenciosa. Na comparação com o token, o backend aceita o e-mail e o id do usuário (registros antigos podem trazer o id), sem diferenciar maiúsculas de minúsculas — nunca o login, que o token não traz.
A marcação de administrador segue essa mesma regra: um membro conta como administrador só quando todos os usuários a quem ele dá acesso — pelo id ou pelo e-mail — são administradores. Um membro gravado pelo login de alguém não dá acesso a ninguém e conta como comum, em vez de herdar o papel do dono do login.
Um workspace restrito de que o usuário não participa responde 404, e não 403: confirmar a existência do recurso já seria um vazamento (o usuário descobriria os nomes/ids dos workspaces sigilosos das outras equipes). Por isso a mensagem na interface é deliberadamente ambígua — "Workspace não encontrado ou sem acesso". A negação é registrada na auditoria como AUTHZ.PERMISSION_DENIED com permission=WORKSPACE_MEMBERSHIP.
O escopo vale para todas as rotas do workspace, não só a leitura: alterar, excluir, mexer em painéis, contextos e membros de um workspace invisível também responde 404, mesmo com CONFIG_EDIT. A validação da ação Vincular existentes (PUT .../panel-refs) também é escopada: um painel de workspace invisível é recusado exatamente como um id inexistente, com a mesma mensagem — caso contrário a diferença entre as duas respostas revelaria, por tentativa e erro, quais workspaces restritos existem. E o catálogo de painéis (GET /api/config/panels) achata apenas os workspaces visíveis — sem isso, os títulos dos painéis e dashboards de um workspace restrito vazariam pela galeria e pelo popover da home. Os painéis nativos da plataforma são oferecidos a todos no catálogo completo, mas a galeria e o popover da home só mostram o painel nativo ligado a um workspace que o usuário enxerga — e o catálogo só lista, em contextosDeOrigem, os contextos dos workspaces que ele enxerga (quem edita a configuração vê todos).
Consequência prática. Enquanto a aba Membros de um workspace estiver vazia — ou só tiver administradores da plataforma —, ele continua visível para toda a plataforma. Para restringir um workspace, basta adicionar o primeiro membro que não é administrador: a partir daí ele some para quem não estiver na lista (exceto o dono e os administradores). Remover o último desses membros o torna público de novo — a tela avisa antes, nos dois sentidos.
Níveis de permissão do membro
Ver o workspace é decidido pela visibilidade acima; alterar é decidido pelo nível de cada membro, aplicado pelo serviço de configuração em toda rota de alteração (desde 29/09/2026 — antes o nível era gravado e não era conferido):
| Quem | O que pode alterar |
|---|---|
Membro Pode visualizar (viewer) | Nada: vê o workspace, os painéis, os contextos e os membros |
Membro Pode editar (editor) | Nome, descrição, funcionalidades, contextos, painéis e vínculos |
Membro Pode gerenciar membros (owner) | Tudo o que o editor altera, e também adiciona, muda o nível e remove membros, e exclui o workspace |
| Dono do workspace (quem o criou) e administrador da plataforma | Tudo, estejam ou não na lista de membros |
| Quem não é membro, num workspace público | O mesmo de antes: CONFIG_EDIT basta (workspace público não tem gestor) |
- O nível nunca muda quem vê o workspace. O nível de um membro nunca tira a visibilidade de outro membro: todo membro vê o workspace. O dono e o administrador da plataforma sempre o veem. Só quem pode gerenciar membros tira alguém da lista, e essa pessoa deixa de ver o workspace se ele continuar restrito. Remover o último membro que não é administrador o torna público de novo. Quem gerencia membros também pode excluir o workspace, e a exclusão é definitiva: ninguém o vê mais, nem o administrador.
- Nível insuficiente responde
403com a frase do que falta (o workspace já é visível para quem pediu, então não há o que esconder) e registra a negação na auditoria (AUTHZ.PERMISSION_DENIED,permission=WORKSPACE_EDITouWORKSPACE_MANAGE_MEMBERS). - A permissão da plataforma continua valendo por cima: sem
CONFIG_EDIT, nenhum nível altera nada. - O nível é conferido de novo no momento da gravação, sobre o workspace como ele está: quem foi rebaixado (ou removido) enquanto a própria alteração estava a caminho recebe
403(ou404, se deixou de ver o workspace) e nada é gravado. - O mesmo usuário em mais de uma entrada fica com o maior nível. O nível gravado vale mesmo quando as permissões gravadas no membro dizem mais; só o registro antigo sem nível é lido pelas permissões. Nível desconhecido vale como Pode visualizar, e a API recusa (
400) gravar um nível que não sejaviewer,editorouowner. As permissões gravadas no membro saem sempre do nível. - Membros gravados antes de 29/09/2026. A janela de adicionar membro abria com Pode visualizar já escolhido, e o nível não valia nada: o membro editava com
CONFIG_EDIT. Por decisão do usuário (29/09/2026), todo membro gravado como "Pode visualizar" até a subida desta versão é promovido UMA vez a "Pode editar" — inclusive os incluídos pela API sem o camporole, que o servidor antigo gravava comoviewer. A promoção roda uma única vez na subida do config-service, registra no log quantos membros subiram em cada workspace e grava o marcadorpromocao-viewer-editor.jsonao lado doworkspaces.json(sobrevive a restart e rollback); não toca em dono, "Pode editar", "Pode gerenciar membros" nem em administrador da plataforma, e não muda quem vê o workspace. Quem for incluído como "Pode visualizar" depois dela continua só-leitura. Marcador ilegível: a promoção não roda e avisa no log. Na subida do serviço, uma migração alinha as permissões gravadas ao nível e registra no log (workspace.member.reconciliado) cada membro cujas permissões diziam mais que o nível — é a lista para reclassificar. Para devolver a edição, o dono, quem pode gerenciar membros ou o administrador da plataforma muda o nível na aba Membros (ouPUT /api/config/workspaces/{id}/members/{userId}comrole). Em workspace sem dono ativo — os criados pela instalação têm o dono Sistema — só o administrador da plataforma faz isso. - A tela esconde o que o seu nível não permite — sem Editar e Excluir, sem salvar, sem criar ou remover painéis, sem Adicionar membro — e diz, no topo do workspace, qual é o seu nível.
Auditoria
- A vinculação de painéis usa o fluxo existente de mutação de workspace (
PUT .../panel-refs), coberto pela auditoria de mutações de configuração doconfig-service. - A criação/alteração/arquivamento de dashboards no DATTABI segue a auditoria do próprio
dattabi-service(versionamento via o serviço de versões de dashboards e invalidação de caches de copilot no save/archive/delete). A leitura (GET /api/dattabi/dashboards) não gera evento de mutação.
Cache
- A listagem de dashboards por workspace é consultada a cada montagem do catálogo de painéis. O caminho de leitura
GET /api/dattabi/dashboardsapoia-se no repositório do DATTABI; o catálogo em si não adiciona uma camada Valkey própria — a chamada S2S é paralelizada e protegida por timeout/best-effort. - O DATTABI mantém caches próprios de narrativa e sugestões por dashboard, invalidados em save/archive/delete (API de dashboards).
Observabilidade (OTel)
Backend e frontend já são instrumentados pelo padrão da plataforma (Diretrizes do Projeto §11):
- As chamadas HTTP de
GET /api/config/panels,GET /api/dattabi/dashboardsePUT .../panel-refsgeram spans de entrada (controllers) e a chamada S2S doconfig-serviceaodattabi-servicegera span de cliente HTTP, todos exportados aos índicesotel-v1-apm-span-*. - O shell (página
index.html) carrega o OTel browser SDK e propagatraceparentnas chamadasfetch()de/api/config/workspacese/api/config/panels.
Como usar (passo a passo do usuário)
O menu lateral mostra o workspace ativo
Com um workspace ativo (escolhido no card do topo do menu), o menu lateral mostra só as funcionalidades de trabalho marcadas nele — Regras, Triagem em Lote, Upload, Explorar Dados, DATTA Extract, DATTA BPM, DATTA BI e as demais do catálogo da aba Funcionalidades. Trocar de workspace troca a lista.
O mesmo filtro vale para os cartões da tela Features. Das 25 funcionalidades do catálogo, 14 não têm item próprio no menu — Knowledge Catalog, Linhagem, Casos, Alertas, Notebook, Agentes, Governança e outras — e só são alcançadas pela Features. O DATTA Investigation ganhou item próprio, logo abaixo do Chat, em 28/09/2026. Com o workspace ativo, a Features mostra só as marcadas (e diz de qual workspace é o filtro, com o atalho Gerenciar workspaces); as desmarcadas somem do menu e da Features. Painel de Processos e Onboarding PJ são painéis: aparecem na galeria Painéis quando ligados ao workspace na aba Painéis do gerenciador, e não no menu nem na Features.
As salvaguardas, todas valendo ao mesmo tempo:
- Plataforma aparece sempre: Chat, Sistema, DATTA Captain, Documentação, o perfil, Painéis, Features e a troca de workspace (o card do topo) não dependem do workspace. São plataforma, não trabalho de equipe — e filtrar o Sistema poderia trancar o administrador fora da própria configuração.
- O Chat é sempre visível (decisão de 29/09/2026): ele é a porta do DATTA Captain e do assistente, e um workspace que o desmarcasse tirava do menu a entrada pela qual se pergunta qualquer coisa à plataforma. Por isso saiu da aba Funcionalidades. A chave
chatgravada em workspaces antigos sai da lista na subida do serviço de configuração (e a API a aceita e descarta, sem400); um workspace cuja única funcionalidade marcada era o Chat fica sem nenhuma marcada — e mostra o menu inteiro, pela regra abaixo. O escopo do Chat pelo workspace ativo (os contextos do workspace) continua o mesmo. - "Todos os workspaces" mostra tudo: sem workspace ativo, nada é filtrado.
- Workspace sem nenhuma funcionalidade marcada mostra tudo: "nada marcado" é "não configurado", não "esconda tudo". O mesmo vale enquanto a lista de workspaces ainda não chegou (ou a chamada falhou).
- Funcionalidade que o catálogo não conhece aparece sempre: uma tela nova nunca some do menu por falta de cadastro.
- A permissão continua valendo por cima: o filtro do workspace só esconde, nunca libera. O menu não esconde itens pelo papel da pessoa: um item marcado no workspace aparece para todos os membros, e quem não tem a permissão é barrado pelo backend ao abrir a tela. Da mesma forma, esconder um item do menu não bloqueia o acesso por link. Quem decide o acesso é sempre o backend, pela permissão.
Um grupo (DATTA Extract, DATTA BPM, DATTA BI) aparece quando ao menos uma das suas funcionalidades está marcada, e mostra só as marcadas. No pior caso — um workspace com uma única funcionalidade que não tem item no menu, como o Knowledge Catalog — o menu fica com Painéis, Features, Documentação, Sistema e o card de troca de workspace, e a funcionalidade marcada está na Features; nunca sem saída.
Histórico. O menu já foi filtrado assim em 23/08/2026 e voltou atrás no mesmo dia: sem salvaguardas, um workspace com poucas funcionalidades deixava o menu com dois itens, sem Sistema e sem como trocar de workspace. O filtro voltou em 27/09/2026 com as salvaguardas acima, e um teste que renderiza o menu para cada workspace possível impede que aquele caso volte — e cobra que toda funcionalidade marcada apareça no menu ou na Features.
Para escolher o que cada workspace mostra, use a aba Funcionalidades da tela de gestão.
Criar um workspace
- Abra .
- Clique em Novo workspace, no topo da lista à esquerda.
- Informe o nome (obrigatório) e uma descrição (opcional) e confirme em Criar workspace.
O workspace nasce vazio e já fica selecionado: as abas à direita — Funcionalidades, Contextos de Dados, Painéis e Membros — são onde ele ganha conteúdo. Enquanto não tiver membros (ou só tiver administradores da plataforma), ele é público para quem enxerga a tela; ao ganhar o primeiro membro que não é administrador, passa a aparecer só para membros, dono e administradores.
O nome aceita até 120 caracteres e a descrição até 500 — a tela limita os campos e a API recusa (400) o que passar disso.
Exige a permissão CONFIG_EDIT.
Membros e visibilidade
A aba Membros mostra, antes da lista:
- a visibilidade do workspace — Público (todos os usuários com acesso de leitura o veem) ou Restrito (só o dono, os membros e os administradores da plataforma);
- o dono, que sempre vê o workspace, mesmo fora da lista. Workspace criado pela instalação aparece com o dono Sistema.
Cada membro aparece com o nome e o e-mail do cadastro de usuários; o administrador da plataforma aparece identificado como tal ("não restringe o workspace").
Para adicionar:
- Clique em Adicionar membro.
- Escolha o usuário na lista — ela começa em Selecione um usuário…, mostra o nome, o login e o e-mail de cada pessoa (e identifica os administradores da plataforma) e não traz quem já é membro. A lista é a dos usuários cadastrados na plataforma e vem para quem tem
CONFIG_EDIT, sem exigir a permissão de gestão de usuários. Numa instalação com mais de 2000 usuários, a janela mostra um campo Buscar por nome, login ou e-mail: a lista traz só os primeiros em ordem alfabética, e a busca alcança os demais. - Escolha o nível de permissão (ver Níveis de permissão do membro) e confirme.
Só entra como membro quem já está cadastrado na plataforma: a adição procura o usuário no cadastro e recusa (400) o que não encontra, com a mensagem "… não é um usuário cadastrado na plataforma, e só usuários cadastrados podem ser membros de um workspace". Com o cadastro de usuários fora do ar, a adição responde (503) que não foi possível consultá-lo, que o membro não foi adicionado e que vale tentar de novo em instantes; se a lista de usuários não carregar, a tela diz o motivo e oferece Tentar novamente em vez de um campo de texto que o servidor recusaria. Por isso, um colaborador que vai entrar por Microsoft ou Google só pode ser incluído depois do primeiro login dele — é esse login que cria o cadastro. Criar antes um usuário local com o mesmo e-mail não serve de atalho: a plataforma recusa depois o login pelo Microsoft/Google numa conta criada por outro método de acesso.
Um identificador que corresponde a mais de um usuário também é recusado — no cadastro real, admin é o e-mail de um usuário e o login de outro. A lista da tela envia o identificador único de cada pessoa, então quem escolhe pela lista não passa por isso; é o caso de quem chama a API diretamente.
Com o workspace público, a tela avisa antes que o primeiro membro que não é administrador o torna restrito — o botão passa a ser Adicionar e restringir. Adicionar um administrador não muda a visibilidade, e a tela diz isso. Ao remover o último membro que restringe, a tela avisa que o workspace volta a ser visível para todos, e o botão passa a ser Remover e tornar público.
Toda gravação da tela — funcionalidades, contextos, painéis, vínculos e membros — atualiza na hora o menu lateral e o escopo do workspace ativo, sem recarregar a página. Trocar de aba ou de workspace com alterações não salvas em Funcionalidades ou em Contextos de Dados pergunta antes o que fazer (continuar editando, descartar ou salvar agora).
Criar um painel próprio do workspace
- Selecione o workspace e abra a aba Painéis.
- Clique em Novo painel, informe o título e confirme em Criar e abrir.
O painel é criado vazio e abre no construtor de painéis em outra aba, onde os widgets e a fonte de dados são definidos. É diferente de Vincular existentes, que apenas traz para este workspace um painel que já existe em outro lugar — vincular não copia nem duplica.
Vincular um dashboard do DATTABI a um workspace
- Acesse Sistema → Workspaces (
/configurar/workspaces). - Selecione o workspace desejado e abra a aba Painéis.
- Clique em Vincular existentes.
- Na lista, marque os dashboards do DATTABI (e/ou painéis de outros workspaces) que deseja exibir e confirme. Os dashboards aparecem com a indicação de origem; os do próprio workspace também podem ser selecionados na UI.
- Os vínculos persistidos aparecem na seção Painéis vinculados com o badge Vinculado e o botão Abrir.
Limitação atual: o backend
setPanelRefsainda não reconhece dashboards do DATTABI como referência válida (rejeita os de outro workspace com400e descarta os do próprio). Até o backend aceitarkind == 'dattabi-dashboard', esse vínculo pode não persistir e o atalho no menu não se materializa. Ver Limitações conhecidas.
Abrir um dashboard pelo menu
- No menu lateral, expanda o grupo do workspace.
- Os dashboards cujo
panelRefestá efetivamente persistido aparecem como botões (com o título do dashboard) — ver a limitação acima. - Clique para abrir o dashboard (
/dattabi.html?dashId=<id>) em uma nova aba.
Usar os contextos do workspace para filtrar dados
- Defina os contextos do workspace na aba de contextos do gerenciador. Um contexto excluído em sai sozinho de todo workspace que o listava; se um nome antigo ainda estiver gravado, a tela o retira ao salvar e diz quantos retirou.
- No menu lateral, expanda o grupo do workspace e clique em uma funcionalidade.
- O escopo aplicado depende da funcionalidade:
- Chat direciona para uma tela própria escopada ao workspace: a busca (Pesquisa e Investigação) considera somente os contextos do workspace, resolvidos no servidor pelo id do workspace com a identidade do usuário, e os nomes dos contextos aparecem esmaecidos ao lado do seletor de modo. O alcance inclui os contextos de regra ligados aos do workspace (os "Conectado a" da aba de contextos — a CLT de um contexto trabalhista): a pergunta sobre a lei do domínio encontra a lei. E o workspace delimita a pergunta: quando o classificador só enxerga contextos de fora, a resposta vem dos contextos do workspace, sem remeter o usuário a outro contexto.
- Painel (
dashboard), Upload e Triagem em Lote recebem os contextos do workspace via?domain=e abrem já filtrados (Triagem em Lote vem com os contextos pré-selecionados).
Como operar (admin)
- A descoberta de dashboards depende do
dattabi-serviceestar acessível peloconfig-servicena URL configurada emdatta.dattabi-service.url(defaulthttp://dattabi-service:8210). Se indisponível, o catálogo de painéis continua respondendo, apenas sem as entradas de dashboard (degradação graciosa, logada em níveldebug). - O JWT interno usado na chamada S2S exige
datta.jwt.secret/JWT_SECRETpopulado noconfig-service; sem segredo válido, o cabeçalhoAuthorizationnão é enviado e odattabi-servicepode recusar a leitura. - A permissão
DATTABI_VIEWdeve estar mapeada nas roles que precisam ver os dashboards, eCONFIG_EDITnas roles que administram os vínculos do workspace.
Limitações conhecidas
- Cobertura de contexto parcial: apenas as funcionalidades em
WS_CONTEXT_AWARE(dashboard,upload,triagem-lote) consomem?domain. O Chat tem escopo próprio (view dedicadachat-ws, resolvida no servidor pelo id do workspace). O DATTA Investigation usa o mesmo seletor de workspace do Chat, na própria barra. Regras, Lineage, Notebook, Explorar Dados e Catálogo ainda não filtram por contexto do workspace — extensão prevista para o futuro (Regras é chaveada por contexto legal — CPC/CDC/CPP — e abre em "Todos", não pelos contextos de dados do workspace). - Vínculo de dashboard do DATTABI via
panelRefnão persiste hoje: a UI do gerenciador permite marcar dashboards do DATTABI (inclusive do próprio workspace), mas osetPanelRefsatual só aceita referências a painéis nativos ou a painéis canvas existentes em outro workspace. Dashboards do DATTABI vêm do catálogo via S2S e não sãoWorkspace.Panelpersistidos, então:- referência a dashboard de outro workspace é rejeitada com
400("Painel referenciado não existe"); - referência a dashboard do próprio workspace é descartada silenciosamente.
- referência a dashboard de outro workspace é rejeitada com
O menu lateral (index.js:2105-2107) só renderiza um dashboard quando o panelRef correspondente foi efetivamente persistido e resolve no catálogo pela chave workspaceId::panelId. Enquanto o backend não reconhecer dashboards do DATTABI como referência valida (ex.: aceitar kind == 'dattabi-dashboard' na validacao), o atalho no menu via panelRefs não se materializa de forma estável — extensão pendente.
- A propagação de
?domainocorre ao navegar a partir do grupo do workspace; ao acessar a funcionalidade por outro caminho (sem workspace), o filtro de contexto não é aplicado automaticamente. - O filtro do menu é dica, não barreira: esconder uma funcionalidade do menu e da tela Features não impede abri-la por link — quem bloqueia é a permissão, no backend.
- Membro precisa estar cadastrado antes. A adição de membro exige um usuário que já exista no cadastro (e responde
503, dizendo que nada foi gravado, com o cadastro fora do ar). Quem entra por Microsoft ou Google só pode ser incluído depois do primeiro login; pré-cadastrar um usuário local com o mesmo e-mail não resolve, porque o login pelo provedor externo é recusado numa conta criada por outro método. Aceitar o e-mail de quem ainda não entrou foi avaliado e descartado em 29/09/2026: a regra "membro = usuário cadastrado" fica. - Lista de usuários com teto. A lista de escolha traz até 2000 usuários, ordenados pelo nome; numa instalação maior que isso, a janela oferece a busca por nome, login ou e-mail, feita no servidor, que alcança quem ficou fora da lista.
- Quem vê os e-mails dos usuários. A lista de escolha (nome, login, e-mail e a marca de administrador) vem para qualquer um com
CONFIG_EDIT, mesmo sem gerenciar membros de workspace nenhum (decisão de 29/09/2026): quem temCONFIG_EDITpode criar um workspace e ser o dono dele, e o workspace público não tem gestor, então exigir "gerencia algum workspace" não restringiria ninguém de fato. Quem só temCONFIG_VIEWnão recebe a lista, e a tela nem a pede.
Galeria "Painéis" do workspace (cards com screenshot)
Cada grupo de workspace no menu lateral tem um item "Painéis" que abre a galeria de painéis escopada àquele workspace (?workspaceId=<id>). A galeria substitui a antiga listagem de dashboards individuais por uma grade de cartões de ~3,5 polegadas (336px @96dpi), cada um com um "screenshot" do painel:
A galeria mostra apenas painéis publicados: dashboards do DATTA BI (kind == 'dattabi-dashboard'), os painéis nativos ligados ao workspace pelo gerenciador (ver abaixo) e painéis vinculados (panelRefs). Painéis de canvas (declarativos) entram quando estão publicados — só o published os separa, como qualquer outro tipo. (Eles já foram filtrados por kind !== 'canvas', quando ainda não existia passo de publicação de canvas; ele existe, e o filtro saiu.)
Painel nativo: ligado pelo gerenciador de workspaces
O painel nativo não entra sozinho no workspace. Desde 27/09/2026 (decisão do dono do produto: "o painel de processos não deve ser ligado sozinho, ele deve ser ligado pelo gerenciador de workspaces") quem liga um painel nativo a um workspace é o gerenciador, na aba Painéis → Vincular existentes (grupo Nativo). O tipo do contexto de dados (tipoContexto) diz só qual contexto cada painel lê:
| Tipo do contexto que o painel lê | Cartão nativo | Tela aberta |
|---|---|---|
processo_judicial | Painel de Processos | painel de triagem (view dashboard), ancorado em :Processo/processoNumero |
custom com âncora (identificadorLabel + identificadorProperty no cadastro) | Painel de \<valor do tipo\> — ex. Painel de Contratos (painel-contrato) | o mesmo painel de triagem (view dashboard), lendo a âncora do contexto (:Contrato/contratoId) |
dossie_cadastral | Painel de Onboarding PJ e Pendências de Compliance | lista de dossiês PJ por CNPJ (view kyb-onboarding) e pendências de certidão (view compliance-pendencias) |
demais tipos (e custom sem âncora) | nenhum | — |
- O catálogo oferece, o gerenciador liga. O catálogo de painéis oferece todos os painéis nativos da instalação — os fixos e os por âncora de cada contexto
customancorado — e diz, em cada entrada nativa, quais workspaces o ligaram (workspaceIds, que vem só dos vínculos gravados empanelRefscomworkspaceId == "native") e quais contextos cadastrados ele lê (contextosDeOrigem; a quem não edita a configuração, só os dos workspaces que ele enxerga). A galeria do workspace e o popover de painéis da home mostram só os ligados — a rota da galeria nem devolve o painel nativo que nenhum workspace visível ligou. - Salvar os contextos não liga painel. Depois de salvar contextos que oferecem um painel nativo que o workspace ainda não liga, a aba Contextos de Dados sugere: "Para ver o Painel de Processos neste workspace, ligue-o na aba Painéis" — com um botão que leva à aba.
- Ligar a workspace sem o contexto que o painel lê é permitido. Ao marcar o painel, o gerenciador avisa sob a caixa o que ele vai mostrar naquele workspace — pela mesma regra da tela que o painel abre —, e o salvamento segue normal:
- Painel de triagem (o Painel de Processos e os painéis por âncora): é uma tela só, que lê qualquer contexto ancorado do workspace. Sem contexto ancorado, o aviso é "Este workspace não tem contexto do tipo Processo Judicial; o painel abrirá sem registros" (o painel por âncora cita o contexto que lê). Com contexto ancorado de outro tipo o painel abre com os registros dele, e o aviso diz isso: "…; o painel mostrará os registros de Contratos, com o título "Painel de Contratos"". Workspace cujos contextos não declaram tipo (instalação antiga) não recebe aviso: sem tipo não há como afirmar o que o painel mostrará. Marcar o Painel de Processos e um painel por âncora juntos gera o aviso de que são a mesma tela — dois cartões com o mesmo conteúdo.
- Painéis de dossiê (Onboarding PJ e Pendências de Compliance): não são filtrados pelo workspace. Abrem no dossiê do workspace quando ele é o único contexto do workspace; senão, no primeiro dossiê cadastrado na instalação — e o aviso diz qual: "…este painel não é filtrado pelo workspace: ele abrirá com os dossiês de FEBRABAN". "Sem registros" só quando a instalação não tem contexto de dossiê.
- Nenhuma subida da plataforma liga nem troca vínculo nativo, e nenhuma apaga o que o gerenciador ligou. Até 27/09/2026 o vínculo era decidido pelo tipo do contexto: acrescentado ou trocado ao salvar os contextos do workspace e reconciliado em toda subida, e a semente de instalação nova já o gravava. Nada disso existe mais.
- Remoção dos vínculos que o gerenciador não fez. O gerenciador registra cada painel nativo que liga no arquivo
paineis-nativos-manuais.json, ao lado doworkspaces.json(listamanuais: workspace, painel e quando), antes de gravar o vínculo — se o registro não puder ser gravado, o vínculo também não é, e a tela mostra o erro. Em toda subida, o serviço de configuração tira dos workspaces o vínculo nativo que não está nessa lista: na primeira subida com a mudança, todos (Painel de Processos, Onboarding PJ, Pendências de Compliance e painéis por âncora); depois, só o que uma versão anterior da plataforma tenha religado pelo tipo (um rollback, ou um release feito de um ramo sem a mudança). O que saiu fica emremovidos(workspace, nome, painéis e quando) — o dado para religar pela aba Painéis.- Se a remoção falhar, o log avisa, o registro lista em
naoRemovidoso que ficou, e a subida seguinte tenta de novo. - Registro ilegível: nenhuma subida remove nada, e o log avisa com o caminho do arquivo. Apagar o arquivo recomeça a política — a subida seguinte remove todos os vínculos nativos, inclusive os ligados pelo gerenciador.
- Limite: um vínculo feito no gerenciador enquanto uma versão anterior está no ar não entra no registro e sai na subida seguinte desta versão (o log diz qual). E um vínculo registrado que uma versão anterior apagou não é religado sozinho — o log avisa para religar pela aba Painéis.
- Se a remoção falhar, o log avisa, o registro lista em
- Funcionalidades do dossiê: uma vez por workspace. O workspace que agrupa um contexto de dossiê cadastral recebe, uma vez, as funcionalidades do vertical (
kyb-onboarding,compliance-pendencias, upload, regras e triagem em lote, explorar, chat, catálogo), somadas às que o admin habilitou. Se o admin tirar alguma depois, ela não volta em restart nem em release — até 29/09/2026 voltava a cada subida. O registro de quem já foi atendido é o arquivoupgrades-de-boot-aplicados.json, ao lado doworkspaces.json(o mesmo do canvas "Painel de Processos", descrito no guia de Painéis). Na primeira subida com essa regra, os workspaces que já agrupam um dossiê são só registrados, sem mudança; um workspace associado a um dossiê depois recebe as funcionalidades na subida seguinte, uma vez. Registro ilegível: nenhuma subida acrescenta nada, e o log avisa. Workspace que não grava (volume cheio, falha de disco) sai do registro e é tentado de novo na subida seguinte, sem impedir os demais; se nem o registro puder ser corrigido, o log nomeia o workspace que ficou sem as funcionalidades. Um rollback para versão anterior volta a acrescentá-las em toda subida. - Título e descrição do cartão vêm do catálogo, não da página: o cartão de dossiê não herda mais o texto "Triagem e auditoria de processos judiciais".
- O cartão de dossiê cadastral exige a permissão
KYB_VIEW— a mesma que esconde a entrada "Onboarding PJ" no menu lateral. O catálogo não devolve a entrada a quem não a tem, então o cartão some de todas as superfícies ao mesmo tempo, em vez de abrir em 403.
Contexto custom com âncora: o mesmo painel de triagem, com o dado do contexto
O painel de triagem (view dashboard) não é o "painel de :Processo": ele lê a âncora do contexto — o label e a propriedade identificadora que o cadastro declara (identificadorLabel/identificadorProperty, expostos em GET /api/config/domain/list). Num contexto processo_judicial a âncora é :Processo/processoNumero; num contexto custom como Contratos é :Contrato/contratoId. O que muda entre os dois é só o dado apresentado:
- Mesmo layout e mesmas ações — totalizadores, filtros, lista paginada no servidor, detalhe com laudo/regras/histórico, reprocessar triagem, atribuir a pessoa (encaminhar), atribuir a modelo BPM, exportar PDF, baixar os PDFs em ZIP, excluir com justificativa e "Continuar no Chat". O que só existe no jurídico fica de fora nos outros: o filtro/coluna de tipo por faixa do número CNJ, os blocos de decisão final / cabimento de recurso e a ação Gerar recurso (peça recursal).
- Termos e título vêm da âncora: "Contratos", coluna "Contrato", "Identificador do contrato", "Excluir contrato", breadcrumb "Painel de Contratos". O título do cartão é
Painel de <valor do tipo>(otipoContextoValordo cadastro; na falta, o nome do contexto). - Partes e classificação: fora do jurídico a parte é qualquer entidade
:Empresa/:Pessoaligada à âncora (a aresta que o NER do contexto emite, ex.PARTE_CELEBRA_CONTRATO, com opapeldela), o nome é o que o NER gravou (razaoSocial/nome) e a coluna de classificação mostra a propriedade convencionaltipo<Label>(tipoContrato) ou otitulo. - Backend:
GET /api/processos/dashboard,DELETE /api/processose a lista de documentos do ZIP consultam o nó da âncora (graph-data-serviceresolve o par pelo cadastro; fora da forma segura de identificador vale o default jurídico). A resposta trazancora: {label, property}.
O que o contexto precisa para oferecer o cartão é declarar a âncora no cadastro (tipoContexto=custom + identificadorLabel + identificadorProperty) e ter documentos cujo NER emita o nó âncora ligado ao documento; para o cartão aparecer num workspace, ligue-o na aba Painéis do gerenciador. Contexto custom sem âncora não oferece cartão nativo — não há o que a tela consultar. O painel declarativo "Contratos" que existia até 2026-09-16 (tabela de propriedades, sem triagem) foi removido junto com o script que o criava; o cartão nativo o substitui.
Cada cartão tem um "screenshot" do painel:
- Dashboard do DATTA BI: usa a miniatura de
GET /api/dattabi/dashboards/{id}/thumbnail.png, que prioriza o screenshot real capturado no browser: quando um editor abre/salva/publica o dashboard, odattabi.jscompõe um PNG fiel (pixels reais dos canvases ECharts + textos de KPIs/tabelas) e envia viaPUT .../thumbnail; o backend persiste no Neo4j e cacheia no Valkey. O render sintético server-side ficou apenas como fallback para dashboards nunca abertos. (Render "ao vivo" via iframe do BI foi tentado e revertido — carregar o app inteiro por cartão era pesado/lento demais para thumbnail.) - Painel nativo ("Painel de Processos", "Painel de Onboarding PJ" ou outro nativo ligado ao workspace): usa miniatura "ao vivo" — um
iframenão-interativo do painel real, carregado no shell embutido (/index.html?view=<view>&embed=1&domain=<contextos do workspace>, que esconde menu lateral e barra do topo), escalado (largura lógica 1280,transform: scale, recorte no topo), lazy (IntersectionObserver) + escala responsiva (ResizeObserver),sandbox="allow-scripts allow-same-origin",pointer-events: none. Não há renderizador de screenshot server-side para nativos — a miniatura ao vivo é a abordagem 100% client-side e on-prem. (Até a migração das telas para fragmentos o alvo era a página standalone/dashboard.html; ela não existe mais, e a view do shell a substituiu.) - Painel de canvas (declarativo, feito no Panel Builder): mesma miniatura "ao vivo" do nativo, apontando para
/index.html?view=painel&ws=…&panel=…&mode=view&embed=1. Oembed=1esconde a toolbar (pb-embed) e implicaviewOnly, então o que aparece na miniatura é o painel pronto, nunca o construtor.
A miniatura sempre nasce de carregar o painel em background, e todo painel pronto tem uma — seja painel da plataforma (nativo ou declarativo) ou dashboard do DATTABI. O que muda entre os tipos é só onde o resultado para: o painel da plataforma renderiza o iframe direto no cartão, e o DATTABI guarda a PNG no servidor porque abrir o app inteiro do BI por cartão seria pesado demais para repetir a cada carga da galeria.
O construtor não aparece em superfície nenhuma da galeria — nem na miniatura (
embed=1o esconde) nem na trilha (ver abaixo). Ele é ferramenta interna; a galeria mostra painéis, não a ferramenta que os monta. A captura do DATTABI é enfileirada pordatta-thumb-regen.js(um iframe oculto por vez,?thumb=1, teto de capturas e timeout por captura), compartilhado com a Galeria do DATTA BI.
Ao clicar num cartão:
- Dashboard de BI → abre a tela do DATTABI travada em modo somente-visualização (
?view=dattabi&mode=view&dashId=<id>; ver DATTABI — Guia do Usuário §15.2). OdashIdviaja na querystring, e não como parâmetro de navegação interna, porque é de lá que o DATTABI o lê. - Painel nativo → navega para a view nativa do tipo do contexto (
dashboardpara processo judicial,kyb-onboardingpara dossiê cadastral), já filtrada pelos contextos do workspace, sem recarregar a aplicação. - Painel de canvas → abre o painel em modo visualização (
?view=painel&mode=view&ws=…&panel=…), com "Abrir no construtor" como ação separada — a galeria não entra no construtor por conta própria.
Todo cartão é um link: Ctrl/Cmd+clique abre em nova aba, e o teclado o alcança sem ARIA extra. A trilha de navegação reflete o caminho percorrido (Painéis › painel), e não a posição do item no menu — sem isso o painel de dossiês abriria como "Investigação › Onboarding PJ", correto para quem chegou pelo menu e errado para quem clicou no cartão.
O construtor não aparece nem na URL. A view chama-se
painel, nãopanel-builder: o id da view fica visível na barra de endereços, e nomear ali uma ferramenta interna para quem só abriu um painel pela galeria contradiz a mesma regra que o tira da trilha. A view é uma só para ver e editar — o que muda émode=view, não a tela. O id antigo (?view=panel-builder&…) segue como alias, para não quebrar link já gravado.
Num painel declarativo aberto como visualizador, a folha da trilha é o título do painel, nunca "Construtor de Painéis": DATTA › Painéis › Onboarding PJ — Pendências. O gatilho é o mode=view da URL, e não a origem da navegação — origem é estado de tela e some ao recarregar, e o deep-link do cartão é justamente uma carga nova. O título vem do que a própria tela publica (BREADCRUMB_SET). Dentro do construtor (sem mode=view) a trilha volta a nomeá-lo, e o título entra como nível extra dizendo qual painel está aberto: DATTA › Painéis › Construtor de Painéis › Onboarding PJ — Pendências.
URLs externas eventualmente configuradas num painel passam por validação (safePanelUrl: só http(s)/relativa) antes de window.open — bloqueia javascript:/data: (Diretrizes do Projeto §2).
Limpeza do menu
- O item "Painel" (a view
dashboard= "Painel de Processos") foi removido da lista de funcionalidades por-workspace no menu lateral, por ser redundante com a galeria "Painéis" — esse painel continua acessível pelo cartão NATIVO da galeria. A viewdashboardpermanece roteável (deeplink?view=dashboardsegue funcionando). - O construtor de painéis (Gerenciador de Painéis em Sistema › Contextos) foi mantido — a galeria/menu "Painéis" apenas não expõe o construtor.
Seleção múltipla de painéis + popover de painéis na home
Esta seção cobre duas capacidades complementares ao catálogo de painéis descrito acima:
- Vincular múltiplos painéis existentes a um workspace numa única operação (multi-seleção), por referência — sem duplicar a definição do painel.
- Popover de painéis na home (chat): o botão ao lado do enviar consulta deixou de navegar cegamente para o painel de processos e passou a abrir um popover que lista os painéis prontos para consumo (o painel nativo, só quando ligado ao workspace ativo).
Vincular existentes (multi-seleção) no gerenciador de workspaces
Na aba Painéis do gerenciador (/configurar/workspaces), o botão Vincular existentes abre um dialog M3 com checkboxes — o usuário marca vários painéis do catálogo (GET /api/config/panels) de uma só vez e confirma. O estado de seleção é um conjunto (Set) inicializado com os vínculos já persistidos, de modo que abrir o dialog novamente mostra o que já está vinculado (screens/settings-workspaces.html).
As opções oferecidas são o catálogo achatado, filtrado assim (screens/settings-workspaces.html):
const options = catalog.filter(
c => c.workspaceId !== selected.id || c.kind === 'dattabi-dashboard'
);Ou seja: painéis de canvas de outros workspaces (o próprio já aparece sozinho na lista de painéis do workspace) mais os dashboards do DATTABI (kind === 'dattabi-dashboard') de qualquer workspace, inclusive o próprio, e todos os painéis nativos da instalação (grupo Nativo, primeiro da lista). Cada opção mostra o título e a origem (Plataforma para nativos, ou o nome do workspace de origem). Painel nativo marcado ganha, sob a caixa, o aviso do que ele vai mostrar naquele workspace — "o painel abrirá sem registros", os registros de outro contexto ancorado, ou os dossiês de outro contexto — e é só aviso: salvar continua permitido.
A confirmação persiste a lista completa de referências via PUT /api/config/workspaces/{id}/panel-refs. Os vínculos aparecem na seção Painéis vinculados, cada um com um badge de origem — Plataforma para painéis nativos, Vinculado para os demais — e as ações Abrir (abre o painel de origem) e Remover vínculo (screens/settings-workspaces.html). O contador do card do workspace na listagem soma painéis próprios + vinculados: (w.panels || []).length + (w.panelRefs || []).length (screens/settings-workspaces.html).
Referência, não cópia. O vínculo aponta para o painel de origem — editar o painel no workspace dono reflete em todos os que o vinculam; remover o vínculo não apaga o painel de origem (CA-2). A fonte de verdade permanece única.
Validação do backend (setPanelRefs)
A API de workspaces valida, deduplica e normaliza a lista antes de gravar. Regras atuais:
- Dedupe por chave
workspaceId::panelId(refs repetidas são descartadas). - Refs com
workspaceId/panelIdnulos são ignoradas. - Auto-referência redundante: um painel de canvas do próprio workspace já aparece sozinho, então a ref a ele é descartada silenciosamente.
- Aceita: painel nativo (API do catálogo de painéis), painel canvas de qualquer workspace visível ao usuário, ou qualquer referência a um workspace existente e visível (
knownWorkspace— inclui"native"e os workspaces do store que passam porWorkspaceAccess.canSee; um workspace invisível é recusado com a mesma mensagem de um id inexistente, para não virar oráculo de existência). Isso cobre os dashboards do DATTABI (que vêm do catálogo S2S e não sãoWorkspace.Panel): eles são aceitos pela existência do workspace de origem e resolvidos no menu/galeria viaGET /api/config/panels. - Rejeita apenas quando a ref não é nativa, não é canvas e o workspace de origem não existe:
400 Bad Requestcom{"error":"Painel referenciado não existe: <ws>/<panel>"}(CA-4).
Nota (evolução vs. seção anterior). A seção "Limitações conhecidas" mais acima (herdada da entrega dos dashboards do DATTABI no workspace) descreve um gap em que refs a dashboard do DATTABI eram rejeitadas/descartadas. O
setPanelRefsatual é mais permissivo: valida a ref pela existência do workspace de origem, então dashboards do DATTABI de outros workspaces e do próprio são aceitos como referência; refs que não resolvem no catálogo são ignoradas graciosamente na renderização (não quebram o catálogo nem o menu).
Permissão: CONFIG_EDIT, em dupla camada — path-matcher no SecurityConfig (pathMatchers(PUT,"/api/config/**").hasAnyAuthority("CONFIG_EDIT","ROLE_ADMIN")) mais @RequirePermission("CONFIG_EDIT") no método (que retorna Mono, ramo reativo da checagem de permissão declarada).
Popover de painéis na home (chat)
Na home (chat), o botão ao lado do enviar consulta virou um toggle de popover (mesmo padrão visual do picker de agentes JDBC). O primeiro clique abre o popover e carrega o catálogo uma única vez (lazy); o clique num item fecha o popover e navega ao painel escolhido (CA-3).
O catálogo vem de GET /api/config/panels e é filtrado, na home, para somente painéis prontos para consumo — nativos da plataforma e dashboards BI publicados (chat.js:367-372):
fetch(API + '/api/config/panels')
.then(r => r.ok ? r.json() : [])
.then(d => setPanelsList((Array.isArray(d) ? d : [])
.filter(p => p && (p.native || p.kind === 'dattabi-dashboard'))))
.catch(() => setPanelsList([]));Por que canvas fica de fora da home (ajuste de escopo, feedback do usuário em 2026-06-11): painéis de canvas estão em construção no construtor e são acessados pelo Gerenciador de Painéis/Workspaces — incluí-los na home duplicava a entrada "Painel de Processos" (canvas seedado vs. nativo) e expunha um link para o builder na home.
Painel nativo só quando ligado (decisão de 27/09/2026). O popover mostra um painel nativo no grupo Plataforma só quando ele está ligado ao workspace ativo (o workspaceIds da entrada, como na galeria); em Todos os workspaces, mostra os nativos ligados a algum workspace que você enxerga — o servidor não devolve, na galeria, painel nativo que ninguém ligou. Antes, todo painel nativo da instalação aparecia ali para qualquer usuário, em qualquer workspace: um membro só de "Assistência Social" via o Painel de Processos e o painel de contratos de outro workspace, e o clique abria o painel com o escopo do workspace dele.
Cada item mostra o título e a origem do painel. Estados em PT-BR: "Carregando painéis..." (loading) e "Nenhum painel disponível para o seu usuário." (empty). Se o fetch falhar, a lista cai para vazia — o popover degrada para o empty state amigável, sem erro bruto (Diretrizes do Projeto §5).
Roteamento do clique (chat.js:openPanelEntry, chat.js:384-403):
- Nativo (
p.native): navega no próprio shell (SPA) viawindow.parent.postMessage({type:'NAVIGATE_TO', payload: p.view || 'dashboard'}). - Dashboard BI (
kind === 'dattabi-dashboard'): abrep.url(/dattabi.html?dashId=<id>) em nova aba, após passar pelosafePanelUrl. - Canvas (não aparece na home hoje, mas o roteamento existe):
?view=painel&ws=<ws>&panel=<panel>&mode=viewem nova aba.
Segurança de URL. URLs vindas do config do painel passam por safePanelUrl, que só aceita http/https (ou relativa) — bloqueia javascript:/data: graváveis por quem tem CONFIG_EDIT (Diretrizes do Projeto §2).
Permissão PANEL_VIEW
A leitura do catálogo e a abertura de painéis em modo visualização passaram a aceitar a permissão dedicada PANEL_VIEW (categoria Config, registrada no catálogo central Permission) ou CONFIG_VIEW — semântica any-of via @RequirePermission({"PANEL_VIEW", "CONFIG_VIEW"}) (a anotação foi ampliada para aceitar String[]). Isso evita exigir CONFIG_VIEW (admin-leaning) do usuário comum só para ver/consumir painéis.
Gates any-of atuais:
| Endpoint | Permissão any-of |
|---|---|
GET /api/config/panels (API do catálogo de painéis) | PANEL_VIEW \ |
GET /api/config/workspaces/{id} (API de workspaces) | PANEL_VIEW \ |
POST /api/config/panel/{domain}/aggregate e .../preview (a API de dados de painéis) | PANEL_VIEW \ |
Para os endpoints de dados do painel (aggregate/preview), que são leitura de renderização apesar de serem POST (parâmetros só no body), há um matcher dedicado no SecurityConfig antes do gate de mutação, para que PANEL_VIEW libere a leitura sem conceder escrita.
PANEL_VIEW está mapeada nas roles padrão ADVANCED_USER, ANALISTA e READ_ONLY (RolePermissions + BuiltInRoleSync). Usuários não-admin precisam re-logar para o JWT carregar a permissão nova.
Como usar (passo a passo)
Vincular vários painéis de uma vez:
- Acesse Sistema → Workspaces e selecione o workspace.
- Na aba Painéis, clique em Vincular existentes.
- Marque, com os checkboxes, todos os painéis desejados (dashboards BI e/ou painéis de outros workspaces) e confirme.
- Eles aparecem em Painéis vinculados com o badge de origem (Plataforma/Vinculado) e as ações Abrir e Remover vínculo.
- Remover o vínculo tira apenas a referência — o painel de origem permanece.
Descobrir e abrir painéis pela home:
- Na home (chat), clique no botão ao lado do enviar consulta.
- O popover Painéis lista os painéis acessíveis: os dashboards BI e os painéis nativos ligados ao workspace ativo.
- Clique em um item para abrir o painel escolhido.