feat(dashboard): v0.3.0 — Status panel, dashboard_panel/grid, AppConfig pattern

- main.cpp: bump 0.2.0 → 0.3.0; añadir Status section en Settings via
  settings_window_add_section (fuente API/SQLite, URL, DB path, Reload).
- views.cpp: chart_panel manual + BeginChild custom → dashboard_panel/grid
  del registry. KPI cards con sparkline a la derecha, altura responsive.
- views.h: signature de draw_kpi_row pasa RegistryData (para sparkline).
- CMakeLists.txt: añadir process_state_machine.cpp (dependencia de
  process_runner tras issue 0045).
This commit is contained in:
2026-04-29 00:58:17 +02:00
parent a728e2b0a1
commit 00a19d8632
4 changed files with 75 additions and 18 deletions
+1
View File
@@ -41,6 +41,7 @@ add_imgui_app(registry_dashboard
${CMAKE_SOURCE_DIR}/functions/core/select.cpp ${CMAKE_SOURCE_DIR}/functions/core/select.cpp
${CMAKE_SOURCE_DIR}/functions/core/toast.cpp ${CMAKE_SOURCE_DIR}/functions/core/toast.cpp
${CMAKE_SOURCE_DIR}/functions/core/process_runner.cpp ${CMAKE_SOURCE_DIR}/functions/core/process_runner.cpp
${CMAKE_SOURCE_DIR}/functions/core/process_state_machine.cpp
${CMAKE_SOURCE_DIR}/functions/core/tree_view.cpp ${CMAKE_SOURCE_DIR}/functions/core/tree_view.cpp
) )
+43 -2
View File
@@ -3,6 +3,8 @@
#include "core/fullscreen_window.h" #include "core/fullscreen_window.h"
#include "core/app_menubar.h" #include "core/app_menubar.h"
#include "core/app_about.h" #include "core/app_about.h"
#include "core/app_settings.h"
#include "core/tokens.h"
#include "data.h" #include "data.h"
#include "data_http.h" #include "data_http.h"
#include "views.h" #include "views.h"
@@ -118,12 +120,51 @@ int main(int argc, char** argv) {
// Info de la ventana About (submenu Settings → About...) // Info de la ventana About (submenu Settings → About...)
fn_ui::about_window_set_info( fn_ui::about_window_set_info(
"fn_registry Dashboard", "fn_registry Dashboard",
"0.2.0", "0.3.0",
"Dashboard ImGui para visualizar el estado del fn_registry. " "Dashboard ImGui para visualizar el estado del fn_registry. "
"Consume datos via sqlite_api HTTP (fallback a SQLite directo). " "Consume datos via sqlite_api HTTP (fallback a SQLite directo). "
"KPIs, charts, tablas, desglose por lenguaje/dominio/pureza." "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(); reload_data();
return fn::run_app( return fn::run_app(
+30 -15
View File
@@ -9,9 +9,9 @@
#include "viz/pie_chart.h" #include "viz/pie_chart.h"
#include "viz/table_view.h" #include "viz/table_view.h"
#include "viz/sparkline.h" #include "viz/sparkline.h"
#include "core/icons_tabler.h"
#include "core/dashboard_panel.h" #include "core/dashboard_panel.h"
#include "core/dashboard_grid.h" #include "core/dashboard_grid.h"
#include "core/fps_overlay.h"
#include "core/fullscreen_window.h" #include "core/fullscreen_window.h"
#include "core/tokens.h" #include "core/tokens.h"
#include "core/page_header.h" #include "core/page_header.h"
@@ -68,7 +68,8 @@ static void trigger_reload() {
// KPI row // KPI row
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
void draw_kpi_row(const RegistryStats& stats) { void draw_kpi_row(const RegistryData& data) {
const RegistryStats& stats = data.stats;
float tested_pct = stats.total_functions > 0 float tested_pct = stats.total_functions > 0
? 100.0f * stats.tested_functions / stats.total_functions : 0.0f; ? 100.0f * stats.tested_functions / stats.total_functions : 0.0f;
float pure_pct = stats.total_functions > 0 float pure_pct = stats.total_functions > 0
@@ -77,22 +78,30 @@ void draw_kpi_row(const RegistryStats& stats) {
const ImGuiTableFlags flags = ImGuiTableFlags_SizingStretchSame const ImGuiTableFlags flags = ImGuiTableFlags_SizingStretchSame
| ImGuiTableFlags_NoPadOuterX; | ImGuiTableFlags_NoPadOuterX;
// Sparkline shared: ultimos 30 dias de creacion de funciones (timeline real
// del registry). Si no hay datos cargados, queda vacio y el card mostrara
// solo valor + delta placeholder.
const float* spark_data = data.date_values.empty() ? nullptr : data.date_values.data();
const int spark_count = static_cast<int>(data.date_values.size());
if (ImGui::BeginTable("##kpi_grid", 4, flags)) { if (ImGui::BeginTable("##kpi_grid", 4, flags)) {
struct KPI { const char* label; float value; const char* fmt; }; struct KPI { const char* label; float value; const char* fmt; const char* icon; };
const KPI cards[8] = { const KPI cards[8] = {
{"Functions", static_cast<float>(stats.total_functions), "%.0f"}, {"Functions", static_cast<float>(stats.total_functions), "%.0f", TI_FUNCTION},
{"Types", static_cast<float>(stats.total_types), "%.0f"}, {"Types", static_cast<float>(stats.total_types), "%.0f", TI_HEXAGON},
{"Apps", static_cast<float>(stats.total_apps), "%.0f"}, {"Apps", static_cast<float>(stats.total_apps), "%.0f", TI_APPS},
{"Analysis", static_cast<float>(stats.total_analysis), "%.0f"}, {"Analysis", static_cast<float>(stats.total_analysis), "%.0f", TI_FLASK},
{"Unit Tests", static_cast<float>(stats.total_unit_tests), "%.0f"}, {"Unit Tests", static_cast<float>(stats.total_unit_tests), "%.0f", TI_TEST_PIPE},
{"Proposals", static_cast<float>(stats.total_proposals), "%.0f"}, {"Proposals", static_cast<float>(stats.total_proposals), "%.0f", TI_BULB},
{"Tested", tested_pct, "%.0f%%"}, {"Tested", tested_pct, "%.0f%%", TI_CIRCLE_CHECK},
{"Pure", pure_pct, "%.0f%%"}, {"Pure", pure_pct, "%.0f%%", TI_HEART},
}; };
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
if (i % 4 == 0) ImGui::TableNextRow(); if (i % 4 == 0) ImGui::TableNextRow();
ImGui::TableSetColumnIndex(i % 4); ImGui::TableSetColumnIndex(i % 4);
kpi_card(cards[i].label, cards[i].value, 0.0f, nullptr, 0, cards[i].fmt); kpi_card(cards[i].label, cards[i].value, 0.0f,
spark_data, spark_count,
cards[i].fmt, cards[i].icon);
} }
ImGui::EndTable(); ImGui::EndTable();
} }
@@ -587,8 +596,8 @@ static void draw_actions_bar() {
void draw_dashboard(RegistryData& data) { void draw_dashboard(RegistryData& data) {
// Tema aplicado por fn::run_app() (app_base.h, ThemeMode::FnDark default). // Tema aplicado por fn::run_app() (app_base.h, ThemeMode::FnDark default).
// FPS overlay lo dibuja app_base.cpp segun settings().show_fps — no llamarlo aqui.
fps_overlay();
fullscreen_window_begin("##dashboard"); fullscreen_window_begin("##dashboard");
char subtitle[128]; char subtitle[128];
@@ -604,10 +613,16 @@ void draw_dashboard(RegistryData& data) {
draw_actions_bar(); draw_actions_bar();
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::sm)); ImGui::Dummy(ImVec2(0, fn_tokens::spacing::sm));
draw_kpi_row(data.stats); draw_kpi_row(data);
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md)); ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md));
constexpr float chart_h = 260.0f; // Altura del bloque de charts proporcional al espacio disponible: ~28% del
// espacio restante despues de KPIs/header/tabs (clamped a [200, 360] px
// para que ni se aplaste ni domine en pantallas grandes).
const float remaining_h = ImGui::GetContentRegionAvail().y;
float chart_h = remaining_h * 0.32f;
if (chart_h < 200.0f) chart_h = 200.0f;
if (chart_h > 360.0f) chart_h = 360.0f;
draw_charts(data, chart_h); draw_charts(data, chart_h);
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md)); ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md));
+1 -1
View File
@@ -11,7 +11,7 @@ void draw_dashboard(RegistryData& data);
void views_set_api_url(const std::string& url); void views_set_api_url(const std::string& url);
// Individual views (called by draw_dashboard) // Individual views (called by draw_dashboard)
void draw_kpi_row(const RegistryStats& stats); void draw_kpi_row(const RegistryData& data);
void draw_charts(RegistryData& data, float height = 250.0f); void draw_charts(RegistryData& data, float height = 250.0f);
void draw_recent_functions(const std::vector<FunctionRow>& funcs); void draw_recent_functions(const std::vector<FunctionRow>& funcs);
void draw_apps_list(const std::vector<AppRow>& apps); void draw_apps_list(const std::vector<AppRow>& apps);