87b7ef45ff
Rebrand "Claude Usage" tab to "Monitor" and promote it to first/default tab in
the main TabBar. Monitor is the landing for the reactive loop (construir →
ejecutar → recopilar → analizar → mejorar).
UI additions:
- Local toolbar inside Monitor with date preset combo (1h / 24h / 7d / 30d /
All), manual Refresh button, and live LED + last-event-ts indicator.
- 5 KPI cards (was 4): added "Errors" derived from COUNT(*) FROM calls WHERE
success = 0 filtered by the active window.
- New sub-tab "Recent Executions" (first sub-tab) with columns: When,
Function, Tool, ms, OK (check/X colored), Error class. Backed by calls
table, sorted by ts DESC, limit 100, filtered by window.
- Violations sub-tab gains "When" column with formatted ts.
Data layer:
- data.h: RecentExecutionRow + window_secs + ws_connected/last_event_ts /
last_seen_call_id watermark on ClaudeUsageData.
- data_http.{h,cpp}: load_claude_usage_http now takes window_secs and embeds
ts_filter() in calls/errors/violations queries. total_errors populated.
recent_executions populated up to 100 rows. New standalone
load_recent_executions_http() for future WS-driven partial refetch.
- main.cpp: reload_data preserves window_secs across reloads; new
reload_monitor() does a Monitor-only refetch when the user changes the
window or clicks Refresh, without re-querying the full registry.
Wiring:
- views.h: draw_monitor + monitor_consume_reload_request() +
monitor_set_ws_state() exported. draw_claude_usage removed.
- views.cpp: render() consumes monitor_consume_reload_request each frame
and dispatches reload_monitor().
This is the UI half of issue 0086. The server-side WebSocket endpoint in
sqlite_api and the C++ WS client are next; the LED is wired to
monitor_set_ws_state but stays gray until those land.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
201 lines
6.7 KiB
C++
201 lines
6.7 KiB
C++
#include "app_base.h"
|
|
#include "imgui.h"
|
|
#include "core/fullscreen_window.h"
|
|
#include "core/app_menubar.h"
|
|
#include "core/app_about.h"
|
|
#include "core/app_settings.h"
|
|
#include "core/tokens.h"
|
|
#include "data.h"
|
|
#include "data_http.h"
|
|
#include "views.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <fstream>
|
|
|
|
static RegistryData g_data;
|
|
static std::string g_db_path;
|
|
static std::string g_api_url;
|
|
static bool g_loaded = false;
|
|
static bool g_using_http = false;
|
|
|
|
static void reload_data() {
|
|
// Conservar la ventana del Monitor entre recargas (no se pierde al refrescar).
|
|
int prev_window = g_data.claude.window_secs;
|
|
if (prev_window == 0 && g_data.claude.total_calls == 0) prev_window = 86400;
|
|
g_data = RegistryData{};
|
|
g_data.claude.window_secs = prev_window;
|
|
|
|
// Try HTTP API first
|
|
if (!g_api_url.empty()) {
|
|
g_loaded = load_registry_data_http(g_api_url, g_data);
|
|
if (g_loaded) {
|
|
g_using_http = true;
|
|
// Issue 0085d: best-effort load of Claude telemetry from
|
|
// ops:call_monitor. Falla silenciosamente si no esta disponible
|
|
// (la tab Monitor mostrara placeholder).
|
|
load_claude_usage_http(g_api_url, g_data, g_data.claude.window_secs);
|
|
return;
|
|
}
|
|
fprintf(stderr, "HTTP API failed, falling back to SQLite\n");
|
|
}
|
|
|
|
// Fallback to direct SQLite
|
|
g_using_http = false;
|
|
if (!g_db_path.empty()) {
|
|
g_loaded = load_registry_data(g_db_path.c_str(), g_data);
|
|
if (!g_loaded) {
|
|
fprintf(stderr, "Failed to load registry data from: %s\n", g_db_path.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Refetch SOLO de telemetria del Monitor. Se dispara al cambiar la ventana
|
|
// temporal o al recibir un evento WS que invalide el snapshot. No toca el
|
|
// registry general.
|
|
static void reload_monitor() {
|
|
if (g_api_url.empty() || !g_loaded) return;
|
|
load_claude_usage_http(g_api_url, g_data, g_data.claude.window_secs);
|
|
}
|
|
|
|
static void render() {
|
|
if (ImGui::GetIO().UserData != nullptr) {
|
|
ImGui::GetIO().UserData = nullptr;
|
|
reload_data();
|
|
}
|
|
|
|
// Issue 0086: el Monitor pide refetch parcial cuando el usuario cambia la
|
|
// ventana temporal o pulsa Refresh. No pasa por reload_data() para no
|
|
// tirar abajo todo el dataset del registry.
|
|
if (monitor_consume_reload_request()) {
|
|
reload_monitor();
|
|
}
|
|
|
|
if (!g_loaded) {
|
|
fullscreen_window_begin("##error");
|
|
ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1),
|
|
"Could not load registry data");
|
|
ImGui::Spacing();
|
|
if (!g_api_url.empty())
|
|
ImGui::Text("API: %s (unreachable)", g_api_url.c_str());
|
|
if (!g_db_path.empty())
|
|
ImGui::Text("DB: %s", g_db_path.c_str());
|
|
ImGui::Spacing();
|
|
ImGui::TextWrapped(
|
|
"Usage: registry_dashboard [--api URL] [db_path ...]\n"
|
|
" --api URL Connect to sqlite_api (default: http://127.0.0.1:8484)\n"
|
|
" db_path Direct SQLite path(s) as fallback");
|
|
ImGui::Spacing();
|
|
if (ImGui::Button("Retry")) {
|
|
reload_data();
|
|
}
|
|
fullscreen_window_end();
|
|
return;
|
|
}
|
|
|
|
draw_dashboard(g_data);
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
// Parse --api flag
|
|
std::vector<std::string> db_candidates;
|
|
bool api_explicit = false;
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
if (strcmp(argv[i], "--api") == 0 && i + 1 < argc) {
|
|
g_api_url = argv[++i];
|
|
api_explicit = true;
|
|
} else if (strncmp(argv[i], "--api=", 6) == 0) {
|
|
g_api_url = argv[i] + 6;
|
|
api_explicit = true;
|
|
} else {
|
|
db_candidates.push_back(argv[i]);
|
|
}
|
|
}
|
|
|
|
// Default: try localhost API if no --api given
|
|
if (!api_explicit) {
|
|
g_api_url = "http://127.0.0.1:8484";
|
|
}
|
|
|
|
// Resolve SQLite fallback path
|
|
for (auto& candidate : db_candidates) {
|
|
if (std::ifstream(candidate).good()) {
|
|
g_db_path = candidate;
|
|
fprintf(stdout, "SQLite fallback: %s\n", g_db_path.c_str());
|
|
break;
|
|
}
|
|
fprintf(stderr, "Not found: %s\n", candidate.c_str());
|
|
}
|
|
|
|
if (!db_candidates.empty()) {
|
|
if (g_db_path.empty()) g_db_path = db_candidates.back();
|
|
}
|
|
|
|
// Compartir el API URL con las vistas (para reindex/add desde la toolbar)
|
|
views_set_api_url(g_api_url);
|
|
|
|
// Info de la ventana About (submenu Settings → About...)
|
|
fn_ui::about_window_set_info(
|
|
"fn_registry Dashboard",
|
|
"0.3.0",
|
|
"Dashboard ImGui para visualizar el estado del fn_registry. "
|
|
"Consume datos via sqlite_api HTTP (fallback a SQLite directo). "
|
|
"KPIs con sparkline, charts con leyenda, tablas, altura responsive, "
|
|
"Status panel en Settings, multi-viewport, dashboard_panel en views."
|
|
);
|
|
|
|
// Seccion Status dentro de la ventana Settings (submenu Settings → Settings...).
|
|
// Muestra fuente activa de datos, URL del API y path SQLite fallback.
|
|
fn_ui::settings_window_add_section("status", "Status", []{
|
|
using namespace fn_tokens;
|
|
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_muted);
|
|
ImGui::TextUnformatted("Source:");
|
|
ImGui::PopStyleColor();
|
|
ImGui::SameLine();
|
|
if (g_loaded) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, colors::success);
|
|
ImGui::TextUnformatted(g_using_http ? "HTTP API (connected)" : "SQLite (direct)");
|
|
ImGui::PopStyleColor();
|
|
} else {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, colors::error);
|
|
ImGui::TextUnformatted("not connected");
|
|
ImGui::PopStyleColor();
|
|
}
|
|
|
|
if (!g_api_url.empty()) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_muted);
|
|
ImGui::TextUnformatted("API:");
|
|
ImGui::PopStyleColor();
|
|
ImGui::SameLine();
|
|
ImGui::TextUnformatted(g_api_url.c_str());
|
|
}
|
|
if (!g_db_path.empty()) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, colors::text_muted);
|
|
ImGui::TextUnformatted("DB:");
|
|
ImGui::PopStyleColor();
|
|
ImGui::SameLine();
|
|
ImGui::TextUnformatted(g_db_path.c_str());
|
|
}
|
|
ImGui::Spacing();
|
|
if (ImGui::Button("Reload")) {
|
|
ImGui::GetIO().UserData = reinterpret_cast<void*>(1);
|
|
}
|
|
});
|
|
|
|
reload_data();
|
|
|
|
return fn::run_app(
|
|
{.title = "fn_registry Dashboard",
|
|
.width = 1600,
|
|
.height = 1000,
|
|
.viewports = true,
|
|
.about = {"fn_registry Dashboard", "0.1.0",
|
|
"Dashboard del registry: funciones, tipos, apps, drift."},
|
|
.log = {"registry_dashboard.log", 1}},
|
|
render
|
|
);
|
|
}
|