feat(dashboard): página Historial con tabs ejecuciones y violaciones

Tab "Ejecuciones": tabla resumen + detalle (status, error, output, violations,
traza) por trace_id. Tab "Violaciones": log filtrable por severidad.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Juan
2026-05-11 12:53:17 +02:00
co-authored by Claude Opus 4.7
parent 826d5b494e
commit a36a8afa09
@@ -0,0 +1,53 @@
"""Página de historial de ejecuciones y log de violaciones."""
from __future__ import annotations
import streamlit as st
from agentforge_dashboard.client import CoreClient
from agentforge_dashboard.components.trace_view import render_trace
from agentforge_dashboard.components.violation_view import render_violations
@st.cache_resource
def get_client() -> CoreClient:
return CoreClient()
client = get_client()
st.title("📜 Historial")
st.caption("Ejecuciones registradas y log auditable de violaciones de guardrails.")
tabs = st.tabs(["Ejecuciones", "Violaciones"])
with tabs[0]:
executions = client.list_executions()
if not executions:
st.info("Aún no hay ejecuciones registradas.")
else:
st.dataframe(executions, hide_index=True, use_container_width=True)
trace_ids = [e["trace_id"] for e in executions]
selected = st.selectbox(
"Ver detalle",
trace_ids,
format_func=lambda t: t[:8] + "",
)
if selected:
detail = client.get_execution(selected)
st.markdown(f"**Status:** `{detail['status']}`")
if detail.get("error"):
st.error(f"Error: {detail['error']}")
if detail.get("final_output"):
st.subheader("Output final")
st.json(detail["final_output"])
render_violations(detail.get("violations", []))
render_trace(detail.get("decision_path", []))
with tabs[1]:
sev = st.selectbox("Filtrar severidad", ["(todas)", "info", "warning", "block"])
filter_kwargs = {} if sev == "(todas)" else {"severity": sev}
violations = client.list_violations(**filter_kwargs)
if not violations:
st.success("Sin violaciones registradas con ese filtro.")
else:
st.dataframe(violations, hide_index=True, use_container_width=True)