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:
gti
2026-09-24 13:38:40 -03:00
co-authored by Claude Sonnet 5
parent bfd4d28aa4
commit 9a142975a6
13 changed files with 725 additions and 88 deletions
+72 -13
View File
@@ -80,11 +80,11 @@ def test_dashboard_renders_operational_queue_labels_and_human_dates():
response = client.get("/")
assert response.status_code == 200
for label in ("Agents total", "Online", "Degraded", "Offline", "Failed", "Unknown"):
for label in ("Servidores", "Online", "Atenção", "Offline", "Falhou", "Não informado"):
assert label in response.text
assert "Produtos com blockers abertos" in response.text
assert "Blockers abertos" in response.text
assert "ATENÇÃO NECESSÁRIA" in response.text
assert "Atenção necessária" in response.text
assert "Ver todos" in response.text
assert "Jobs com falha" in response.text
assert "Agents desatualizados" in response.text
@@ -103,6 +103,68 @@ def test_dashboard_coverage_displays_reconciled_product_buckets():
assert "Cobertura reconciliada" in response.text
def test_agents_scope_querystring_distinguishes_active_inventory_from_history():
client, _, _ = _panel_client()
with client._session_factory() as session:
tenant = session.scalar(select(Tenant).where(Tenant.name == "Acme"))
tenant.active = False
session.commit()
current = client.get("/agents?scope=active")
history = client.get("/agents?scope=all")
assert current.status_code == history.status_code == 200
assert "Acme ERP" not in current.text
assert "Acme ERP" in history.text
def test_blockers_and_jobs_scope_routes_keep_history_and_server_filters():
client, agent_id, _ = _panel_client()
with client._session_factory() as session:
agent = session.get(Agent, agent_id)
agent.enabled = False
session.commit()
assert "P-001" not in client.get("/blockers?scope=active&field=CFOP&reason_code=MISSING_CONTEXT").text
assert "P-001" in client.get("/blockers?scope=all&field=CFOP&reason_code=MISSING_CONTEXT").text
assert "Acme ERP" not in client.get("/jobs?scope=active&status=RUNNING").text
assert "Acme ERP" in client.get("/jobs?scope=all&status=RUNNING").text
def test_dashboard_links_reach_scoped_health_blocker_failed_job_and_outdated_filters():
client, agent_id, _ = _panel_client()
with client._session_factory() as session:
installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
upsert_blocker(session, installation.tenant_id, {"company": "Acme", "product_code": "P-NCM", "description": "Mouse GTI", "blocker_type": "BLOCKED_MISSING_EVIDENCE", "field": "NCM", "reason_code": "INVALID_NCM", "input_fingerprint": "ncm-link"}, installation_id=installation.id)
session.add(Job(agent_id=agent_id, kind="SYNC_INCREMENTAL", status="FAILED", created_at=datetime.now(timezone.utc), error="falha longa"))
session.commit()
home = client.get("/").text
assert 'href="/agents?scope=active"' in home
assert 'href="/agents?scope=active&amp;health=ONLINE"' in home
assert 'href="/blockers?scope=active&amp;status=OPEN&amp;field=NCM"' in home
assert 'href="/jobs?scope=active&amp;status=FAILED"' in home
assert 'href="/agents?scope=active&amp;version=outdated"' in home
def test_list_filters_are_visible_and_clearable_with_ptbr_statuses():
client, _, _ = _panel_client()
agents = client.get("/agents?scope=active&health=ONLINE").text
blockers = client.get("/blockers?scope=active&field=CFOP").text
jobs = client.get("/jobs?scope=active&status=RUNNING").text
assert 'name="health"' in agents and 'value="ONLINE" selected' in agents
assert 'href="/agents?scope=active"' in agents and "Limpar filtros" in agents
assert "Saúde técnica: Online" not in agents
assert 'name="field"' in blockers and 'value="CFOP" selected' in blockers
assert "Evidência fiscal ausente" in blockers and "Prioridade: HIGH" not in blockers
assert 'href="/blockers?scope=active"' in blockers and "Limpar filtros" in blockers
assert 'value="RUNNING" selected' in jobs and "Executando" in jobs
assert 'href="/jobs?scope=active"' in jobs and "Limpar filtros" in jobs
def test_login_has_centered_card_and_explicit_field_labels():
client, _, _ = _panel_client()
@@ -126,12 +188,10 @@ def test_dashboard_lists_bounded_failed_job_rows_with_identity_error_age_and_emp
response = client.get("/")
assert response.status_code == 200
assert "Acme" in response.text and "Acme ERP" in response.text and "srv-01" in response.text
assert "erro curto" in response.text and "Job ID" in response.text and "Criado" in response.text
assert 'href="/jobs?job_id=' in response.text
assert "erro curto" in response.text and "Tipo / erro" in response.text and "Falhou" in response.text
assert 'href="/jobs?scope=active&amp;status=FAILED&amp;job_id=' in response.text
assert response.text.count("erro curto") == 10
assert 'title="2026-09-22T18:00:11+00:00"' in response.text
assert 'title="2026-09-22T18:05:11+00:00"' in response.text
assert ">22/09/2026 15:00<" in response.text
assert ">22/09/2026 15:05<" in response.text
with factory() as session:
session.query(Job).filter(Job.status == "FAILED").delete()
@@ -176,7 +236,7 @@ def test_agents_render_separate_health_and_fiscal_status_with_server_sort():
response = client.get("/agents?sort=worst_coverage&health=ONLINE&search=Acme")
assert response.status_code == 200
for label in ("Cliente", "Nome de exibição", "Hostname", "Saúde técnica", "Status fiscal", "Cobertura fiscal", "Versão"):
for label in ("Cliente / servidor", "Saúde", "Status fiscal", "Cobertura", "Produtos", "Versão", "Último contato"):
assert label in response.text
assert "Acme ERP" in response.text
assert "srv-01" in response.text
@@ -222,9 +282,8 @@ def test_agent_list_hides_technical_uuid_and_explains_remainder():
assert response.status_code == 200
assert technical_uuid not in response.text
assert "classificados" in response.text
assert "bloqueados com segurança" in response.text
assert "falhas técnicas" in response.text
assert "bloqueados" in response.text
assert "Não informada" in response.text
assert "<td>Desconhecida<small>Desconhecida</small></td>" not in response.text
@@ -254,7 +313,7 @@ def test_jobs_render_client_identity_duration_and_preserve_filters():
response = client.get("/jobs?status=RUNNING&search=Acme")
assert response.status_code == 200
for label in ("Cliente", "Nome de exibição", "Hostname", "Job ID", "Tipo", "Status", "Criado", "Finalizado", "Duração", "Erro"):
for label in ("Cliente / servidor", "Tipo / job", "Status", "Criado", "Finalizado / duração", "Erro / ação"):
assert label in response.text
assert "Acme" in response.text
assert "srv-01" in response.text
@@ -300,10 +359,10 @@ def test_blockers_render_visible_filters_summary_priority_and_detail_link():
response = client.get("/blockers?client=Acme&installation=srv-01&field=CFOP&blocker_type=BLOCKED_MISSING_EVIDENCE&status=OPEN&age=0h&search=P-001")
assert response.status_code == 200
for label in ("Cliente", "Instalação / Agent / Servidor", "Campo", "Tipo de blocker", "Status", "Idade", "Produto ou busca", "Blockers abertos", "Produtos afetados", "Clientes afetados", "CFOP", "ST", "ICMS", "CSOSN", "NCM", "OTHER", "Prioridade"):
for label in ("Cliente", "Instalação / servidor", "Campo", "Tipo de blocker", "Status", "Idade", "Produto ou busca", "Blockers abertos", "Produtos afetados", "Clientes afetados", "CFOP", "ST", "ICMS", "CSOSN", "NCM", "Outros", "Prioridade"):
assert label in response.text
assert f"/blockers/{blocker_id}" in response.text
assert "CRITICAL" in response.text or "HIGH" in response.text
assert "Alta" in response.text and "Evidência fiscal ausente" in response.text
assert 'value="Acme"' in response.text
assert 'value="BLOCKED_MISSING_EVIDENCE"' in response.text
assert "window.confirm" not in response.text
+189 -10
View File
@@ -5,15 +5,27 @@ from sqlalchemy.dialects import postgresql
from central.app.audit import record_audit
from central.app.blocked_products import upsert_blocker
from central.app.control_plane import _blocker_breakdown_query, dashboard_summary, list_blockers, list_installations, list_jobs
from central.app.control_plane import _blocker_breakdown_query, blocker_filter_summary, dashboard_summary, jobs_status_summary, list_blockers, list_installations, list_jobs
from central.app.config import settings
from central.app.events import record_event
from central.app.models import Agent, AgentSnapshot, AuditEvent, BlockedProduct, Installation, Job, Tenant
from central.app.models import Agent, AgentEnrollment, AgentRelease, AgentSnapshot, AuditEvent, BlockedProduct, Installation, Job, Tenant
from central.app.panel_presentation import blocker_reason_label, blocker_type_label, fiscal_status_label, job_kind_label, job_status_label, priority_label
from central.tests.test_control_plane import setup_db
NOW = datetime(2026, 9, 22, 19, 0, tzinfo=timezone.utc)
def test_operator_labels_are_ptbr_without_changing_internal_codes():
assert job_status_label("FAILED") == "Falhou"
assert job_status_label("RUNNING") == "Executando"
assert job_kind_label("SYNC_INCREMENTAL") == "Sincronização incremental"
assert blocker_type_label("BLOCKED_MISSING_EVIDENCE") == "Evidência fiscal ausente"
assert blocker_reason_label("INVALID_NCM") == "NCM inválido"
assert priority_label("HIGH") == "Alta"
assert fiscal_status_label("BLOCKED") == "Bloqueado"
def _installation(session, index, *, health="ONLINE", coverage=80, blocked=0, version="1.0.0rc1"):
tenant = Tenant(name=f"Client {index}")
session.add(tenant)
@@ -80,14 +92,14 @@ def _blocker(session, tenant, *, product, field="CFOP", blocker_type="BLOCKED_MI
def test_dashboard_reconciles_five_health_states_and_counts_distinct_safe_products():
session_factory = setup_db()
with session_factory() as session:
first = _installation(session, 1, health="ONLINE", blocked=2)[0]
second = _installation(session, 2, health="DEGRADED")[0]
first, _, first_installation, _ = _installation(session, 1, health="ONLINE", blocked=2)
second, _, second_installation, _ = _installation(session, 2, health="DEGRADED")
_installation(session, 3, health="OFFLINE")
_installation(session, 4, health="FAILED")
_installation(session, 5, health="UNKNOWN")
_blocker(session, first, product="P1", field="CFOP", index=1)
_blocker(session, first, product="P1", field="NCM", index=2)
_blocker(session, second, product="P2", field="ICMS", index=3)
_blocker(session, first, product="P1", field="CFOP", index=1, installation_id=first_installation.id)
_blocker(session, first, product="P1", field="NCM", index=2, installation_id=first_installation.id)
_blocker(session, second, product="P2", field="ICMS", index=3, installation_id=second_installation.id)
session.commit()
summary = dashboard_summary(session, now=NOW)
@@ -100,6 +112,173 @@ def test_dashboard_reconciles_five_health_states_and_counts_distinct_safe_produc
assert summary["online"] == 1 and summary["safe_blocked"] == 2
def test_installation_inventory_active_scope_excludes_inactive_and_revoked_but_keeps_history():
factory = setup_db()
with factory() as session:
active = _installation(session, 1)[2]
inactive_tenant, _, _, _ = _installation(session, 2)
inactive_tenant.active = False
_, disabled_agent, _, _ = _installation(session, 3)
disabled_agent.enabled = False
tenant, revoked_agent, revoked, _ = _installation(session, 4, health="UNKNOWN")
_, _, paused, _ = _installation(session, 5)
paused.processing_enabled = False
release = AgentRelease(version="test-inventory", platform="windows", architecture="x64", filename="test.exe", storage_path="test.exe", size_bytes=1, sha256="x" * 64)
session.add(release)
session.flush()
session.add(AgentEnrollment(enrollment_uuid=revoked.installation_id, tenant_id=tenant.id, agent_id=revoked_agent.id, display_name="Histórico", release_id=release.id, status="REVOKED"))
session.commit()
current = list_installations(session, page_size=10, now=NOW)
historical = list_installations(session, page_size=10, scope="all", now=NOW)
unknown = list_installations(session, health="UNKNOWN", now=NOW)
assert {row["installation_id"] for row in current["items"]} == {active.installation_id, paused.installation_id}
assert current["total"] == 2
assert historical["total"] == 5
assert unknown["total"] == 0
assert {row["installation_id"]: row["operational_active"] for row in historical["items"]}[revoked.installation_id] is False
def test_outdated_agent_filter_returns_only_outdated_active_installations():
factory = setup_db()
with factory() as session:
_, _, outdated, _ = _installation(session, 1, version="0.9.0")
_, _, _, current_snapshot = _installation(session, 2, version=settings.agent_server_version)
current_snapshot.version_drift = "CURRENT"
_, disabled, _, _ = _installation(session, 3, version="0.9.0")
disabled.enabled = False
session.commit()
result = list_installations(session, version="outdated", now=NOW)
assert result["total"] == 1
assert result["items"][0]["installation_id"] == outdated.installation_id
def test_dashboard_excludes_inactive_history_and_groups_repeated_blockers():
factory = setup_db()
with factory() as session:
tenant, agent, installation, snapshot = _installation(session, 1, health="ONLINE", coverage=74, blocked=26, version=settings.agent_server_version)
snapshot.version_drift = "CURRENT"
for index in range(26):
_blocker(session, tenant, product=f"P{index:03}", field="NCM", index=index, installation_id=installation.id)
session.add(Job(agent_id=agent.id, kind="REPROCESS_PRODUCTS", status="FAILED", error="erro real", created_at=NOW))
old_tenant, old_agent, old_installation, _ = _installation(session, 2, health="UNKNOWN", version="0.9.0")
_blocker(session, old_tenant, product="HISTORICAL", field="CFOP", index=80, installation_id=old_installation.id)
session.add(Job(agent_id=old_agent.id, kind="REPROCESS_ALL", status="FAILED", error="erro histórico", created_at=NOW))
release = AgentRelease(version="test-dashboard", platform="windows", architecture="x64", filename="test.exe", storage_path="test.exe", size_bytes=1, sha256="y" * 64)
session.add(release)
session.flush()
session.add(AgentEnrollment(enrollment_uuid=old_installation.installation_id, tenant_id=old_tenant.id, agent_id=old_agent.id, display_name="Encerrada", release_id=release.id, status="REVOKED"))
inactive_tenant, _, _, _ = _installation(session, 3, health="OFFLINE")
inactive_tenant.active = False
_, disabled_agent, _, _ = _installation(session, 4, health="FAILED")
disabled_agent.enabled = False
session.commit()
summary = dashboard_summary(session, now=NOW)
assert summary["agents_total"] == 1
assert summary["health_counts"] == {"ONLINE": 1, "DEGRADED": 0, "OFFLINE": 0, "FAILED": 0, "UNKNOWN": 0}
assert summary["total_products"] == 100
assert summary["safe_blocked"] == 26
assert summary["open_blockers"] == 26
assert summary["safe_blocked_products"] == 26
assert summary["open_blocker_breakdown"]["NCM"] == 26
assert summary["open_blocker_breakdown"]["CFOP"] == 0
assert summary["failed_jobs"] == 1
fiscal_groups = [item for item in summary["attention_items"] if item.get("field") == "NCM"]
assert len(fiscal_groups) == 1
assert fiscal_groups[0]["group_count"] == 26
assert fiscal_groups[0]["products_affected"] == 26
assert fiscal_groups[0]["link"].startswith("/blockers?")
assert len(summary["attention_items"]) <= 10
assert all(item["client"] != "Client 2" for item in summary["attention_items"])
def test_blockers_and_jobs_keep_revoked_history_out_of_active_scope():
factory = setup_db()
with factory() as session:
tenant, agent, installation, _ = _installation(session, 1)
current = _blocker(session, tenant, product="CURRENT", field="NCM", index=1, installation_id=installation.id)
current.reason_code = "INVALID_NCM"
resolved = _blocker(session, tenant, product="RESOLVED", field="NCM", index=2, installation_id=installation.id)
resolved.resolved_at = NOW
session.add(Job(agent_id=agent.id, kind="REPROCESS_PRODUCTS", status="FAILED", created_at=NOW, error="current"))
old_tenant, old_agent, old_installation, _ = _installation(session, 2)
_blocker(session, old_tenant, product="OLD", field="NCM", index=3, installation_id=old_installation.id)
session.add(Job(agent_id=old_agent.id, kind="REPROCESS_ALL", status="FAILED", created_at=NOW, error="historical"))
old_agent.enabled = False
session.commit()
assert list_blockers(session, field="NCM")["total"] == 1
assert list_blockers(session, field="NCM", reason_code="INVALID_NCM")["items"][0]["id"] == current.id
assert list_blockers(session, field="NCM", scope="all")["total"] == 2
assert list_blockers(session, status="RESOLVED", scope="all")["items"][0]["id"] == resolved.id
assert blocker_filter_summary(session)["open_blockers"] == 1
assert list_jobs(session, status="FAILED")["total"] == 1
assert list_jobs(session, status="FAILED", scope="all")["total"] == 2
def test_jobs_status_kpis_count_active_jobs_by_status_in_one_group():
factory = setup_db()
with factory() as session:
_, agent, _, _ = _installation(session, 1)
_, disabled, _, _ = _installation(session, 2)
disabled.enabled = False
for status in ("PENDING", "RUNNING", "COMPLETED", "FAILED"):
session.add(Job(agent_id=agent.id, kind="SYNC_INCREMENTAL", status=status, created_at=NOW))
session.add(Job(agent_id=disabled.id, kind="REPROCESS_ALL", status="FAILED", created_at=NOW))
session.commit()
assert jobs_status_summary(session) == {"PENDING": 1, "RUNNING": 1, "COMPLETED": 1, "FAILED": 1}
assert jobs_status_summary(session, scope="all")["FAILED"] == 2
def test_attention_groups_rank_high_priority_before_limiting_recent_low_priority():
factory = setup_db()
with factory() as session:
tenant, _, installation, _ = _installation(session, 1, version=settings.agent_server_version)
high = _blocker(session, tenant, product="IMPORTANT", field="NCM", index=90, installation_id=installation.id)
high.last_seen_at = NOW - timedelta(days=3)
for index in range(12):
low = _blocker(session, tenant, product=f"LOW-{index}", field="OTHER", blocker_type="UNMAPPED", index=index, installation_id=installation.id)
low.reason_code = f"OTHER_{index}"
low.last_seen_at = NOW - timedelta(minutes=index)
session.commit()
attention = dashboard_summary(session, now=NOW)["attention_items"]
assert len(attention) <= 10
assert any(item.get("field") == "NCM" for item in attention)
def test_dashboard_query_count_stays_bounded_with_many_blockers():
factory = setup_db()
with factory() as session:
tenant, _, installation, _ = _installation(session, 1, version=settings.agent_server_version)
for index in range(130):
_blocker(session, tenant, product=f"P-{index:03}", field="NCM", index=index, installation_id=installation.id)
session.commit()
statements = []
def count_statement(conn, cursor, statement, parameters, context, executemany):
statements.append(statement)
event.listen(session.bind, "before_cursor_execute", count_statement)
try:
summary = dashboard_summary(session, now=NOW)
finally:
event.remove(session.bind, "before_cursor_execute", count_statement)
assert summary["open_blockers"] == 130
assert len(statements) <= 18
assert not any("SELECT" in sql.upper() and "LIMIT 130" in sql.upper() for sql in statements)
def test_snapshot_coverage_exposes_unclassified_residual_and_overcount():
session_factory = setup_db()
with session_factory() as session:
@@ -290,7 +469,7 @@ def test_legacy_tenant_blocker_has_no_fabricated_installation_context():
with session_factory() as session:
tenant, _, installation, _ = _installation(session, 1)
row = _blocker(session, tenant, product="LEGACY", index=99)
result = list_blockers(session, tenant_id=tenant.id)
result = list_blockers(session, tenant_id=tenant.id, scope="all")
assert result["items"][0]["id"] == row.id
assert result["items"][0]["installation_id"] is None
@@ -370,11 +549,11 @@ def test_lowercase_required_blocker_field_reaches_attention_candidates():
session_factory = setup_db()
with session_factory() as session:
tenant, _, installation, _ = _installation(session, 1)
blocker = _blocker(session, tenant, product="LOWER", field="cfop", index=120, installation_id=installation.id)
_blocker(session, tenant, product="LOWER", field="cfop", index=120, installation_id=installation.id)
attention = dashboard_summary(session, now=NOW)["attention_items"]
assert any(item["link"] == f"/blockers/{blocker.id}" for item in attention)
assert any(item.get("field") == "CFOP" and item["link"].startswith("/blockers?scope=active") for item in attention)
def test_blocker_breakdown_query_groups_on_same_postgres_expression():