merge: quick/dashboard-improvements — Layout responsivo, KPIs extra, fix UNC paths Windows

This commit is contained in:
2026-04-13 01:31:46 +02:00
6 changed files with 84 additions and 58 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ uses_types: []
framework: "imgui" framework: "imgui"
entry_point: "main.cpp" entry_point: "main.cpp"
dir_path: "apps/registry_dashboard" dir_path: "apps/registry_dashboard"
repo_url: "" repo_url: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/dataforge/registry_dashboard"
--- ---
## Arquitectura ## Arquitectura
+53 -2
View File
@@ -1,6 +1,13 @@
#include "data.h" #include "data.h"
#include <sqlite3.h> #include <sqlite3.h>
#include <cstdio> #include <cstdio>
#include <cstring>
#include <string>
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#endif
// Helper: execute a query and call row_fn for each row // Helper: execute a query and call row_fn for each row
template<typename F> template<typename F>
@@ -52,13 +59,57 @@ void RegistryData::prepare_chart_data() {
} }
} }
// Copy file src to dst. Returns true on success.
static bool copy_file(const char* src, const char* dst) {
#ifdef _WIN32
return CopyFileA(src, dst, FALSE) != 0;
#else
FILE* in = fopen(src, "rb");
if (!in) return false;
FILE* out = fopen(dst, "wb");
if (!out) { fclose(in); return false; }
char buf[65536];
size_t n;
while ((n = fread(buf, 1, sizeof(buf), in)) > 0)
fwrite(buf, 1, n, out);
fclose(in);
fclose(out);
return true;
#endif
}
// On Windows, if the path is a network/UNC path (like \\wsl.localhost\...),
// copy the DB to a local temp file so SQLite locking works correctly.
// Returns the path to use (may be the original or a temp copy).
static std::string ensure_local_db(const char* db_path) {
#ifdef _WIN32
// Detect UNC / network paths
if (strncmp(db_path, "\\\\", 2) == 0 || strncmp(db_path, "//", 2) == 0) {
char temp_dir[MAX_PATH] = {0};
GetTempPathA(MAX_PATH, temp_dir);
std::string local_path = std::string(temp_dir) + "fn_registry_dashboard.db";
fprintf(stdout, "Copying DB to local temp: %s\n", local_path.c_str());
if (copy_file(db_path, local_path.c_str())) {
return local_path;
}
fprintf(stderr, "Warning: failed to copy DB locally, using original path\n");
}
#endif
return db_path;
}
bool load_registry_data(const char* db_path, RegistryData& out) { bool load_registry_data(const char* db_path, RegistryData& out) {
std::string effective_path = ensure_local_db(db_path);
sqlite3* db = nullptr; sqlite3* db = nullptr;
if (sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READONLY, nullptr) != SQLITE_OK) { if (sqlite3_open_v2(effective_path.c_str(), &db, SQLITE_OPEN_READONLY, nullptr) != SQLITE_OK) {
fprintf(stderr, "Cannot open %s: %s\n", db_path, sqlite3_errmsg(db)); fprintf(stderr, "Cannot open %s: %s\n", effective_path.c_str(), sqlite3_errmsg(db));
return false; return false;
} }
sqlite3_busy_timeout(db, 3000);
// --- Counts --- // --- Counts ---
query(db, "SELECT COUNT(*) FROM functions", [&](sqlite3_stmt* s) { query(db, "SELECT COUNT(*) FROM functions", [&](sqlite3_stmt* s) {
out.stats.total_functions = col_int(s, 0); out.stats.total_functions = col_int(s, 0);
+1 -1
View File
@@ -61,7 +61,7 @@ int main(int argc, char** argv) {
fprintf(stdout, "Using: %s\n", g_db_path.c_str()); fprintf(stdout, "Using: %s\n", g_db_path.c_str());
break; break;
} }
fprintf(stderr, "Not found: %s\n", candidate); fprintf(stderr, "Not found: %s\n", candidate.c_str());
} }
if (g_db_path.empty()) { if (g_db_path.empty()) {
+2 -3
View File
@@ -1,6 +1,5 @@
$exe = Join-Path $PSScriptRoot "registry_dashboard.exe" $exe = Join-Path $PSScriptRoot "registry_dashboard.exe"
& $exe ` & $exe `
"\\wsl.localhost\Ubuntu\home\lucas\fn_registry\registry.db" ` "\\wsl.localhost\Ubuntu-22.04\home\lucas\fn_registry\registry.db" `
"\\wsl$\Ubuntu\home\lucas\fn_registry\registry.db" `
"$PSScriptRoot\registry.db"
+26 -50
View File
@@ -23,13 +23,11 @@ static std::vector<const char*> to_cstr(const std::vector<std::string>& v) {
void draw_kpi_row(const RegistryStats& stats) { void draw_kpi_row(const RegistryStats& stats) {
float tested_pct = stats.total_functions > 0 float tested_pct = stats.total_functions > 0
? 100.0f * stats.tested_functions / stats.total_functions ? 100.0f * stats.tested_functions / stats.total_functions : 0.0f;
: 0.0f;
float pure_pct = stats.total_functions > 0 float pure_pct = stats.total_functions > 0
? 100.0f * stats.pure_functions / stats.total_functions ? 100.0f * stats.pure_functions / stats.total_functions : 0.0f;
: 0.0f;
dashboard_grid_begin(6, 12.0f); dashboard_grid_begin(8, 8.0f);
kpi_card("Functions", static_cast<float>(stats.total_functions), 0, nullptr, 0, "%.0f"); kpi_card("Functions", static_cast<float>(stats.total_functions), 0, nullptr, 0, "%.0f");
dashboard_grid_next(); dashboard_grid_next();
@@ -39,17 +37,21 @@ void draw_kpi_row(const RegistryStats& stats) {
dashboard_grid_next(); dashboard_grid_next();
kpi_card("Analysis", static_cast<float>(stats.total_analysis), 0, nullptr, 0, "%.0f"); kpi_card("Analysis", static_cast<float>(stats.total_analysis), 0, nullptr, 0, "%.0f");
dashboard_grid_next(); dashboard_grid_next();
kpi_card("Tested %", tested_pct, 0, nullptr, 0, "%.1f%%"); kpi_card("Unit Tests", static_cast<float>(stats.total_unit_tests), 0, nullptr, 0, "%.0f");
dashboard_grid_next(); dashboard_grid_next();
kpi_card("Pure %", pure_pct, 0, nullptr, 0, "%.1f%%"); kpi_card("Proposals", static_cast<float>(stats.total_proposals), 0, nullptr, 0, "%.0f");
dashboard_grid_next();
kpi_card("Tested", tested_pct, 0, nullptr, 0, "%.0f%%");
dashboard_grid_next();
kpi_card("Pure", pure_pct, 0, nullptr, 0, "%.0f%%");
dashboard_grid_end(); dashboard_grid_end();
} }
void draw_charts(RegistryData& data) { void draw_charts(RegistryData& data, float height) {
dashboard_grid_begin(2, 12.0f); dashboard_grid_begin(4, 8.0f);
if (dashboard_panel_begin("Functions by Language", 300, 280)) { if (dashboard_panel_begin("By Language", 0, height)) {
auto labels = to_cstr(data.lang_labels); auto labels = to_cstr(data.lang_labels);
if (!labels.empty()) if (!labels.empty())
bar_chart("##lang", labels.data(), data.lang_values.data(), static_cast<int>(labels.size())); bar_chart("##lang", labels.data(), data.lang_values.data(), static_cast<int>(labels.size()));
@@ -57,7 +59,7 @@ void draw_charts(RegistryData& data) {
dashboard_panel_end(); dashboard_panel_end();
dashboard_grid_next(); dashboard_grid_next();
if (dashboard_panel_begin("Functions by Domain", 300, 280)) { if (dashboard_panel_begin("By Domain", 0, height)) {
auto labels = to_cstr(data.domain_labels); auto labels = to_cstr(data.domain_labels);
if (!labels.empty()) if (!labels.empty())
bar_chart("##domain", labels.data(), data.domain_values.data(), static_cast<int>(labels.size())); bar_chart("##domain", labels.data(), data.domain_values.data(), static_cast<int>(labels.size()));
@@ -65,43 +67,21 @@ void draw_charts(RegistryData& data) {
dashboard_panel_end(); dashboard_panel_end();
dashboard_grid_next(); dashboard_grid_next();
if (dashboard_panel_begin("Purity", 250, 280)) { if (dashboard_panel_begin("Purity", 0, height)) {
const char* labels[] = {"Pure", "Impure"}; const char* labels[] = {"Pure", "Impure"};
float values[] = {static_cast<float>(data.stats.pure_functions), static_cast<float>(data.stats.impure_functions)}; float values[] = {static_cast<float>(data.stats.pure_functions),
static_cast<float>(data.stats.impure_functions)};
pie_chart("##purity", labels, values, 2); pie_chart("##purity", labels, values, 2);
} }
dashboard_panel_end(); dashboard_panel_end();
dashboard_grid_next(); dashboard_grid_next();
if (dashboard_panel_begin("Kind", 250, 280)) { if (dashboard_panel_begin("Kind", 0, height)) {
auto labels = to_cstr(data.kind_labels); auto labels = to_cstr(data.kind_labels);
if (!labels.empty()) if (!labels.empty())
pie_chart("##kind", labels.data(), data.kind_values.data(), static_cast<int>(labels.size())); pie_chart("##kind", labels.data(), data.kind_values.data(), static_cast<int>(labels.size()));
} }
dashboard_panel_end(); dashboard_panel_end();
dashboard_grid_next();
if (dashboard_panel_begin("Activity (last 30 days)", 600, 280)) {
auto labels = to_cstr(data.date_labels);
if (!labels.empty())
bar_chart("##dates", labels.data(), data.date_values.data(), static_cast<int>(labels.size()));
else
ImGui::TextDisabled("No functions created in the last 30 days");
}
dashboard_panel_end();
dashboard_grid_next();
if (dashboard_panel_begin("Extras", 250, 280)) {
kpi_card("Unit Tests", static_cast<float>(data.stats.total_unit_tests), 0, nullptr, 0, "%.0f");
ImGui::Spacing();
kpi_card("Proposals", static_cast<float>(data.stats.total_proposals), 0, nullptr, 0, "%.0f");
ImGui::Spacing();
const char* t_labels[] = {"Tested", "Untested"};
float t_values[] = {static_cast<float>(data.stats.tested_functions),
static_cast<float>(data.stats.total_functions - data.stats.tested_functions)};
pie_chart("##tested", t_labels, t_values, 2);
}
dashboard_panel_end();
dashboard_grid_end(); dashboard_grid_end();
} }
@@ -172,34 +152,30 @@ void draw_types_list(const std::vector<TypeRow>& types) {
void draw_dashboard(RegistryData& data) { void draw_dashboard(RegistryData& data) {
fps_overlay(); fps_overlay();
fullscreen_window_begin("##dashboard"); fullscreen_window_begin("##dashboard");
// Header float win_h = ImGui::GetContentRegionAvail().y;
// Row 1: Header + KPIs (~80px)
ImGui::Text("fn_registry Dashboard"); ImGui::Text("fn_registry Dashboard");
ImGui::SameLine(ImGui::GetWindowWidth() - 100); ImGui::SameLine(ImGui::GetWindowWidth() - 100);
if (ImGui::Button("Reload")) { if (ImGui::Button("Reload")) {
ImGui::GetIO().UserData = reinterpret_cast<void*>(1); ImGui::GetIO().UserData = reinterpret_cast<void*>(1);
} }
ImGui::Separator(); ImGui::Separator();
ImGui::Spacing();
// KPIs
draw_kpi_row(data.stats); draw_kpi_row(data.stats);
ImGui::Spacing();
ImGui::Separator(); ImGui::Separator();
ImGui::Spacing();
// Charts // Row 2: Charts — take 35% of remaining height
draw_charts(data); float remaining = ImGui::GetContentRegionAvail().y;
float chart_h = remaining * 0.35f;
if (chart_h < 150.0f) chart_h = 150.0f;
ImGui::Spacing(); draw_charts(data, chart_h);
ImGui::Separator(); ImGui::Separator();
ImGui::Spacing();
// Tables in tabs // Row 3: Tables — fill the rest
if (ImGui::BeginTabBar("##tables")) { if (ImGui::BeginTabBar("##tables")) {
if (ImGui::BeginTabItem("Recent Functions")) { if (ImGui::BeginTabItem("Recent Functions")) {
draw_recent_functions(data.recent_funcs); draw_recent_functions(data.recent_funcs);
+1 -1
View File
@@ -7,7 +7,7 @@ void draw_dashboard(RegistryData& data);
// Individual views (called by draw_dashboard) // Individual views (called by draw_dashboard)
void draw_kpi_row(const RegistryStats& stats); void draw_kpi_row(const RegistryStats& stats);
void draw_charts(RegistryData& data); 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);
void draw_analysis_list(const std::vector<AnalysisRow>& analyses); void draw_analysis_list(const std::vector<AnalysisRow>& analyses);