chore: auto-commit (4 archivos)
- data.h - data_http.cpp - main.cpp - views.cpp Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -176,6 +176,8 @@ struct RecentExecutionRow {
|
||||
int duration_ms = 0;
|
||||
bool success = true;
|
||||
std::string error_class;
|
||||
std::string error_snippet; // texto de error si success=false
|
||||
std::string command_snippet; // raw command (truncado/redactado) cuando function_id vacio
|
||||
std::string session_id;
|
||||
};
|
||||
|
||||
@@ -186,6 +188,12 @@ struct ClaudeUsageData {
|
||||
int total_violations = 0;
|
||||
int total_copies = 0;
|
||||
int total_versions = 0;
|
||||
// Calls que Claude lanza via tools registry-aware: MCP del registry
|
||||
// (mcp), `fn run` (fn_cli_run) o heredocs python. Excluye bash plano.
|
||||
int total_mcp = 0;
|
||||
// % de filas en la ventana con function_id != '' — es decir, calls que
|
||||
// golpean una funcion registrada. Indicador clave de adopcion del registry.
|
||||
double registry_pct = 0.0;
|
||||
std::vector<ClaudeUsageRow> top_functions; // top 20 by calls_total
|
||||
std::vector<ClaudeViolationRow> recent_violations; // last 20
|
||||
std::vector<ClaudeCopiedRow> copies;
|
||||
|
||||
+31
-3
@@ -612,12 +612,38 @@ bool load_claude_usage_http(const std::string& api_url, RegistryData& out,
|
||||
const std::string sql_viol = "SELECT COUNT(*) FROM violations" + wf_viol;
|
||||
out.claude.total_violations = extract_int(call_monitor_query(cli, sql_viol.c_str()));
|
||||
}
|
||||
// MCP / fn run / heredoc — herramientas registry-aware. Cubre las
|
||||
// variantes vistas en produccion: mcp, mcp_fn_search, mcp_fn_run,
|
||||
// fn_cli_run, fn_run_cli, heredoc, heredoc_py.
|
||||
static const char* kRegistryAwareToolCond =
|
||||
"(tool_used LIKE 'mcp%' OR tool_used LIKE 'heredoc%' "
|
||||
"OR tool_used IN ('fn_cli_run','fn_run_cli'))";
|
||||
{
|
||||
const std::string wf_and = wf_calls.empty()
|
||||
? std::string(" WHERE ") + kRegistryAwareToolCond
|
||||
: std::string(wf_calls + " AND " + kRegistryAwareToolCond);
|
||||
const std::string sql = "SELECT COUNT(*) FROM calls" + wf_and;
|
||||
out.claude.total_mcp = extract_int(call_monitor_query(cli, sql.c_str()));
|
||||
}
|
||||
// % calls que llamaron a una funcion del registry (function_id no vacio).
|
||||
{
|
||||
const std::string wf_and = wf_calls.empty()
|
||||
? std::string(" WHERE function_id != '' ")
|
||||
: std::string(wf_calls + " AND function_id != '' ");
|
||||
const std::string sql = "SELECT COUNT(*) FROM calls" + wf_and;
|
||||
int reg_hits = extract_int(call_monitor_query(cli, sql.c_str()));
|
||||
out.claude.registry_pct = (out.claude.total_calls > 0)
|
||||
? 100.0 * static_cast<double>(reg_hits) / static_cast<double>(out.claude.total_calls)
|
||||
: 0.0;
|
||||
}
|
||||
out.claude.total_copies = extract_int(call_monitor_query(cli, "SELECT COUNT(*) FROM copied_code"));
|
||||
out.claude.total_versions = extract_int(call_monitor_query(cli, "SELECT COUNT(*) FROM function_versions"));
|
||||
|
||||
// Recent executions (calls table) ordenada por ts DESC
|
||||
{
|
||||
std::string sql = "SELECT id, ts, function_id, tool_used, duration_ms, success, error_class, session_id "
|
||||
std::string sql = "SELECT id, ts, function_id, tool_used, duration_ms, success, error_class, session_id, "
|
||||
"COALESCE(command_snippet,'') AS command_snippet, "
|
||||
"COALESCE(error_snippet,'') AS error_snippet "
|
||||
"FROM calls" + wf_calls + " ORDER BY ts DESC LIMIT 100";
|
||||
json rx = call_monitor_query(cli, sql.c_str());
|
||||
if (rx.is_object() && rx.contains("rows")) {
|
||||
@@ -630,8 +656,10 @@ bool load_claude_usage_http(const std::string& api_url, RegistryData& out,
|
||||
row.tool_used = extract_str(r, 3);
|
||||
row.duration_ms = extract_row_int(r, 4);
|
||||
row.success = extract_row_int(r, 5) != 0;
|
||||
row.error_class = extract_str(r, 6);
|
||||
row.session_id = extract_str(r, 7);
|
||||
row.error_class = extract_str(r, 6);
|
||||
row.session_id = extract_str(r, 7);
|
||||
row.command_snippet = extract_str(r, 8);
|
||||
row.error_snippet = extract_str(r, 9);
|
||||
if (row.id > mx) mx = row.id;
|
||||
out.claude.recent_executions.push_back(row);
|
||||
}
|
||||
|
||||
@@ -86,6 +86,8 @@ static bool apply_ws_message(const std::string& raw) {
|
||||
std::vector<RecentExecutionRow> incoming;
|
||||
incoming.reserve(msg["calls"].size());
|
||||
int new_errors = 0;
|
||||
int new_mcp = 0;
|
||||
int new_reg_hits = 0;
|
||||
for (const auto& c : msg["calls"]) {
|
||||
RecentExecutionRow row;
|
||||
row.id = c.value("id", 0LL);
|
||||
@@ -94,15 +96,39 @@ static bool apply_ws_message(const std::string& raw) {
|
||||
row.tool_used = c.value("tool_used", "");
|
||||
row.duration_ms = c.value("duration_ms", 0);
|
||||
row.success = c.value("success", true);
|
||||
row.error_class = c.value("error_class", "");
|
||||
row.session_id = c.value("session_id", "");
|
||||
row.error_class = c.value("error_class", "");
|
||||
row.error_snippet = c.value("error_snippet", "");
|
||||
row.command_snippet = c.value("command_snippet", "");
|
||||
row.session_id = c.value("session_id", "");
|
||||
if (!row.success) new_errors++;
|
||||
// Registry-aware tools: mcp*, heredoc*, fn_cli_run, fn_run_cli.
|
||||
const auto starts_with = [](const std::string& s, const char* p) {
|
||||
size_t lp = std::strlen(p);
|
||||
return s.size() >= lp && std::memcmp(s.c_str(), p, lp) == 0;
|
||||
};
|
||||
if (starts_with(row.tool_used, "mcp") ||
|
||||
starts_with(row.tool_used, "heredoc") ||
|
||||
row.tool_used == "fn_cli_run" ||
|
||||
row.tool_used == "fn_run_cli") {
|
||||
new_mcp++;
|
||||
}
|
||||
if (!row.function_id.empty()) new_reg_hits++;
|
||||
incoming.push_back(std::move(row));
|
||||
}
|
||||
|
||||
if (type == "delta") {
|
||||
g_data.claude.total_calls += static_cast<int>(incoming.size());
|
||||
g_data.claude.total_errors += new_errors;
|
||||
g_data.claude.total_mcp += new_mcp;
|
||||
// registry_pct se recalcula sobre el total acumulado tras el delta.
|
||||
// Aproximacion: prev_pct * prev_total + new_reg_hits / new_total.
|
||||
int prev_total = g_data.claude.total_calls - static_cast<int>(incoming.size());
|
||||
int prev_hits = static_cast<int>(g_data.claude.registry_pct *
|
||||
static_cast<double>(prev_total) / 100.0 + 0.5);
|
||||
int total_hits = prev_hits + new_reg_hits;
|
||||
g_data.claude.registry_pct = (g_data.claude.total_calls > 0)
|
||||
? 100.0 * static_cast<double>(total_hits) / static_cast<double>(g_data.claude.total_calls)
|
||||
: 0.0;
|
||||
}
|
||||
|
||||
// Prepend (newer al frente). Para delta: filas vienen ASC del server,
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <cstring>
|
||||
#include <cctype>
|
||||
#include <ctime>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -53,6 +54,14 @@ static bool g_monitor_reload_request = false;
|
||||
static bool g_monitor_ws_connected = false;
|
||||
static long long g_monitor_last_event_ts = 0;
|
||||
|
||||
// Scatter: ventana solo del eje X. NO afecta KPIs ni queries — solo al plot.
|
||||
static const char* kScatterWindowLabels[] = {"1m", "5m", "15m", "1h", "6h"};
|
||||
static const int kScatterWindowSecs[] = {60, 300, 900, 3600, 21600};
|
||||
static int g_scatter_window_idx = 1; // 5m por defecto
|
||||
|
||||
// Recent Executions: si true, oculta filas con function_id vacio.
|
||||
static bool g_recent_only_registry = false;
|
||||
|
||||
bool monitor_consume_reload_request() {
|
||||
bool r = g_monitor_reload_request;
|
||||
g_monitor_reload_request = false;
|
||||
@@ -393,6 +402,23 @@ static void draw_monitor_toolbar(RegistryData& data) {
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
// Scatter axis window — separado de la ventana de KPIs.
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("Scatter:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(90.0f);
|
||||
if (ImGui::BeginCombo("##scatter_window", kScatterWindowLabels[g_scatter_window_idx])) {
|
||||
const int n = (int)(sizeof(kScatterWindowLabels) / sizeof(kScatterWindowLabels[0]));
|
||||
for (int i = 0; i < n; i++) {
|
||||
const bool selected = (i == g_scatter_window_idx);
|
||||
if (ImGui::Selectable(kScatterWindowLabels[i], selected)) {
|
||||
g_scatter_window_idx = i;
|
||||
}
|
||||
if (selected) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (fn_ui::button("Refresh", fn_ui::ButtonVariant::Subtle)) {
|
||||
g_monitor_reload_request = true;
|
||||
@@ -435,29 +461,159 @@ void draw_monitor(RegistryData& data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 5 KPI cards: Calls / Errors / Violations / Copies / Versions
|
||||
// 7 KPI cards: Calls / MCP / Reg% / Errors / Violations / Copies / Versions
|
||||
// "MCP" = calls Claude lanza via tools registry-aware (mcp / fn_cli_run /
|
||||
// heredoc). "Reg %" = porcentaje del total con function_id no vacio.
|
||||
const ImGuiTableFlags flags = ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_NoPadOuterX;
|
||||
if (ImGui::BeginTable("##monitor_kpi", 5, flags)) {
|
||||
struct KPI { const char* label; float value; const char* icon; };
|
||||
const KPI cards[5] = {
|
||||
{"Calls", static_cast<float>(cu.total_calls), TI_ACTIVITY},
|
||||
{"Errors", static_cast<float>(cu.total_errors), TI_ALERT_TRIANGLE},
|
||||
{"Violations", static_cast<float>(cu.total_violations), TI_ALERT_CIRCLE},
|
||||
{"Copies", static_cast<float>(cu.total_copies), TI_COPY},
|
||||
{"Versions", static_cast<float>(cu.total_versions), TI_HISTORY},
|
||||
if (ImGui::BeginTable("##monitor_kpi", 7, flags)) {
|
||||
struct KPI { const char* label; float value; const char* icon; const char* fmt; };
|
||||
const KPI cards[7] = {
|
||||
{"Calls", static_cast<float>(cu.total_calls), TI_ACTIVITY, "%.0f"},
|
||||
{"MCP", static_cast<float>(cu.total_mcp), TI_PLUG_CONNECTED, "%.0f"},
|
||||
{"Reg %", static_cast<float>(cu.registry_pct), TI_PERCENTAGE, "%.1f%%"},
|
||||
{"Errors", static_cast<float>(cu.total_errors), TI_ALERT_TRIANGLE, "%.0f"},
|
||||
{"Violations", static_cast<float>(cu.total_violations), TI_ALERT_CIRCLE, "%.0f"},
|
||||
{"Copies", static_cast<float>(cu.total_copies), TI_COPY, "%.0f"},
|
||||
{"Versions", static_cast<float>(cu.total_versions), TI_HISTORY, "%.0f"},
|
||||
};
|
||||
ImGui::TableNextRow();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int i = 0; i < 7; i++) {
|
||||
ImGui::TableSetColumnIndex(i);
|
||||
kpi_card(cards[i].label, cards[i].value, 0.0f, nullptr, 0, "%.0f", cards[i].icon);
|
||||
kpi_card(cards[i].label, cards[i].value, 0.0f, nullptr, 0,
|
||||
cards[i].fmt, cards[i].icon);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md));
|
||||
|
||||
// Live scatter — duracion (ms) por ejecucion, X = ts en tiempo real.
|
||||
// Ventana del eje X configurable via combo "Scatter" del toolbar (default
|
||||
// 5 min). Eje Y dinamico: 0..max_dentro_de_ventana + 500ms — asi siempre
|
||||
// hay headroom y el plot se reescala con picos sin perder la base.
|
||||
// ImPlot auto-scrollea fijando el limite derecho a `now` cada frame.
|
||||
// Dos series (ok/error) para colorear sin getter custom.
|
||||
{
|
||||
const double now = static_cast<double>(std::time(nullptr));
|
||||
const int win_s = kScatterWindowSecs[g_scatter_window_idx];
|
||||
const double left = now - static_cast<double>(win_s);
|
||||
|
||||
// Reuse buffers entre frames; reset cada frame es O(N) sobre <=200 filas.
|
||||
static std::vector<double> ok_x, ok_y, err_x, err_y;
|
||||
ok_x.clear(); ok_y.clear(); err_x.clear(); err_y.clear();
|
||||
ok_x.reserve(cu.recent_executions.size());
|
||||
ok_y.reserve(cu.recent_executions.size());
|
||||
double y_max_in_window = 0.0;
|
||||
for (const auto& r : cu.recent_executions) {
|
||||
double x = static_cast<double>(r.ts);
|
||||
double y = static_cast<double>(r.duration_ms);
|
||||
// Solo considera y_max sobre puntos visibles para que el rescale
|
||||
// no se quede pillado en un pico antiguo fuera de ventana.
|
||||
if (x >= left && y > y_max_in_window) y_max_in_window = y;
|
||||
if (r.success) { ok_x.push_back(x); ok_y.push_back(y); }
|
||||
else { err_x.push_back(x); err_y.push_back(y); }
|
||||
}
|
||||
const double y_top = y_max_in_window + 500.0;
|
||||
|
||||
// Forzar localtime en ImPlot — por defecto formatea como UTC y se
|
||||
// desincroniza con el resto de la app que usa hora local.
|
||||
ImPlot::GetStyle().UseLocalTime = true;
|
||||
|
||||
if (ImPlot::BeginPlot("##monitor_scatter", ImVec2(-1, 200),
|
||||
ImPlotFlags_NoTitle | ImPlotFlags_NoMouseText)) {
|
||||
// NoHighlight evita el efecto de iluminacion del fondo del eje
|
||||
// cuando el cursor pasa por encima (era visualmente ruidoso).
|
||||
ImPlot::SetupAxis(ImAxis_X1, "time",
|
||||
ImPlotAxisFlags_NoGridLines | ImPlotAxisFlags_NoHighlight);
|
||||
ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Time);
|
||||
ImPlot::SetupAxisLimits(ImAxis_X1, left, now + 5.0, ImPlotCond_Always);
|
||||
ImPlot::SetupAxis(ImAxis_Y1, "duration (ms)",
|
||||
ImPlotAxisFlags_NoHighlight);
|
||||
ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0, y_top, ImPlotCond_Always);
|
||||
|
||||
// Marcadores. Verde = ok, rojo = error. ImPlot v1.0+ usa ImPlotSpec
|
||||
// en lugar de SetNextMarkerStyle.
|
||||
if (!ok_x.empty()) {
|
||||
ImPlotSpec spec_ok;
|
||||
spec_ok.Marker = ImPlotMarker_Circle;
|
||||
spec_ok.MarkerSize = 4.0f;
|
||||
spec_ok.MarkerFillColor = ImVec4(0.30f, 0.85f, 0.40f, 0.85f);
|
||||
spec_ok.MarkerLineColor = ImVec4(0.30f, 0.85f, 0.40f, 1.0f);
|
||||
spec_ok.LineColor = ImVec4(0.30f, 0.85f, 0.40f, 1.0f);
|
||||
ImPlot::PlotScatter("ok", ok_x.data(), ok_y.data(),
|
||||
static_cast<int>(ok_x.size()), spec_ok);
|
||||
}
|
||||
if (!err_x.empty()) {
|
||||
ImPlotSpec spec_err;
|
||||
spec_err.Marker = ImPlotMarker_Cross;
|
||||
spec_err.MarkerSize = 5.0f;
|
||||
spec_err.MarkerFillColor = ImVec4(0.95f, 0.35f, 0.30f, 0.95f);
|
||||
spec_err.MarkerLineColor = ImVec4(0.95f, 0.35f, 0.30f, 1.0f);
|
||||
spec_err.LineColor = ImVec4(0.95f, 0.35f, 0.30f, 1.0f);
|
||||
ImPlot::PlotScatter("error", err_x.data(), err_y.data(),
|
||||
static_cast<int>(err_x.size()), spec_err);
|
||||
}
|
||||
|
||||
// Hover tooltip: localiza el punto mas cercano al cursor (en
|
||||
// pixel-space, no plot-space, para que la tolerancia sea uniforme
|
||||
// sin importar la escala del eje Y) y muestra function/tool/ms.
|
||||
if (ImPlot::IsPlotHovered() && !cu.recent_executions.empty()) {
|
||||
const ImVec2 mp_px = ImGui::GetIO().MousePos;
|
||||
const double kHitRadiusPx = 14.0;
|
||||
double best_dist = kHitRadiusPx;
|
||||
const RecentExecutionRow* best = nullptr;
|
||||
for (const auto& r : cu.recent_executions) {
|
||||
double x = static_cast<double>(r.ts);
|
||||
double y = static_cast<double>(r.duration_ms);
|
||||
if (x < left || x > now + 5.0) continue;
|
||||
ImVec2 px = ImPlot::PlotToPixels(x, y);
|
||||
double dx = px.x - mp_px.x;
|
||||
double dy = px.y - mp_px.y;
|
||||
double d = std::sqrt(dx * dx + dy * dy);
|
||||
if (d < best_dist) {
|
||||
best_dist = d;
|
||||
best = &r;
|
||||
}
|
||||
}
|
||||
if (best != nullptr) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
|
||||
ImGui::TextUnformatted(format_ts(best->ts).c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Function: %s", best->function_id.empty()
|
||||
? "-" : best->function_id.c_str());
|
||||
ImGui::Text("Tool: %s", best->tool_used.c_str());
|
||||
ImGui::Text("Duration: %d ms", best->duration_ms);
|
||||
if (!best->success) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::error);
|
||||
ImGui::Text("Error: %s", best->error_class.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md));
|
||||
|
||||
// Sub-tabs: Recent Executions (primera) / Top Functions / Violations / Copies
|
||||
if (ImGui::BeginTabBar("##monitor_sub_tabs")) {
|
||||
if (ImGui::BeginTabItem("Recent Executions")) {
|
||||
// Filtro: solo calls a funciones del registry (function_id no vacio).
|
||||
ImGui::Checkbox("Only registry functions", &g_recent_only_registry);
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
|
||||
int total_shown = 0, total_all = 0;
|
||||
for (const auto& r : cu.recent_executions) {
|
||||
total_all++;
|
||||
if (!g_recent_only_registry || !r.function_id.empty()) total_shown++;
|
||||
}
|
||||
ImGui::Text("(%d/%d)", total_shown, total_all);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::xs));
|
||||
|
||||
if (cu.recent_executions.empty()) {
|
||||
ImGui::TextDisabled("No executions in this window. Try widening (7d/30d/All) or wait for the next call.");
|
||||
} else {
|
||||
@@ -472,11 +628,38 @@ void draw_monitor(RegistryData& data) {
|
||||
ImGui::TableSetupColumn("Error");
|
||||
ImGui::TableHeadersRow();
|
||||
for (const auto& r : cu.recent_executions) {
|
||||
if (g_recent_only_registry && r.function_id.empty()) continue;
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::TextUnformatted(format_ts(r.ts).c_str());
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::TextUnformatted(r.function_id.empty() ? "-" : r.function_id.c_str());
|
||||
if (!r.function_id.empty()) {
|
||||
// Call de registry — destacada en color normal.
|
||||
ImGui::TextUnformatted(r.function_id.c_str());
|
||||
} else if (!r.command_snippet.empty()) {
|
||||
// Tool generica (Bash/heredoc): muestra prefijo `$` + snippet
|
||||
// truncado en muted para distinguirla visualmente de calls registry.
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
|
||||
// Truncado visual a ~80 chars para no romper layout.
|
||||
char buf[88];
|
||||
std::snprintf(buf, sizeof(buf), "$ %.80s%s",
|
||||
r.command_snippet.c_str(),
|
||||
r.command_snippet.size() > 80 ? "..." : "");
|
||||
ImGui::TextUnformatted(buf);
|
||||
// Hover tooltip con snippet completo (hasta 200 chars).
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(560.0f);
|
||||
ImGui::TextUnformatted(r.command_snippet.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
} else {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
|
||||
ImGui::TextUnformatted("-");
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ImGui::TextUnformatted(r.tool_used.c_str());
|
||||
ImGui::TableSetColumnIndex(3);
|
||||
@@ -553,6 +736,59 @@ void draw_monitor(RegistryData& data) {
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Failed Functions")) {
|
||||
// Subset de recent_executions: solo calls que golpean una funcion
|
||||
// del registry Y fallaron. Util para diagnostico: cuales funciones
|
||||
// del registry rompen mas + bug analysis cuando objetivo 1+2 caen.
|
||||
std::vector<const RecentExecutionRow*> failed;
|
||||
for (const auto& r : cu.recent_executions) {
|
||||
if (!r.success && !r.function_id.empty()) failed.push_back(&r);
|
||||
}
|
||||
if (failed.empty()) {
|
||||
ImGui::TextDisabled("No registry-function failures in this window. Healthy.");
|
||||
} else {
|
||||
const ImGuiTableFlags tf = ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders
|
||||
| ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY;
|
||||
if (ImGui::BeginTable("##monitor_failed_fns", 5, tf, ImVec2(0, 0))) {
|
||||
ImGui::TableSetupColumn("When");
|
||||
ImGui::TableSetupColumn("Function");
|
||||
ImGui::TableSetupColumn("Tool");
|
||||
ImGui::TableSetupColumn("Error class");
|
||||
ImGui::TableSetupColumn("Error snippet");
|
||||
ImGui::TableHeadersRow();
|
||||
for (const auto* p : failed) {
|
||||
const auto& r = *p;
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(format_ts(r.ts).c_str());
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::error);
|
||||
ImGui::TextUnformatted(r.function_id.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::TableSetColumnIndex(2); ImGui::TextUnformatted(r.tool_used.c_str());
|
||||
ImGui::TableSetColumnIndex(3); ImGui::TextUnformatted(r.error_class.c_str());
|
||||
ImGui::TableSetColumnIndex(4);
|
||||
if (r.error_snippet.empty()) {
|
||||
ImGui::TextDisabled("-");
|
||||
} else {
|
||||
char buf[120];
|
||||
std::snprintf(buf, sizeof(buf), "%.110s%s",
|
||||
r.error_snippet.c_str(),
|
||||
r.error_snippet.size() > 110 ? "..." : "");
|
||||
ImGui::TextUnformatted(buf);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(560.0f);
|
||||
ImGui::TextUnformatted(r.error_snippet.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Copied Code")) {
|
||||
if (cu.copies.empty()) {
|
||||
ImGui::TextDisabled("No copied code detected. Run `fn doctor copied-code` or `call_monitor copied-code`.");
|
||||
|
||||
Reference in New Issue
Block a user