Workspaces: DATTABI dashboards and context filtering
Overview
Each DATTA workspace groups a set of enabled features, a list of linked panels and a list of contexts (domains/databases). This delivery adds two capabilities to the workspace:
- DATTABI dashboards in the manager and in the menu — the BI dashboards created in DATTABI now appear in the panel catalog, can be linked to a workspace through the manager and become buttons in the workspace's group in the side menu.
- Contexts filtering the features' data — when opening a context-aware feature from a workspace's group, the shell propagates the workspace's contexts to the feature, which starts filtering its data by those contexts.
Problem
- DATTABI dashboards lived isolated in the the DATTABI screen page; there was no way to expose them as a shortcut inside a workspace's menu or to reuse them alongside the other features (chat, panel, investigation, screening).
- A workspace can have several contexts, but the features opened from it showed all data from all contexts. The user had to filter the domain manually on each screen, gaining nothing from the workspace grouping.
Solution
- The panel catalog (
GET /api/config/panels) now includes, besides the native and canvas panels, each workspace's DATTABI dashboards, obtained via a service-to-service (S2S) call to thedattabi-service. - The workspace manager (
/configurar/workspaces) shows those dashboards in the Link existing action, and the side menu renders as buttons the dashboards whosepanelRefis persisted (opening the DATTABI screen). The persistence of the dashboard link viapanelRefhas a known backend limitation — see Known limitations. - The shell propagates the workspace's contexts via the
?domain=ctx1,ctx2querystring when opening a context-aware feature, and each feature reads that parameter to filter/pre-select its data.
How it works (architecture + data flow)
Item 2 — DATTABI dashboards in the catalog, in the manager and in the menu
Backend (panel catalog). The config-service's panel catalog API serves GET /api/config/panels — a flattened catalog of the platform panels visible to the user (see Visibility by members). Besides the native (native-view) and canvas panels of each workspace, it now aggregates the DATTABI dashboards:
- For each workspace, it makes an S2S call to the
dattabi-serviceatGET /api/dattabi/dashboards?workspaceId=<id>usingWebClient. - The per-workspace calls run in parallel, with concurrency 6 (
Flux.fromIterable(workspaces).flatMap(this::dattabiDashboards, 6), following the Project Guidelines §19. - Each call is best-effort: a 6s timeout and an
onErrorResumethat returns an empty list, so that the unavailability of one workspace's dashboards does not bring down the whole catalog. - Each non-archived dashboard becomes a catalog entry in the format:
{
"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 (source of the dashboards). The dattabi-service exposes GET /api/dattabi/dashboards?workspaceId=<id>. The endpoint requires DATTABI_VIEW, lists the workspace's dashboards (parameter includeArchived=false by default) and, defensively, returns an empty list when workspaceId is missing/blank.
Frontend (manager). In the workspace manager (screens/settings-workspaces.html, Panels tab → Link existing), the catalog is loaded and filtered into the list of linkable options:
// screens/settings-workspaces.html
const options = catalog.filter(
c => c.workspaceId !== selected.id || c.kind === 'dattabi-dashboard'
);That is: canvas panels remain linkable only from other workspaces (the workspace's own already appear), but DATTABI dashboards (kind === 'dattabi-dashboard') are linkable from any workspace, including its own. The selection is saved as panelRef via PUT /api/config/workspaces/{id}/panel-refs (screens/settings-workspaces.html).
Since those links are references (panelRef), they appear in the list's Linked panels section, with the generic Linked badge and the Open button (which calls openRef → opens /dattabi.html?dashId=<id> in a new tab) (screens/settings-workspaces.html). The BI Dashboard badge and the Open dashboard button (screens/settings-workspaces.html) are used only for panels owned by the workspace whose config.kind is already dattabi-dashboard — a path distinct from the panelRef link.
Frontend (side menu). The shell (compiled/index.js) loads /api/config/workspaces and /api/config/panels in parallel with Promise.all (index.js:746-756) and indexes the catalog by the workspaceId::panelId key. When building each workspace's group, it resolves the panelRefs in the catalog and renders those of type dattabi-dashboard as buttons:
// index.js:2105-2107
const dashRefs = (ws.panelRefs || [])
.map(r => panelCatalog[r.workspaceId + '::' + r.panelId])
.filter(p => p && p.kind === 'dattabi-dashboard');Clicking the button opens /dattabi.html?dashId=<dashboardId> in a new tab (index.js:2117-2126).
Item 9 — The workspace's contexts filter the features
Propagation in the shell. The shell keeps a panelDomain state (index.js:705). When clicking a feature inside a workspace's group, it sets panelDomain to the workspace's contexts joined by comma and navigates to the feature:
// index.js:2115
onClick: () => { setPanelDomain((ws.contexts || []).join(',')); navigateTo(fk); setSidebarOpen(false); }When the feature is rendered as an iframe, the shell adds the domain parameter to the src only if the feature is context-aware and there is a panelDomain:
// index.js:2602
src: WS_CONTEXT_AWARE.has(key) && panelDomain
? src + (src.includes('?') ? '&' : '?') + 'domain=' + encodeURIComponent(panelDomain)
: src,The set of context-aware features is fixed (index.js:639-642):
var WS_CONTEXT_AWARE = new Set([
'dashboard', 'chat', 'investigate', 'upload', 'analytics',
'triagem-lote', 'regras-triagem'
]);Consumption in the features. Each feature reads ?domain from the URL and uses the contexts to filter its data:
- Panel/Processes (
dashboard), Chat (chat), Investigation (investigate), Upload (upload) and Cases (analytics) already read?domainand now filter/use the workspace's contexts. - Rules (
regras-triagem): filters the rules by the workspace's first context; without?domainit showsTodos(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');- Batch Screening (
triagem-lote): pre-selects only the workspace's contexts (the intersection with the available contexts); without?domainit selects all, keeping the default behavior (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 / contracts
GET /api/config/panels (config-service)
A flattened catalog of the platform panels visible to the user (public workspaces + the ones they are a member of; an admin sees all — see Visibility by members). Response: an array of objects. DATTABI dashboard entries have kind: "dattabi-dashboard" with dashboardId and url (see the format above). Permission: PANEL_VIEW or CONFIG_VIEW (any-of).
GET /api/dattabi/dashboards?workspaceId=<id> (dattabi-service)
Lists a workspace's dashboards. Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
workspaceId | string | — | Required (semantically); empty → [] |
includeArchived | boolean | false | Includes archived dashboards |
Permission: DATTABI_VIEW. Called S2S by the config-service.
PUT /api/config/workspaces/{id}/panel-refs (config-service)
Sets the list of existing panels linked to the workspace. Body:
{ "panelRefs": [ { "workspaceId": "<id>", "panelId": "<id>" } ] }Permission: CONFIG_EDIT. The service validates each reference: it accepts only a native panel (panel catalog API) or an existing canvas panel in another workspace (store.find(workspaceId).panels()); any other reference returns 400 Bad Request with {"error":"Painel referenciado não existe: ..."}. In addition, it silently discards references whose workspaceId is that of the workspace itself.
Attention (current gap): DATTABI dashboards come from the catalog via S2S and are not persisted
Workspace.Panel— so the currentsetPanelRefsdoes not recognize them as a valid reference (it rejects those from another workspace with 400 and discards the workspace's own). See Known limitations.
RBAC / permissions
| Action | Required permission | Where it is checked |
|---|---|---|
| Read the panel catalog | PANEL_VIEW or CONFIG_VIEW | panel catalog API — @RequirePermission via declared-permission check (Mono return, reactive branch). The SecurityWebFilterChain only requires .authenticated() for this GET. |
| List a workspace's dashboards | DATTABI_VIEW | dashboards API — @RequirePermission via declared-permission check (Mono return). The dattabi-service filter only requires .authenticated() on /api/dattabi/**. |
| Link panels/dashboards to the workspace | CONFIG_EDIT | Double layer: the security filter requires CONFIG_EDIT or ROLE_ADMIN on every PUT /api/config/**, and the declared CONFIG_EDIT permission is checked again when linking panels. |
Note on the enforcement point. On these three endpoints the granular permission check runs in the declared-permission check (
datta-common), which only enforces when the method returnsMono/Flux(the reactive branch, viaReactiveSecurityContextHolder). The three methods returnMono, so the annotation applies. For blocking-return methods in WebFlux the aspect does not block (empty thread-local) and authorization falls back to theSecurityWebFilterChain— which is why the config mutations also have the per-permission path-matcher in theSecurityConfig.
The config-service's S2S call to the dattabi-service uses an internal JWT (InternalJwtProvider) issued with the DATTABI_VIEW and ADMIN authorities, satisfying both the dattabi-service filter's .authenticated() and the @RequirePermission("DATTABI_VIEW") checked by the aspect. The frontend is just a visual hint; the security barrier remains in the backend (Project Guidelines §13).
Visibility by members (scope, Project Guidelines §13)
Holding CONFIG_VIEW or PANEL_VIEW authorizes reading workspaces, not reading every workspace. On top of the permission, every access goes through the membership scope (WorkspaceAccess, config-service):
| Who | What they see |
|---|---|
Administrator (ADMIN/SYSTEM_ADMIN) and internal services (S2S) | Every workspace |
| Any authenticated user with a read permission | Workspaces with no members — public by definition (the case of the Processos and Assistência Social seeds) |
| A user listed in the Members tab, and the workspace owner | The restricted workspaces they belong to, plus the public ones |
A member is recognized by e-mail as well as by user id: the Members tab stores the e-mail whenever it is available, and the user's token carries the id — the backend accepts both, case-insensitively.
A restricted workspace the user does not belong to returns 404, not 403: confirming that the resource exists would already be a leak (the user would learn the names/ids of other teams' confidential workspaces). That is why the message in the interface is deliberately ambiguous — "Workspace not found or no access". The denial is recorded in the audit trail as AUTHZ.PERMISSION_DENIED with permission=WORKSPACE_MEMBERSHIP.
The scope applies to all workspace routes, not just reads: updating, deleting, or touching the panels, contexts and members of an invisible workspace also returns 404, even with CONFIG_EDIT. The validation behind the Link existing action (PUT .../panel-refs) is scoped as well: a panel from an invisible workspace is rejected exactly like a non-existent id, with the same message — otherwise the difference between the two responses would reveal, by trial and error, which restricted workspaces exist. And the panel catalog (GET /api/config/panels) flattens only the visible workspaces — without that, the titles of a restricted workspace's panels and dashboards would leak through the gallery and the home popover. Native platform panels remain visible to everyone.
Practical consequence. As long as a workspace's Members tab is empty, it stays visible to the whole platform — that is the historical behavior and it is what keeps the demo workspaces reachable. To restrict a workspace, just add the first member: from then on it disappears for anyone not on the list (administrators excepted).
Auditing
- Panel linking uses the existing workspace mutation flow (
PUT .../panel-refs), covered by theconfig-service's configuration mutation auditing. - Creating/changing/archiving dashboards in DATTABI follows the
dattabi-service's own auditing (versioning via dashboards versions service and copilot cache invalidation on save/archive/delete). The read (GET /api/dattabi/dashboards) does not generate a mutation event.
Cache
- The per-workspace dashboard listing is queried on every assembly of the panel catalog. The
GET /api/dattabi/dashboardsread path relies on the DATTABI repository; the catalog itself does not add its own Valkey layer — the S2S call is parallelized and protected by timeout/best-effort. - DATTABI keeps its own per-dashboard narrative and suggestion caches, invalidated on save/archive/delete (dashboards API).
Observability (OTel)
Backend and frontend are already instrumented by the platform standard (Project Guidelines §11):
- The HTTP calls of
GET /api/config/panels,GET /api/dattabi/dashboardsandPUT .../panel-refsgenerate inbound spans (controllers) and theconfig-service's S2S call to thedattabi-servicegenerates an HTTP client span, all exported to theotel-v1-apm-span-*indices. - The shell (the
index.htmlpage) loads the OTel browser SDK and propagatestraceparenton thefetch()calls to/api/config/workspacesand/api/config/panels.
How to use (user step by step)
Link a DATTABI dashboard to a workspace
- Go to Configure → Workspaces (
/configurar/workspaces). - Select the desired workspace and open the Panels tab.
- Click Link existing.
- In the list, check the DATTABI dashboards (and/or the panels from other workspaces) you want to display and confirm. The dashboards appear with their origin indication; those of the workspace itself can also be selected in the UI.
- The persisted links appear in the Linked panels section with the Linked badge and the Open button.
Current limitation: the
setPanelRefsbackend does not yet recognize DATTABI dashboards as a valid reference (it rejects those from another workspace with400and discards the workspace's own). Until the backend acceptskind == 'dattabi-dashboard', this link may not persist and the menu shortcut does not materialize. See Known limitations.
Open a dashboard from the menu
- In the side menu, expand the workspace's group.
- The dashboards whose
panelRefis effectively persisted appear as buttons (with the dashboard's title) — see the limitation above. - Click to open the dashboard (
/dattabi.html?dashId=<id>) in a new tab.
Use the workspace's contexts to filter data
- Set the workspace's contexts in the manager's contexts tab.
- In the side menu, expand the workspace's group and click a feature.
- The applied scope depends on the feature:
- Chat takes you to a dedicated screen scoped to the workspace: search (Pesquisa and Investigação) considers only the workspace's contexts, resolved server-side from the workspace id with the user's identity, and the context names appear dimmed next to the mode selector. See Workspace chat.
- Panel (
dashboard), Upload and Batch Screening receive the workspace's contexts via?domain=and open already filtered (Batch Screening comes with the contexts pre-selected).
How to operate (admin)
- Dashboard discovery depends on the
dattabi-servicebeing reachable by theconfig-serviceat the URL configured indatta.dattabi-service.url(defaulthttp://dattabi-service:8210). If unavailable, the panel catalog keeps responding, just without the dashboard entries (graceful degradation, logged at thedebuglevel). - The internal JWT used in the S2S call requires
datta.jwt.secret/JWT_SECRETpopulated in theconfig-service; without a valid secret, theAuthorizationheader is not sent and thedattabi-servicemay refuse the read. - The
DATTABI_VIEWpermission must be mapped to the roles that need to see the dashboards, andCONFIG_EDITto the roles that administer the workspace's links.
Known limitations
- Partial context coverage: only the features in
WS_CONTEXT_AWARE(dashboard,upload,triagem-lote) consume?domain. Chat has its own scope (dedicatedchat-wsview, resolved server-side from the workspace id). Investigation, Rules, Lineage, Notebook, Explore Data and Catalog do not yet filter by the workspace's context — an extension planned for the future (Rules is keyed by legal context — CPC/CDC/CPP — and opens on "Todos", not on the workspace's data contexts). - Linking a DATTABI dashboard via
panelRefdoes not persist today: the manager's UI allows checking DATTABI dashboards (including the workspace's own), but the currentsetPanelRefsonly accepts references to native panels or to existing canvas panels in another workspace. DATTABI dashboards come from the catalog via S2S and are not persistedWorkspace.Panel, so:- a reference to another workspace's dashboard is rejected with
400("Painel referenciado não existe"); - a reference to the workspace's own dashboard is silently discarded.
- a reference to another workspace's dashboard is rejected with
The side menu (index.js:2105-2107) only renders a dashboard when the corresponding panelRef has effectively been persisted and resolves in the catalog by the workspaceId::panelId key. Until the backend recognizes DATTABI dashboards as a valid reference (e.g.: accepting kind == 'dattabi-dashboard' in the validation), the menu shortcut via panelRefs does not materialize in a stable way — a pending extension.
- The
?domainpropagation happens when navigating from the workspace's group; when accessing the feature by another path (without a workspace), the context filter is not applied automatically.
The workspace's "Panels" gallery (cards with screenshots)
Each workspace group in the side menu has a "Panels" item that opens the panel gallery scoped to that workspace (?workspaceId=<id>). The gallery replaces the old listing of individual dashboards with a grid of ~3.5-inch cards (336px @96dpi), each with a "screenshot" of the panel:
The gallery shows only published panels: DATTA BI dashboards (kind == 'dattabi-dashboard'), the native panel of the workspace's context type (see below) and linked panels (panelRefs). Canvas (declarative) panels come in once they are published — only published sets them apart, as with any other type. (They used to be filtered out by kind !== 'canvas', back when there was no canvas publish step; there is one now, and the filter is gone.)
Native panel: one per context type
The workspace's native card is chosen by the type of the data context (tipoContexto) it groups, not by a platform-wide constant:
| Context type | Native card | Screen opened |
|---|---|---|
processo_judicial | Painel de Processos | process panel (view dashboard) |
dossie_cadastral | Painel de Onboarding PJ | list of PJ dossiers by CNPJ (view kyb-onboarding) |
| other types | none | — |
- The panel catalog resolves, per workspace, which native panels apply (the
workspaceIdsfield of each native entry) — the gallery does not need to know the context type, and a multi-context workspace gets one card per type. - The card's title and description come from the catalog, not from the page: the dossier card no longer inherits the text "Triagem e auditoria de processos judiciais".
- The stored link (
panelRefswithworkspaceId == "native") is reconciled when the workspace's contexts change and at platform startup — swap, never duplicate; a workspace with no known context (not registered, or registered with no type filled in) stays untouched. - The registration dossier card requires the
KYB_VIEWpermission — the same one that hides the "Onboarding PJ" side-menu entry. The catalog does not return the entry to whoever lacks it, so the card disappears from every surface at once instead of opening with a 403.
Each card has a "screenshot" of the panel:
- DATTA BI dashboard: uses the thumbnail from
GET /api/dattabi/dashboards/{id}/thumbnail.png, which prioritizes the real screenshot captured in the browser: when an editor opens/saves/publishes the dashboard,dattabi.jscomposes a faithful PNG (real pixels of the ECharts canvases + KPI/table texts) and sends it viaPUT .../thumbnail; the backend persists it in Neo4j and caches it in Valkey. The synthetic server-side render remained only as a fallback for dashboards never opened. ("Live" rendering via a BI iframe was tried and reverted — loading the whole app per card was too heavy/slow for a thumbnail.) - Native panel ("Painel de Processos" or "Painel de Onboarding PJ", depending on the context type): uses a "live" thumbnail — a non-interactive
iframeof the real panel, loaded in the embedded shell (/index.html?view=<view>&embed=1&domain=<workspace contexts>, which hides the sidebar and the top bar), scaled (logical width 1280,transform: scale, cropped at the top), lazy (IntersectionObserver) + responsive scaling (ResizeObserver),sandbox="allow-scripts allow-same-origin",pointer-events: none. There is no server-side screenshot renderer for natives — the live thumbnail is the 100% client-side, on-prem approach. (Until the screens were migrated to fragments the target was the standalone page/dashboard.html; it no longer exists, and the shell view replaced it.) - Canvas panel (declarative, built in the Panel Builder): the same "live" thumbnail as the native one, pointing at
/index.html?view=painel&ws=…&panel=…&mode=view&embed=1. Theembed=1hides the toolbar (pb-embed) and impliesviewOnly, so what shows up in the thumbnail is the finished panel, never the builder.
A thumbnail always comes from loading the panel in the background, and every finished panel has one — whether it is a platform panel (native or declarative) or a DATTABI dashboard. What differs between the types is only where the result stops: the platform panel renders the iframe straight into the card, while DATTABI stores the PNG on the server because opening the whole BI app per card would be too heavy to repeat on every load of the gallery.
The builder shows up on no surface of the gallery — not in the thumbnail (
embed=1hides it) and not in the breadcrumb (see below). It is an internal tool; the gallery shows panels, not the tool that assembles them. The DATTABI capture is queued bydatta-thumb-regen.js(one hidden iframe at a time,?thumb=1, a cap on captures and a per-capture timeout), shared with the DATTA BI Gallery.
When clicking a card:
- BI dashboard → opens the DATTABI screen locked in view-only mode (
?view=dattabi&mode=view&dashId=<id>; see DATTABI — User Guide §15.2). ThedashIdtravels in the query string, not as an internal navigation parameter, because that is where DATTABI reads it from. - Native panel → navigates to the native view of the context type (
dashboardfor judicial process,kyb-onboardingfor registration dossier), already filtered by the workspace's contexts, without reloading the application. - Canvas panel → opens the panel in view mode (
?view=painel&mode=view&ws=…&panel=…), with "Abrir no construtor" as a separate action — the gallery never enters the builder on its own.
Every card is a link: Ctrl/Cmd+click opens it in a new tab, and the keyboard reaches it without extra ARIA. The breadcrumb reflects the path actually taken (Painéis › panel), not the item's position in the menu — without that, the dossier panel would open as "Investigação › Onboarding PJ", which is right for someone who arrived from the menu and wrong for someone who clicked the card.
The builder does not show up in the URL either. The view is called
painel, notpanel-builder: the view id is visible in the address bar, and naming an internal tool there — to someone who merely opened a panel from the gallery — contradicts the very rule that keeps it out of the breadcrumb. There is a single view for viewing and editing; what changes ismode=view, not the screen. The old id (?view=panel-builder&…) remains as an alias, so links already saved keep working.
On a declarative panel opened as a viewer, the leaf of the breadcrumb is the panel title, never "Construtor de Painéis": DATTA › Painéis › Onboarding PJ — Pendências. The trigger is mode=view in the URL, not the navigation origin — origin is screen state and disappears on reload, and the card's deep-link is precisely a fresh load. The title comes from what the screen itself publishes (BREADCRUMB_SET). Inside the builder (no mode=view) the breadcrumb names it again, and the title becomes an extra level telling which panel is open: DATTA › Painéis › Construtor de Painéis › Onboarding PJ — Pendências.
External URLs possibly configured on a panel go through validation (safePanelUrl: only http(s)/relative) before window.open — blocking javascript:/data: (Project Guidelines §2).
Menu cleanup
- The "Panel" item (the
dashboardview = "Painel de Processos") was removed from the per-workspace feature list in the side menu, for being redundant with the "Panels" gallery — that panel remains accessible through the gallery's NATIVE card. Thedashboardview remains routable (the?view=dashboarddeeplink keeps working). - The panel builder (the Panel Manager under Configure › Contexts) was kept — the "Panels" gallery/menu just does not expose the builder.
Multiple panel selection + panels popover on the home
This section covers two capabilities complementary to the panel catalog described above:
- Linking multiple existing panels to a workspace in a single operation (multi-selection), by reference — without duplicating the panel definition.
- Panels popover on the home (chat): the button next to the send-query control no longer navigates blindly to the cases panel and now opens a popover that lists, system-wide, the panels ready for consumption.
Link existing (multi-selection) in the workspace manager
On the manager's Panels tab (/configurar/workspaces), the Link existing button opens an M3 dialog with checkboxes — the user checks several catalog panels (GET /api/config/panels) at once and confirms. The selection state is a set (Set) initialized with the already-persisted links, so opening the dialog again shows what is already linked (screens/settings-workspaces.html).
The options offered are the flattened catalog, filtered like this (screens/settings-workspaces.html):
const options = catalog.filter(
c => c.workspaceId !== selected.id || c.kind === 'dattabi-dashboard'
);That is: canvas panels from other workspaces (the workspace's own already appears by itself in the workspace's panel list) plus the DATTABI dashboards (kind === 'dattabi-dashboard') from any workspace, including its own. Each option shows the title and the origin (Plataforma for natives, or the source workspace's name).
Confirmation persists the full list of references via PUT /api/config/workspaces/{id}/panel-refs. The links appear in the Linked panels section, each with an origin badge — Platform for native panels, Linked for the rest — and the Open (opens the source panel) and Remove link actions (screens/settings-workspaces.html). The workspace's card counter in the listing sums own + linked panels: (w.panels || []).length + (w.panelRefs || []).length (screens/settings-workspaces.html).
Reference, not copy. The link points to the source panel — editing the panel in the owning workspace is reflected in all that link it; removing the link does not delete the source panel (CA-2). The source of truth remains single.
Backend validation (setPanelRefs)
The workspaces API validates, deduplicates and normalizes the list before writing. Current rules:
- Dedupe by the
workspaceId::panelIdkey (repeated refs are discarded). - Refs with null
workspaceId/panelIdare ignored. - Redundant self-reference: a canvas panel of the workspace itself already appears by itself, so a ref to it is silently discarded.
- Accepts: a native panel (panel catalog API), a canvas panel from any workspace visible to the user, or any reference to an existing and visible workspace (
knownWorkspace— includes"native"and the store's workspaces that passWorkspaceAccess.canSee; an invisible workspace is rejected with the same message as a non-existent id, so it cannot become an existence oracle). This covers the DATTABI dashboards (which come from the S2S catalog and are notWorkspace.Panel): they are accepted through the existence of the source workspace and resolved in the menu/gallery viaGET /api/config/panels. - Rejects only when the ref is not native, not canvas and the source workspace does not exist:
400 Bad Requestwith{"error":"Painel referenciado não existe: <ws>/<panel>"}(CA-4).
Note (evolution vs. the earlier section). The "Known limitations" section further above (inherited from the delivery of the DATTABI dashboards in the workspace) describes a gap in which refs to a DATTABI dashboard were rejected/discarded. The current
setPanelRefsis more permissive: it validates the ref by the existence of the source workspace, so DATTABI dashboards from other workspaces and from the workspace itself are accepted as references; refs that do not resolve in the catalog are gracefully ignored at render time (they break neither the catalog nor the menu).
Permission: CONFIG_EDIT, in a double layer — a path-matcher in the SecurityConfig (pathMatchers(PUT,"/api/config/**").hasAnyAuthority("CONFIG_EDIT","ROLE_ADMIN")) plus @RequirePermission("CONFIG_EDIT") on the method (which returns Mono, the declared-permission check's reactive branch).
Panels popover on the home (chat)
On the home (chat), the button next to the send-query control became a popover toggle (the same visual pattern as the JDBC agents picker). The first click opens the popover and loads the catalog only once (lazy); clicking an item closes the popover and navigates to the chosen panel (CA-3).
The catalog comes from GET /api/config/panels and is filtered, on the home, to only panels ready for consumption — platform natives and published BI dashboards (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([]));Why canvas stays out of the home (a scope adjustment, user feedback on 2026-06-11): canvas panels are under construction in the builder and are accessed through the Panel/Workspace Managers — including them on the home duplicated the "Painel de Processos" entry (seeded canvas vs. native) and exposed a link to the builder on the home.
Each item shows the panel's title and origin. States in pt-BR: "Carregando painéis..." (loading) and "Nenhum painel disponível para o seu usuário." (empty). If the fetch fails, the list falls back to empty — the popover degrades to the friendly empty state, without a raw error (Project Guidelines §5).
Click routing (chat.js:openPanelEntry, chat.js:384-403):
- Native (
p.native): navigates within the shell itself (SPA) viawindow.parent.postMessage({type:'NAVIGATE_TO', payload: p.view || 'dashboard'}). - BI dashboard (
kind === 'dattabi-dashboard'): opensp.url(/dattabi.html?dashId=<id>) in a new tab, after passing throughsafePanelUrl. - Canvas (does not appear on the home today, but the routing exists):
?view=painel&ws=<ws>&panel=<panel>&mode=viewin a new tab.
URL safety. URLs coming from the panel config go through safePanelUrl, which only accepts http/https (or relative) — blocking javascript:/data: writable by anyone with CONFIG_EDIT (Project Guidelines §2).
The PANEL_VIEW permission
Reading the catalog and opening panels in view mode now accept the dedicated PANEL_VIEW permission (Config category, registered in the central Permission catalog) or CONFIG_VIEW — any-of semantics via @RequirePermission({"PANEL_VIEW", "CONFIG_VIEW"}) (the annotation was extended to accept String[]). This avoids requiring CONFIG_VIEW (admin-leaning) from a regular user just to see/consume panels.
Current any-of gates:
| Endpoint | Any-of permission |
|---|---|
GET /api/config/panels (panel catalog API) | PANEL_VIEW \ |
GET /api/config/workspaces/{id} (workspaces API) | PANEL_VIEW \ |
POST /api/config/panel/{domain}/aggregate and .../preview (panels data API) | PANEL_VIEW \ |
For the panel data endpoints (aggregate/preview), which are rendering reads despite being POST (parameters only in the body), there is a dedicated matcher in the SecurityConfig before the mutation gate, so that PANEL_VIEW unlocks the read without granting write.
PANEL_VIEW is mapped to the default roles ADVANCED_USER, ANALISTA and READ_ONLY (RolePermissions + BuiltInRoleSync). Non-admin users need to log in again for the JWT to carry the new permission.
How to use (step by step)
How the sidebar reflects the active workspace:
The menu shows the features of the selected workspace, and switching the workspace on the card at the top replaces the list. There is no hub and no workspace listing in the navigation: the card is the control.
Always visible, whatever the workspace: Chat, Painéis, DATTA Captain, Documentação, Sistema and the profile. Those are platform, not team work — and filtering the Sistema hub could lock the administrator out of the configuration itself.
A group whose entire contents are disabled does not appear. While no workspace is resolved yet — during loading, if the call fails, or if the user has access to none — the menu shows everything: an empty menu would have neither a way out nor an explanation.
To choose what each workspace shows, use the Features tab of the management screen.
Create a workspace:
- Open .
- Click Novo workspace, at the top of the list on the left.
- Fill in the name (required) and a description (optional), then confirm with Criar workspace.
The workspace is created empty and is selected right away: the tabs on the right — Features, Data Contexts, Panels and Members — are where it gets its content. While it has no members it is public to anyone who can see the screen; once it gets its first member, it only shows up for members and the owner.
Requires the CONFIG_EDIT permission.
Create a panel of the workspace's own:
- Select the workspace and open the Panels tab.
- Click Novo painel, fill in the title and confirm with Criar e abrir.
The panel is created empty and opens in the panel builder in another tab, where the widgets and the data source are defined. This differs from Vincular existentes, which only brings a panel that already exists elsewhere into this workspace — linking neither copies nor duplicates it.
Link several panels at once:
- Go to Configure → Workspaces and select the workspace.
- On the Panels tab, click Link existing.
- Check, with the checkboxes, all the desired panels (BI dashboards and/or panels from other workspaces) and confirm.
- They appear in Linked panels with the origin badge (Platform/Linked) and the Open and Remove link actions.
- Removing the link removes only the reference — the source panel remains.
Discover and open panels from the home:
- On the home (chat), click the button next to the send-query control.
- The Panels popover lists the accessible panels (natives + BI dashboards), regardless of workspace.
- Click an item to open the chosen panel.