diff --git a/.superpowers/sdd/2026-09-22-control-panel-operational-ux-v1/task-5-report.md b/.superpowers/sdd/2026-09-22-control-panel-operational-ux-v1/task-5-report.md index e365cd2..cf909b6 100644 --- a/.superpowers/sdd/2026-09-22-control-panel-operational-ux-v1/task-5-report.md +++ b/.superpowers/sdd/2026-09-22-control-panel-operational-ux-v1/task-5-report.md @@ -55,10 +55,25 @@ write/deployment boundaries. ## Concerns -- The new UX contract documents the design's full technical timestamp in the - title attribute. The inspected templates currently pass the human-formatted - timestamp as the title value; runtime code was outside this task's allowed - scope. Align the title value in a runtime task or revise the contract after - deciding the intended presentation. +- Resolved in the timestamp tooltip follow-up below: templates now use raw ISO + UTC timestamps in `title` and retain localized human-readable visible text. - Full central tests retain the two pre-existing dependency warnings described above; they did not fail the suite. + +## Timestamp tooltip follow-up + +Resolved the full technical timestamp concern: agent, job, blocker, and +populated dashboard failure timestamps now keep their localized human-readable +text visible and expose the complete ISO UTC timestamp (including seconds and +`+00:00`) in the `title` attribute. Blocker detail includes first- and +last-seen timestamps. The existing raw ISO serializer now explicitly normalizes +naive SQLite timestamps as UTC, matching the presentation layer's existing +interpretation without changing the view-model shape. + +### TDD and verification + +- RED: `python -m pytest central/tests/test_control_panel_pages.py -q -k "localized_last_seen or localized_created or localized_seen_times or bounded_failed_job_rows"` — 4 expected failures because the tooltips did not contain the required raw ISO values. +- GREEN: `python -m pytest central/tests/test_control_panel_pages.py -q` — passed (`12 passed`). +- Full suite: `python -m pytest central/tests -q` — passed (`278 passed`, one existing `openpyxl` warning; pytest-asyncio also emits its existing fixture-loop deprecation warning). +- `git diff --check` — passed. +- Commit message: `fix: show technical timestamps in tooltips`. diff --git a/central/app/control_plane.py b/central/app/control_plane.py index 8231187..446da02 100644 --- a/central/app/control_plane.py +++ b/central/app/control_plane.py @@ -24,7 +24,10 @@ def _page(page: int, page_size: int): def _iso(value): - return value.isoformat() if value else None + if value is None: + return None + value = value.replace(tzinfo=timezone.utc) if value.tzinfo is None else value.astimezone(timezone.utc) + return value.isoformat() def _utc_now(value=None): diff --git a/central/templates/agent_detail.html b/central/templates/agent_detail.html index aa71824..0312cb8 100644 --- a/central/templates/agent_detail.html +++ b/central/templates/agent_detail.html @@ -4,7 +4,7 @@
{{ detail.client or "Cliente não identificado" }} / INSTALAÇÃO
{{ detail.hostname or "hostname indisponível" }}
Nenhum evento.
{% endfor %}Filtros aplicados no servidor.
Nenhum blocker aberto.
{% endfor %}INVENTÁRIO OPERACIONAL
Identidade do cliente primeiro; IDs técnicos ficam no detalhe.
| Cliente | Nome de exibição | Hostname | Saúde técnica | Status fiscal | Cobertura fiscal | Produtos | Versão | Último heartbeat |
|---|---|---|---|---|---|---|---|---|
| {{ item.client or "Cliente não identificado" }} | {{ item.display_name or "-" }}ID técnico: {{ item.installation_id }} | {{ item.hostname or "-" }} | Saúde técnica: {{ item.technical_health_label }} | Status fiscal: {{ item.fiscal_status_label }}{% if item.safe_blocked %}{{ item.safe_blocked }} bloqueados com segurança{% endif %} | {{ "%.1f"|format(item.fiscal_completion_percent)|replace(".", ",") }}%{{ item.fiscally_complete }} / {{ item.total_products }} | {{ item.total_products }}{{ item.failed_products }} falhas | {{ item.agent_version or "Desconhecida" }}{{ item.version_drift|title }} | {{ item.last_seen_display }}{{ item.last_seen_age }} |
| Nenhuma instalação encontrada. | ||||||||
| Cliente | Nome de exibição | Hostname | Saúde técnica | Status fiscal | Cobertura fiscal | Produtos | Versão | Último heartbeat |
|---|---|---|---|---|---|---|---|---|
| {{ item.client or "Cliente não identificado" }} | {{ item.display_name or "-" }}ID técnico: {{ item.installation_id }} | {{ item.hostname or "-" }} | Saúde técnica: {{ item.technical_health_label }} | Status fiscal: {{ item.fiscal_status_label }}{% if item.safe_blocked %}{{ item.safe_blocked }} bloqueados com segurança{% endif %} | {{ "%.1f"|format(item.fiscal_completion_percent)|replace(".", ",") }}%{{ item.fiscally_complete }} / {{ item.total_products }} | {{ item.total_products }}{{ item.failed_products }} falhas | {{ item.agent_version or "Desconhecida" }}{{ item.version_drift|title }} | {{ item.last_seen_display }}{{ item.last_seen_age }} |
| Nenhuma instalação encontrada. | ||||||||
RESOLUÇÃO OPERACIONAL
{{ detail.client or "Cliente não identificado" }} · {{ detail.product_code }}
{{ error }}
{{ detail.evidence|tojson(indent=2) }}{{ detail.evidence|tojson(indent=2) }}Valor atual por chave persistida para esta empresa.
{% for entry in context_entries %}Nenhum valor definido.
{% endfor %}Escolha uma chave fiscal validada. O reprocessamento será limitado ao produto deste blocker.
| Cliente | Instalação | Produto | Campo | Tipo de blocker | Prioridade | Problema | Última ocorrência | Ação |
|---|---|---|---|---|---|---|---|---|
| {{ item.client or "Cliente não identificado" }} | {{ item.display_name or "-" }}{{ item.hostname or "hostname indisponível" }} | {{ item.product_code }}{{ item.description or "" }} | {{ item.field or "OTHER" }} | Fiscal: {{ item.blocker_type }} | Prioridade: {{ item.priority }} | {{ item.reason }}{{ item.age }} | {{ item.last_seen_display }}{{ item.retry_count }} tentativas | Ver detalhe |
| Nenhum blocker corresponde aos filtros. | ||||||||
| Cliente | Instalação | Produto | Campo | Tipo de blocker | Prioridade | Problema | Última ocorrência | Ação |
|---|---|---|---|---|---|---|---|---|
| {{ item.client or "Cliente não identificado" }} | {{ item.display_name or "-" }}{{ item.hostname or "hostname indisponível" }} | {{ item.product_code }}{{ item.description or "" }} | {{ item.field or "OTHER" }} | Fiscal: {{ item.blocker_type }} | Prioridade: {{ item.priority }} | {{ item.reason }}{{ item.age }} | {{ item.last_seen_display }}{{ item.retry_count }} tentativas | Ver detalhe |
| Nenhum blocker corresponde aos filtros. | ||||||||
FILA DE AÇÃO
Nenhum item requer atenção imediata.
{% endfor %}{{ summary.failed_jobs }} jobs aguardam investigação.
| Cliente | Instalação | Job ID / Tipo | Erro | Criado | Finalizado |
|---|---|---|---|---|---|
| {{ job.client or "-" }} | {{ job.display_name or "-" }}{{ job.hostname or "hostname indisponível" }} | {{ job.job_id }}{{ job.kind }} | {{ job.error or "Sem detalhe de erro" }} | {{ job.created_display }}{{ job.created_age }} | {{ job.finished_display }}{{ job.finished_age }} |
Nenhum job com falha.
{% endif %}{{ summary.failed_jobs }} jobs aguardam investigação.
| Cliente | Instalação | Job ID / Tipo | Erro | Criado | Finalizado |
|---|---|---|---|---|---|
| {{ job.client or "-" }} | {{ job.display_name or "-" }}{{ job.hostname or "hostname indisponível" }} | {{ job.job_id }}{{ job.kind }} | {{ job.error or "Sem detalhe de erro" }} | {{ job.created_display }}{{ job.created_age }} | {{ job.finished_display }}{{ job.finished_age }} |
Nenhum job com falha.
{% endif %}Nenhum agent desatualizado.
{% endfor %}EXECUÇÕES
Acompanhe o que está em andamento e onde falhou.
| Cliente | Nome de exibição | Hostname | Job ID | Tipo | Status | Criado | Finalizado | Duração | Erro |
|---|---|---|---|---|---|---|---|---|---|
| {{ item.client or "Cliente não identificado" }} | {{ item.display_name or "-" }} | {{ item.hostname or "-" }} | #{{ item.job_id }}Agent técnico {{ item.agent_id }} | {{ item.kind }} | Status: {{ item.status }} | {{ item.created_display }} | {{ item.finished_display }} | {{ item.duration }} | {{ item.error or "-" }} |
| Nenhum job encontrado para os filtros atuais. | |||||||||
| Cliente | Nome de exibição | Hostname | Job ID | Tipo | Status | Criado | Finalizado | Duração | Erro |
|---|---|---|---|---|---|---|---|---|---|
| {{ item.client or "Cliente não identificado" }} | {{ item.display_name or "-" }} | {{ item.hostname or "-" }} | #{{ item.job_id }}Agent técnico {{ item.agent_id }} | {{ item.kind }} | Status: {{ item.status }} | {{ item.created_display }} | {{ item.finished_display }} | {{ item.duration }} | {{ item.error or "-" }} |
| Nenhum job encontrado para os filtros atuais. | |||||||||