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:
@@ -41,6 +41,7 @@ add_imgui_app(registry_dashboard
|
||||
${CMAKE_SOURCE_DIR}/functions/core/select.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/core/toast.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
|
||||
)
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#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"
|
||||
@@ -118,12 +120,51 @@ int main(int argc, char** argv) {
|
||||
// Info de la ventana About (submenu Settings → About...)
|
||||
fn_ui::about_window_set_info(
|
||||
"fn_registry Dashboard",
|
||||
"0.2.0",
|
||||
"0.3.0",
|
||||
"Dashboard ImGui para visualizar el estado del fn_registry. "
|
||||
"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();
|
||||
|
||||
return fn::run_app(
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
#include "viz/pie_chart.h"
|
||||
#include "viz/table_view.h"
|
||||
#include "viz/sparkline.h"
|
||||
#include "core/icons_tabler.h"
|
||||
#include "core/dashboard_panel.h"
|
||||
#include "core/dashboard_grid.h"
|
||||
#include "core/fps_overlay.h"
|
||||
#include "core/fullscreen_window.h"
|
||||
#include "core/tokens.h"
|
||||
#include "core/page_header.h"
|
||||
@@ -68,7 +68,8 @@ static void trigger_reload() {
|
||||
// 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
|
||||
? 100.0f * stats.tested_functions / stats.total_functions : 0.0f;
|
||||
float pure_pct = stats.total_functions > 0
|
||||
@@ -77,22 +78,30 @@ void draw_kpi_row(const RegistryStats& stats) {
|
||||
const ImGuiTableFlags flags = ImGuiTableFlags_SizingStretchSame
|
||||
| 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)) {
|
||||
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] = {
|
||||
{"Functions", static_cast<float>(stats.total_functions), "%.0f"},
|
||||
{"Types", static_cast<float>(stats.total_types), "%.0f"},
|
||||
{"Apps", static_cast<float>(stats.total_apps), "%.0f"},
|
||||
{"Analysis", static_cast<float>(stats.total_analysis), "%.0f"},
|
||||
{"Unit Tests", static_cast<float>(stats.total_unit_tests), "%.0f"},
|
||||
{"Proposals", static_cast<float>(stats.total_proposals), "%.0f"},
|
||||
{"Tested", tested_pct, "%.0f%%"},
|
||||
{"Pure", pure_pct, "%.0f%%"},
|
||||
{"Functions", static_cast<float>(stats.total_functions), "%.0f", TI_FUNCTION},
|
||||
{"Types", static_cast<float>(stats.total_types), "%.0f", TI_HEXAGON},
|
||||
{"Apps", static_cast<float>(stats.total_apps), "%.0f", TI_APPS},
|
||||
{"Analysis", static_cast<float>(stats.total_analysis), "%.0f", TI_FLASK},
|
||||
{"Unit Tests", static_cast<float>(stats.total_unit_tests), "%.0f", TI_TEST_PIPE},
|
||||
{"Proposals", static_cast<float>(stats.total_proposals), "%.0f", TI_BULB},
|
||||
{"Tested", tested_pct, "%.0f%%", TI_CIRCLE_CHECK},
|
||||
{"Pure", pure_pct, "%.0f%%", TI_HEART},
|
||||
};
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (i % 4 == 0) ImGui::TableNextRow();
|
||||
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();
|
||||
}
|
||||
@@ -587,8 +596,8 @@ static void draw_actions_bar() {
|
||||
|
||||
void draw_dashboard(RegistryData& data) {
|
||||
// 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");
|
||||
|
||||
char subtitle[128];
|
||||
@@ -604,10 +613,16 @@ void draw_dashboard(RegistryData& data) {
|
||||
draw_actions_bar();
|
||||
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));
|
||||
|
||||
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);
|
||||
ImGui::Dummy(ImVec2(0, fn_tokens::spacing::md));
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ void draw_dashboard(RegistryData& data);
|
||||
void views_set_api_url(const std::string& url);
|
||||
|
||||
// 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_recent_functions(const std::vector<FunctionRow>& funcs);
|
||||
void draw_apps_list(const std::vector<AppRow>& apps);
|
||||
|
||||
Reference in New Issue
Block a user