feat: operational panel UI - active-scope dashboard, drill-down cards, scoped Agents/Blockers/Jobs, PT-BR labels, responsive CSS
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
# GTI Fiscal Operational UI Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. Work inline; the operator authorized proceeding without additional design confirmation.
|
||||
|
||||
**Goal:** Make Resumo, Agents, Blockers and Jobs a fast, accessible, PT-BR operational workflow where each important aggregate reaches a filtered detail in one click, with revoked/inactive history excluded from live indicators.
|
||||
|
||||
**Architecture:** A single SQLAlchemy active-installation predicate is shared by dashboard aggregates and paged list queries. Small presenter helpers translate labels; Jinja templates render native links and bounded summaries using Gerenciador tokens. Routes preserve GET querystrings and offer explicit active/all history scopes.
|
||||
|
||||
**Tech Stack:** Existing FastAPI, SQLAlchemy, Jinja, CSS tokens, minimal vanilla JS, pytest, Playwright, `scripts/deploy_hub.ps1`.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- `OPERATIONALLY_ACTIVE`: `Tenant.active IS TRUE AND Agent.enabled IS TRUE`, excluding linked `AgentEnrollment` status `REVOKED`, `FAILED`, `EXPIRED`; no inference from client/installation names.
|
||||
- `Installation.processing_enabled=false` means paused jobs, not inactive server; keep it in health counts. Agents without linked enrollment remain active if Tenant and Agent are active.
|
||||
- Preserve history via `scope=all`; default `scope=active` across operational lists. Existing resolved blocker/history URLs remain reachable.
|
||||
- Preserve GTI Gerenciador client combobox, SSO, themes, HTTPS, Agent/runtime, fiscal engine and `FIREBIRD_WRITES=0`; no deletion of historical data.
|
||||
- All filtering and pagination server-side (50/page); bounded SQL aggregates before LIMIT 10; no N+1 or client-side loading of products.
|
||||
- Do not commit unless explicitly asked. Deploy exclusively through `scripts/deploy_hub.ps1`; no ad-hoc SSH/deploy script.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Shared active scope and paginated inventory
|
||||
|
||||
**Files:** `central/app/control_plane.py`, `central/tests/test_operational_view_models.py`, `central/app/web.py`.
|
||||
|
||||
**Interfaces:** `operational_installation_predicate()` yields a SQLAlchemy condition on Tenant/Agent/Installation, using `~exists(select(AgentEnrollment.id).where(AgentEnrollment.agent_id == Agent.id, AgentEnrollment.status.in_(["REVOKED", "FAILED", "EXPIRED"])))`. `list_installations(..., scope="active", version=None)` accepts `scope=active|all`; `version="outdated"` restricts to outdated versions. Web route validates scope/health/sort/version before querying and passes `query_base` with these filters.
|
||||
|
||||
- [ ] RED: seed active, Tenant.active=False, Agent.enabled=False, ENROLLED and REVOKED linked cases and assert default `list_installations` contains only active ids; `scope="all"` contains every id and preserves `health` filtering. Assert a `processing_enabled=False` active installation remains visible. Run `python -m pytest central/tests/test_operational_view_models.py -q` and confirm missing scope behavior fails.
|
||||
- [ ] GREEN: add `AgentEnrollment` import/predicate, apply same predicate before both paged list and count; support `version="outdated"` via existing drift expression. Preserve SQL `LIMIT/OFFSET` and existing joins. Re-run focused test.
|
||||
- [ ] RED/GREEN: web TestClient `/agents?scope=active&health=UNKNOWN` and `/agents?scope=all` reflect selected filter, pagination links preserve scope; implement route validation and query_base, rerun `python -m pytest central/tests/test_control_panel_pages.py -q`.
|
||||
|
||||
### Task 2: Operational dashboard aggregates and grouped attention
|
||||
|
||||
**Files:** `central/app/control_plane.py`, `central/tests/test_operational_view_models.py`.
|
||||
|
||||
**Interfaces:** `dashboard_summary` returns same keys as today plus grouped `attention_items` (<=10) with `group_count`, `products_affected`, `link`, `priority` and client/server identity. Blocker totals and failed-job counts only include open/current records attributed to active installations. Limit candidate groups after SQL `GROUP BY (installation_id, field, blocker_type, reason_code)`; lower-case fields normalized to upper-case for breakdown. Dashboard does not load all blockers/products.
|
||||
|
||||
- [ ] RED: seed revoked Agent enrollment with UNKNOWN snapshot, inactive tenant, disabled Agent, 26 NCM blockers on one active installation plus historical resolved blockers; assert active health/coverage/blocker/job aggregates exclude inactive rows, NCM becomes one attention group with 26 products, and `len(attention_items) <= 10`. Run focused operational test and observe incorrect counts/duplicate rows.
|
||||
- [ ] GREEN: reuse Task 1 predicate for health/snapshots/outdated/failed jobs. For blocker aggregation join Tenant→Installation→Agent, filter active/open; count distinct `(tenant_id, product_code)` and compute grouped fields before `.limit(10)`, then order technical alerts before fiscal groups and outdated. Keep a bounded fixed number of SQL queries independent of product volume. Re-run focused test.
|
||||
- [ ] Measure number of executed SQL statements with SQLAlchemy `before_cursor_execute` for 1 vs 26 blockers; assert bounded query count and no statement per blocker. Compare SQL GROUP BY query on SQLite and PostgreSQL dialect compilation; inspect indexes from `blocked_products(installation_id,resolved_at,field)` and `jobs(agent_id,status)` before adding migration.
|
||||
|
||||
### Task 3: Scoped Blockers and Jobs data models
|
||||
|
||||
**Files:** `central/app/control_plane.py`, `central/app/web.py`, `central/tests/test_operational_view_models.py`, `central/tests/test_control_panel_pages.py`.
|
||||
|
||||
**Interfaces:** `list_blockers`/`blocker_filter_summary(..., scope="active")` and `list_jobs(..., scope="active")` reuse Task 1 active condition; `jobs_status_summary(..., scope="active")` returns counts PENDING/RUNNING/COMPLETED/FAILED using one GROUP BY. Web GET routes accept scope, keep valid filters/query_base on pagination, and expose a selected `job_id` result with untruncated error. `status=RESOLVED&scope=all` retains historic blocker access.
|
||||
|
||||
- [ ] RED: tests for active defaults excluding revoked/inactive records, `scope="all"` preserving history, `status="RESOLVED"`, `/blockers?field=NCM&scope=active`, `/jobs?status=FAILED&scope=active&job_id=123`, and no product dropdown; run focused tests and see active-scope failures.
|
||||
- [ ] GREEN: apply active predicate after existing joins (for blocker query, require linked active installation only when `scope=active`); keep `page_size=50`, filters and count separate. Add small status GROUP BY and route params. Render one selected job detail from the returned page data or a single scoped `job_id` query; full error only there. Re-run focused tests.
|
||||
|
||||
### Task 4: Human-readable presentation and dashboard/filters markup
|
||||
|
||||
**Files:** `central/app/panel_presentation.py`, `central/templates/dashboard.html`, `central/templates/agents.html`, `central/templates/blockers.html`, `central/templates/jobs.html`, `central/tests/test_control_panel_pages.py`, `central/tests/test_operational_view_models.py`.
|
||||
|
||||
**Interfaces:** `job_status_label`, `job_kind_label`, `blocker_type_label`, `blocker_reason_label`, `priority_label`, `fiscal_status_label` map known enums, falling back to readable text; retain raw value on details. All dashboard health cards are `<a class="metric metric-link" href="/agents?scope=active&health=...">`; blocker field positive links preserve scope/status; zero stays text. Filter chips and clear links display active selections.
|
||||
|
||||
- [ ] RED: TestClient HTML assertions for card→health filter, breakdown NCM→field, failed jobs→FAILED, outdated→version, selected filters/clear actions, human PT-BR labels; confirm old HTML fails.
|
||||
- [ ] GREEN: add presenter mappings to `panel_presentation.py` and include presentation fields in view-model dictionaries; render cards as links, attention groups with count/CTA, compact failed jobs/outdated; edit Agents table to combine identity columns, Blockers table product link + human labels, Jobs compact columns + detail/error. Keep historical routes intact, use `query_base`/`urlencode` to preserve filters and avoid XSS/unescaped params. Re-run HTML assertions.
|
||||
- [ ] Retain the Nova instalação combobox unchanged: TestClient asserts `client_id` hidden, searchable active Gerenciador-only clients, keyboard/mouse JS and server-side validation still present.
|
||||
|
||||
### Task 5: Interaction/CSS and responsive presentation
|
||||
|
||||
**Files:** `central/static/control-panel.css`, `central/static/control-panel.js`, templates from Task 4.
|
||||
|
||||
- [ ] RED: browser preview at 1366/1920/375 exposes horizontal scroll on Agents today and non-clickable cards; capture snapshot/measure `scrollWidth`, focus order and toolbar visibility.
|
||||
- [ ] GREEN: use only `var(--cor-*)` tokens for `.metric-link:hover/:focus-visible`, active-filter chips, grouped attention, semantic badges, compact tables and responsive 375px row layout. Set Agents table sizing to available content area and hide secondary fields below tablet; no 960px minimum for Agents on desktop. Keep Blockers/Jobs list bounded and filters stacked on mobile. Vanilla JS event delegation on `tr[data-href]` for mouse clicks outside existing links; keep primary `<a>` for Tab/Enter and avoid nested controls/intercepting modifier clicks.
|
||||
- [ ] Browser check at 1366x768, 1920x1080, 375px for focus-visible, Tab/Enter (and Space on buttons), no desktop horizontal scroll in Agents, readable names, light/dark styles. Adjust CSS and repeat visual checks until criteria pass.
|
||||
|
||||
### Task 6: Full verification and official deploy
|
||||
|
||||
**Files:** `scripts/deploy_hub.ps1` (execute only; do not edit), test suites.
|
||||
|
||||
- [ ] Run `python -m pytest central/tests -q`, `python -m pytest tests -q`, and `python -m pytest agent/tests -q`; capture totals and failures. Verify no runtime/engine/SSO/auth files changed; `git diff --check` and inspect status/diff.
|
||||
- [ ] Verify dashboard query count and, if SQL plan shows a missing index, first add a small Alembic migration plus migration test, re-run affected and full tests, and deploy with the same official script. No migrations solely for aesthetics.
|
||||
- [ ] Use authenticated browser smoke on Resumo, Agents, Blockers, Jobs and Nova instalação; click Resumo→Online/Total/NCM/failed job/outdated Agent and navigate back. Recheck server-side selected filters and disabled/revoked exclusion at 1920x1080, 1366x768, 375px in light and dark. If production SSO login is unavailable, do equivalent smoke with TestClient/local preview and report production visual check as blocked.
|
||||
- [ ] Run only `powershell -ExecutionPolicy Bypass -File .\scripts\deploy_hub.ps1` (or `pwsh -File .\scripts\deploy_hub.ps1` if script requires pwsh) as documented in `docs/DEPLOY_HUB.md`. Check `logs/last_deploy_result.json`, release health and public `https://gtifiscal.gtigrupo.com.br/health`. No ad-hoc SSH, no write to Firebird, no client action.
|
||||
- [ ] Report the user-requested field list with observed evidence; `COMMITS=nenhum` if no explicit request to commit, and truthful blockers where browser authentication/production smoke cannot be completed.
|
||||
@@ -0,0 +1,41 @@
|
||||
# GTI Fiscal — painel operacional com drill-down
|
||||
|
||||
**Objetivo.** Refinar as telas atuais Resumo, Agents, Blockers, Jobs e Nova instalação para operação de ~70 clientes, inclusive clientes com 20.000+ produtos. Preservar visual do Gerenciador, stack Jinja/FastAPI/SQLAlchemy e funcionalidades fiscais, Agent, SSO e sincronização existentes. Sem gráficos decorativos, framework frontend novo nem carga de produtos no browser.
|
||||
|
||||
## Semântica operacional e histórico
|
||||
|
||||
Uma expressão SQL compartilhada define `OPERATIONALLY_ACTIVE` para instalação vinculada a Agent: `Tenant.active IS TRUE`, `Agent.enabled IS TRUE` e ausência de enrollment vinculado em estado `REVOKED`, `EXPIRED` ou `FAILED` (estados finais/inativos). Agent legado sem enrollment não é revogado por inferência; clientes `LOCAL` não são classificados por nome. `Installation.processing_enabled=false` pausa processamento, **não** equivale a instalação revogada: ainda pode comunicar heartbeat e indicar indisponibilidade. Nunca excluir registros por nome `E2E` ou por versão antiga. O registro histórico permanece intacto.
|
||||
|
||||
Dashboard usa exclusivamente esta expressão para contagem de saúde, soma de snapshots/cobertura, blockers operacionais, jobs falhos operacionais, versão desatualizada e atenção. Blockers ativos requerem instalação vinculada operacionalmente ativa e `resolved_at IS NULL`; blockers legados sem instalação e blockers resolvidos permanecem nas listagens históricas. A página Agents usa `scope=active` por padrão e `scope=all` para inventário/histórico (com indicação de histórico e badges); Blockers/Jobs usam o mesmo `scope` para a lista e seus KPIs, preservando filtros adicionais. Um card do Resumo sempre leva à visão `scope=active` e ao filtro correspondente. O filtro `scope=all` inclui registros inativos/revogados, e `status=RESOLVED` em Blockers permite acessar problemas resolvidos. Totais do dashboard e contagens da listagem ativa são reconciliados pela mesma expressão SQL.
|
||||
|
||||
## Resumo: decisão em um clique
|
||||
|
||||
Seis cards `<a>` completos para Total, Online, Degraded, Offline, Failed, Unknown; destino `/agents?scope=active` com `health=<ENUM>` quando aplicável. Labels de apresentação PT-BR: Servidores, Online, Atenção, Offline, Falhou, Não informado. A identidade técnica do filtro permanece na URL e na query SQL. Cobertura: total/completos levam a Agents com ordenação fiscal (visão disponível), bloqueados levam a `/blockers?scope=active&status=OPEN`, falhas técnicas a `/agents?scope=active&health=FAILED`, e não classificados a Agents ordenados por pior cobertura. Sem prometer filtros de produto que o sistema não possui. O breakdown CFOP/ST/ICMS/CSOSN/NCM/OTHER liga valores positivos a `/blockers?scope=active&status=OPEN&field=<ENUM>`; zeros ficam visualmente neutros.
|
||||
|
||||
“Atenção necessária” usa até 10 **grupos**, não 10 linhas cruas: primeiro instalações com falha técnica, depois saúde Failed/Degraded/Offline, grupos de blockers fiscais por instalação/campo/tipo/motivo, versões desatualizadas e outros estados realmente acionáveis. Dentro do grupo fiscal: número de blockers abertos e produtos distintos; agrupar **em SQL antes do LIMIT**, com ordenação por gravidade e recência. Um grupo oferece link de lista filtrada de blockers ou detalhe de Agent/job. Instalações revogadas, agentes desabilitados, clientes inativos, itens resolvidos ou sem destino operacional não aparecem. Link “Ver todos” abre lista operacional correspondente. Evitar duplicar a mesma instalação como alerta de versão quando um alerta técnico de prioridade maior já a representa; histórico continua nas listas.
|
||||
|
||||
Jobs falhos: contagem e lista curta da operação ativa com Cliente, Agent/hostname, tipo, erro truncado, momento e link `Ver job` para `/jobs?scope=active&status=FAILED&job_id=<id>`. A página Jobs exibe erro integral quando `job_id` é informado. Link “Ver jobs” abre `status=FAILED`. Agents desatualizados: lista compacta em largura suficiente (sem sidebar estreita), cliente/hostname, versão atual/atualização e detalhe do Agent; poucos itens mais “Ver todos” com `version=outdated` aplicado.
|
||||
|
||||
## Agents / Servidores
|
||||
|
||||
Uma tabela densa, legível em 1366×768 sem scroll horizontal: cliente com instalação/hostname na mesma célula, saúde, fiscal (quantidades resumidas), cobertura, produtos, versão e último contato. Texto secundário só quando existir; badges mostram apenas o valor humano, sem repetir “Saúde técnica:” ou “Status fiscal:”. Busca, saúde (todos os cinco estados), ordenação e scope via GET; chip de filtro ativo e “Limpar filtros”; paginação mantém todos os parâmetros. Link primário visível no cliente/hostname é nativamente acessível por Tab/Enter; clique com mouse na restante da linha abre o mesmo detalhe via delegação pequena em JS, sem eliminar a semântica da tabela. Em telas pequenas, linhas podem assumir apresentação compacta com rótulos por célula, mantendo filtros e ações expostos.
|
||||
|
||||
## Blockers
|
||||
|
||||
KPIs coerentes com `scope`, `status` e demais filtros (abertos, produtos/clientes afetados, resultado filtrado), com links apenas quando a métrica tem destino fiel. Breakdown filtrado liga campo positivo ao mesmo conjunto de filtros, trocando só `field`; filtro selecionado fica visível. Filtros existentes permanecem server-side (cliente, instalação, campo, tipo, status, idade, busca e identificadores existentes), dispostos em uma ou duas linhas no desktop e empilhados no mobile, com “Limpar filtros” retornando à visão ativa padrão.
|
||||
|
||||
Tabela destaca código e descrição do produto como link para `/blockers/<id>`; exibe cliente/servidor, campo, tipo humano, prioridade humana, problema humano, último registro e CTA. Tradutor somente de apresentação mapeia enums conhecidos (`BLOCKED_MISSING_EVIDENCE` → `Evidência fiscal ausente`, `INVALID_NCM` → `NCM inválido`, prioridades `CRITICAL/HIGH/MEDIUM/LOW` → `Crítica/Alta/Média/Baixa`) e cai em rótulo legível para desconhecidos; o detalhe mantém o código técnico original. Paginação limitada (50), consulta com COUNT separado, sem dropdown de produtos nem N+1.
|
||||
|
||||
## Jobs
|
||||
|
||||
Atalhos compactos para `PENDING`, `RUNNING`, `COMPLETED`, `FAILED`, todos filtrando `scope=active` e mostrando a seleção. Status PT-BR: Pendente, Executando, Concluído, Falhou; tipos conhecidos (`SYNC_INCREMENTAL`, `INITIAL_SYNC`, `REPROCESS_ALL`, `REPROCESS_PRODUCTS`) ganham labels humanos. A lista agrupa cliente e servidor, depois tipo, status, criado/finalizado, duração e erro curto. Erro integral e enum técnico aparecem na vista filtrada `job_id` da própria página, sem criar uma tela artificial. Seleção de scope histórico e “Limpar filtros” mantêm os demais parâmetros úteis. Não acionar jobs, somente leitura do banco.
|
||||
|
||||
## Estilo, acessibilidade e escala
|
||||
|
||||
Usar somente `central/static/control-panel.css` sobre os tokens imutáveis `gti-theme.css`. Estados semânticos: sucesso verde, atenção amarelo, falha vermelha, histórico/desconhecido cinza, bloqueio fiscal laranja. Foco visível, navegação Tab/Enter e Space nos controles apropriados, links `<a>` em cards; nenhum `div` simulando botão. Mobile 375 px preserva busca e ações, desktop 1366×768 e 1920×1080 aproveitam largura sem blocos de vazio. Testar light/dark por screenshot, não só inspecionando tokens. Combobox de clientes ativos da Nova instalação permanece com busca nome/CNPJ, mouse/teclado, `client_id` hidden e validação server-side; só retoque estético se necessário.
|
||||
|
||||
Queries devem usar agregados e grupos SQL, bounded LIMIT, COUNT e paginação server-side; não carregar listas de produtos/snapshots inteiros nem emitir uma consulta por item. Medir número de queries do dashboard e inspecionar planos quando filtros/campos precisarem de índice. Se necessário, migration de índices pequena e reversível; nunca mudar engine fiscal ou dados históricos. Regressões confirmam exclusão de inativos/revogados, coerência card→querystring→resultado, agrupamento, labels, paginação e query count/ausência de N+1.
|
||||
|
||||
## Validação e deploy
|
||||
|
||||
Rodar suites existentes e regressões focadas. Browser smoke em Resumo, Agents, Blockers, Jobs, Nova instalação; navegar Resumo→Online/Total/NCM/Jobs falhos/Agent desatualizado e voltar; verificar light/dark nos viewports 1920×1080, 1366×768, 375 px e Tab/Enter/Space. Nova instalação não deve criar registros no smoke. Usar **exclusivamente** `scripts/deploy_hub.ps1` para deploy/backup/migration/rollback/health, sem SSH improvisado nem scripts paralelos. Verificar health público e métricas operacionais pós-deploy. Se autenticação visual do Gerenciador não estiver disponível, reportar explicitamente o bloqueio em vez de alegar screenshot de produção.
|
||||
Reference in New Issue
Block a user