Initial commit: registry_dashboard — Dashboard ImGui para fn_registry
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
operations.db
|
||||
operations.db-wal
|
||||
operations.db-shm
|
||||
*.exe
|
||||
@@ -0,0 +1,36 @@
|
||||
# SQLite3: use system library on Linux, vendored amalgamation on Windows cross-compile
|
||||
find_package(SQLite3 QUIET)
|
||||
if(NOT SQLite3_FOUND)
|
||||
# Build from amalgamation
|
||||
set(SQLITE3_AMALG_DIR ${CMAKE_SOURCE_DIR}/vendor/sqlite3)
|
||||
add_library(sqlite3_vendored STATIC ${SQLITE3_AMALG_DIR}/sqlite3.c)
|
||||
target_include_directories(sqlite3_vendored PUBLIC ${SQLITE3_AMALG_DIR})
|
||||
target_compile_definitions(sqlite3_vendored PRIVATE
|
||||
SQLITE_THREADSAFE=1
|
||||
SQLITE_ENABLE_FTS5
|
||||
SQLITE_ENABLE_JSON1
|
||||
)
|
||||
# Alias so we can use the same target name
|
||||
add_library(SQLite::SQLite3 ALIAS sqlite3_vendored)
|
||||
endif()
|
||||
|
||||
add_imgui_app(registry_dashboard
|
||||
main.cpp
|
||||
data.cpp
|
||||
views.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/kpi_card.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/bar_chart.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/pie_chart.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/table_view.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/viz/sparkline.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/core/dashboard_panel.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/core/dashboard_grid.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/core/fps_overlay.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/core/fullscreen_window.cpp
|
||||
)
|
||||
|
||||
target_include_directories(registry_dashboard PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(registry_dashboard PRIVATE SQLite::SQLite3)
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
name: registry_dashboard
|
||||
lang: cpp
|
||||
domain: tui
|
||||
description: "Dashboard ImGui para visualizar el estado del fn_registry: KPIs, charts, tablas, desglose por lenguaje/dominio/pureza"
|
||||
tags: [dashboard, imgui, visualization, registry]
|
||||
uses_functions:
|
||||
- kpi_card_cpp_viz
|
||||
- bar_chart_cpp_viz
|
||||
- pie_chart_cpp_viz
|
||||
- table_view_cpp_viz
|
||||
- sparkline_cpp_viz
|
||||
- dashboard_panel_cpp_core
|
||||
- dashboard_grid_cpp_core
|
||||
- fps_overlay_cpp_core
|
||||
- fullscreen_window_cpp_core
|
||||
uses_types: []
|
||||
framework: "imgui"
|
||||
entry_point: "main.cpp"
|
||||
dir_path: "apps/registry_dashboard"
|
||||
repo_url: ""
|
||||
---
|
||||
|
||||
## Arquitectura
|
||||
|
||||
Dashboard C++ que lee `registry.db` directamente via SQLite C API y visualiza el estado completo del fn_registry.
|
||||
|
||||
**Data layer** (`data.cpp`): Carga todas las stats, desgloses y listas en un solo paso desde SQLite. Soporte para reload en caliente.
|
||||
|
||||
**Views** (`views.cpp`): Compone funciones del registry C++ para renderizar:
|
||||
- 6 KPI cards: total functions, types, apps, analysis, tested%, pure%
|
||||
- Bar charts: funciones por lenguaje, por dominio, actividad ultimos 30 dias
|
||||
- Pie charts: pureza (pure/impure), kind (function/pipeline/component), tested/untested
|
||||
- Tablas: ultimas 20 funciones, apps, analysis, tipos
|
||||
|
||||
**Entrada**: Paths a `registry.db` como argumentos con fallback encadenado.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
cd cpp && cmake -B build/linux -S . && cmake --build build/linux --target registry_dashboard -j$(nproc)
|
||||
|
||||
# Windows (cross-compile)
|
||||
cd cpp && cmake -B build/windows -S . -DCMAKE_TOOLCHAIN_FILE=toolchains/mingw-w64.cmake && cmake --build build/windows --target registry_dashboard -j$(nproc)
|
||||
```
|
||||
|
||||
## Ejecucion
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
./cpp/build/linux/apps/registry_dashboard/registry_dashboard /home/lucas/fn_registry/registry.db
|
||||
|
||||
# Windows (PowerShell) — intenta WSL mount, luego local
|
||||
.\registry_dashboard.ps1
|
||||
|
||||
# Otra maquina — copiar .exe + .db al mismo dir
|
||||
registry_dashboard.exe .\registry.db
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [ ] Integrar Go runtime (CGo) para ejecutar comandos `fn` desde el GUI
|
||||
- [ ] Filtros interactivos por lenguaje/dominio en sidebar
|
||||
- [ ] Busqueda FTS5 integrada
|
||||
- [ ] Detalles de funcion al hacer click en tabla
|
||||
|
||||
## Notas
|
||||
|
||||
SQLite compilado estaticamente en Windows via amalgamation vendoreada. En Linux usa libsqlite3 del sistema.
|
||||
@@ -0,0 +1,188 @@
|
||||
#include "data.h"
|
||||
#include <sqlite3.h>
|
||||
#include <cstdio>
|
||||
|
||||
// Helper: execute a query and call row_fn for each row
|
||||
template<typename F>
|
||||
static bool query(sqlite3* db, const char* sql, F row_fn) {
|
||||
sqlite3_stmt* stmt = nullptr;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
|
||||
fprintf(stderr, "SQL error: %s\n query: %s\n", sqlite3_errmsg(db), sql);
|
||||
return false;
|
||||
}
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
row_fn(stmt);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string col_str(sqlite3_stmt* s, int i) {
|
||||
const char* t = reinterpret_cast<const char*>(sqlite3_column_text(s, i));
|
||||
return t ? t : "";
|
||||
}
|
||||
|
||||
static int col_int(sqlite3_stmt* s, int i) {
|
||||
return sqlite3_column_int(s, i);
|
||||
}
|
||||
|
||||
void RegistryData::prepare_chart_data() {
|
||||
lang_labels.clear(); lang_values.clear();
|
||||
for (auto& lc : by_lang) {
|
||||
lang_labels.push_back(lc.lang);
|
||||
lang_values.push_back(static_cast<float>(lc.count));
|
||||
}
|
||||
|
||||
domain_labels.clear(); domain_values.clear();
|
||||
for (auto& dc : by_domain) {
|
||||
domain_labels.push_back(dc.domain);
|
||||
domain_values.push_back(static_cast<float>(dc.count));
|
||||
}
|
||||
|
||||
kind_labels.clear(); kind_values.clear();
|
||||
for (auto& kc : by_kind) {
|
||||
kind_labels.push_back(kc.kind);
|
||||
kind_values.push_back(static_cast<float>(kc.count));
|
||||
}
|
||||
|
||||
date_labels.clear(); date_values.clear();
|
||||
for (auto& dc : by_date) {
|
||||
date_labels.push_back(dc.date);
|
||||
date_values.push_back(static_cast<float>(dc.count));
|
||||
}
|
||||
}
|
||||
|
||||
bool load_registry_data(const char* db_path, RegistryData& out) {
|
||||
sqlite3* db = nullptr;
|
||||
if (sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READONLY, nullptr) != SQLITE_OK) {
|
||||
fprintf(stderr, "Cannot open %s: %s\n", db_path, sqlite3_errmsg(db));
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- Counts ---
|
||||
query(db, "SELECT COUNT(*) FROM functions", [&](sqlite3_stmt* s) {
|
||||
out.stats.total_functions = col_int(s, 0);
|
||||
});
|
||||
query(db, "SELECT COUNT(*) FROM types", [&](sqlite3_stmt* s) {
|
||||
out.stats.total_types = col_int(s, 0);
|
||||
});
|
||||
query(db, "SELECT COUNT(*) FROM apps", [&](sqlite3_stmt* s) {
|
||||
out.stats.total_apps = col_int(s, 0);
|
||||
});
|
||||
query(db, "SELECT COUNT(*) FROM analysis", [&](sqlite3_stmt* s) {
|
||||
out.stats.total_analysis = col_int(s, 0);
|
||||
});
|
||||
query(db, "SELECT COUNT(*) FROM unit_tests", [&](sqlite3_stmt* s) {
|
||||
out.stats.total_unit_tests = col_int(s, 0);
|
||||
});
|
||||
query(db, "SELECT COUNT(*) FROM proposals", [&](sqlite3_stmt* s) {
|
||||
out.stats.total_proposals = col_int(s, 0);
|
||||
});
|
||||
query(db, "SELECT COUNT(*) FROM functions WHERE tested = 1", [&](sqlite3_stmt* s) {
|
||||
out.stats.tested_functions = col_int(s, 0);
|
||||
});
|
||||
query(db, "SELECT COUNT(*) FROM functions WHERE purity = 'pure'", [&](sqlite3_stmt* s) {
|
||||
out.stats.pure_functions = col_int(s, 0);
|
||||
});
|
||||
query(db, "SELECT COUNT(*) FROM functions WHERE purity = 'impure'", [&](sqlite3_stmt* s) {
|
||||
out.stats.impure_functions = col_int(s, 0);
|
||||
});
|
||||
|
||||
// --- By language ---
|
||||
out.by_lang.clear();
|
||||
query(db, "SELECT lang, COUNT(*) as cnt FROM functions GROUP BY lang ORDER BY cnt DESC",
|
||||
[&](sqlite3_stmt* s) {
|
||||
out.by_lang.push_back({col_str(s, 0), col_int(s, 1)});
|
||||
});
|
||||
|
||||
// --- By domain ---
|
||||
out.by_domain.clear();
|
||||
query(db, "SELECT domain, COUNT(*) as cnt FROM functions GROUP BY domain ORDER BY cnt DESC",
|
||||
[&](sqlite3_stmt* s) {
|
||||
out.by_domain.push_back({col_str(s, 0), col_int(s, 1)});
|
||||
});
|
||||
|
||||
// --- By kind ---
|
||||
out.by_kind.clear();
|
||||
query(db, "SELECT kind, COUNT(*) as cnt FROM functions GROUP BY kind ORDER BY cnt DESC",
|
||||
[&](sqlite3_stmt* s) {
|
||||
out.by_kind.push_back({col_str(s, 0), col_int(s, 1)});
|
||||
});
|
||||
|
||||
// --- By date (last 30 days) ---
|
||||
out.by_date.clear();
|
||||
query(db,
|
||||
"SELECT date(created_at) as d, COUNT(*) as cnt FROM functions "
|
||||
"WHERE created_at >= date('now', '-30 days') "
|
||||
"GROUP BY d ORDER BY d",
|
||||
[&](sqlite3_stmt* s) {
|
||||
out.by_date.push_back({col_str(s, 0), col_int(s, 1)});
|
||||
});
|
||||
|
||||
// --- Recent functions (last 20) ---
|
||||
out.recent_funcs.clear();
|
||||
query(db,
|
||||
"SELECT id, name, lang, domain, kind, purity, description, created_at, tested "
|
||||
"FROM functions ORDER BY created_at DESC LIMIT 20",
|
||||
[&](sqlite3_stmt* s) {
|
||||
FunctionRow r;
|
||||
r.id = col_str(s, 0);
|
||||
r.name = col_str(s, 1);
|
||||
r.lang = col_str(s, 2);
|
||||
r.domain = col_str(s, 3);
|
||||
r.kind = col_str(s, 4);
|
||||
r.purity = col_str(s, 5);
|
||||
r.description = col_str(s, 6);
|
||||
r.created_at = col_str(s, 7);
|
||||
r.tested = col_int(s, 8) != 0;
|
||||
out.recent_funcs.push_back(std::move(r));
|
||||
});
|
||||
|
||||
// --- Apps ---
|
||||
out.apps.clear();
|
||||
query(db,
|
||||
"SELECT id, name, lang, domain, description, framework FROM apps ORDER BY name",
|
||||
[&](sqlite3_stmt* s) {
|
||||
AppRow r;
|
||||
r.id = col_str(s, 0);
|
||||
r.name = col_str(s, 1);
|
||||
r.lang = col_str(s, 2);
|
||||
r.domain = col_str(s, 3);
|
||||
r.description = col_str(s, 4);
|
||||
r.framework = col_str(s, 5);
|
||||
out.apps.push_back(std::move(r));
|
||||
});
|
||||
|
||||
// --- Analysis ---
|
||||
out.analyses.clear();
|
||||
query(db,
|
||||
"SELECT id, name, domain, description FROM analysis ORDER BY name",
|
||||
[&](sqlite3_stmt* s) {
|
||||
AnalysisRow r;
|
||||
r.id = col_str(s, 0);
|
||||
r.name = col_str(s, 1);
|
||||
r.domain = col_str(s, 2);
|
||||
r.description = col_str(s, 3);
|
||||
out.analyses.push_back(std::move(r));
|
||||
});
|
||||
|
||||
// --- Types ---
|
||||
out.types.clear();
|
||||
query(db,
|
||||
"SELECT id, name, lang, domain, algebraic, description FROM types ORDER BY name",
|
||||
[&](sqlite3_stmt* s) {
|
||||
TypeRow r;
|
||||
r.id = col_str(s, 0);
|
||||
r.name = col_str(s, 1);
|
||||
r.lang = col_str(s, 2);
|
||||
r.domain = col_str(s, 3);
|
||||
r.algebraic = col_str(s, 4);
|
||||
r.description = col_str(s, 5);
|
||||
out.types.push_back(std::move(r));
|
||||
});
|
||||
|
||||
sqlite3_close(db);
|
||||
|
||||
out.prepare_chart_data();
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
struct RegistryStats {
|
||||
int total_functions = 0;
|
||||
int total_types = 0;
|
||||
int total_apps = 0;
|
||||
int total_analysis = 0;
|
||||
int total_unit_tests = 0;
|
||||
int total_proposals = 0;
|
||||
int tested_functions = 0;
|
||||
int pure_functions = 0;
|
||||
int impure_functions = 0;
|
||||
};
|
||||
|
||||
struct LangCount {
|
||||
std::string lang;
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
struct DomainCount {
|
||||
std::string domain;
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
struct KindCount {
|
||||
std::string kind;
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
struct DateCount {
|
||||
std::string date; // YYYY-MM-DD
|
||||
int count = 0;
|
||||
};
|
||||
|
||||
struct FunctionRow {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string lang;
|
||||
std::string domain;
|
||||
std::string kind;
|
||||
std::string purity;
|
||||
std::string description;
|
||||
std::string created_at;
|
||||
bool tested = false;
|
||||
};
|
||||
|
||||
struct AppRow {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string lang;
|
||||
std::string domain;
|
||||
std::string description;
|
||||
std::string framework;
|
||||
};
|
||||
|
||||
struct AnalysisRow {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string domain;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
struct TypeRow {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string lang;
|
||||
std::string domain;
|
||||
std::string algebraic;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
// All data loaded from registry.db in one shot
|
||||
struct RegistryData {
|
||||
RegistryStats stats;
|
||||
std::vector<LangCount> by_lang;
|
||||
std::vector<DomainCount> by_domain;
|
||||
std::vector<KindCount> by_kind;
|
||||
std::vector<DateCount> by_date; // last 30 days
|
||||
std::vector<FunctionRow> recent_funcs; // last 20
|
||||
std::vector<AppRow> apps;
|
||||
std::vector<AnalysisRow> analyses;
|
||||
std::vector<TypeRow> types;
|
||||
|
||||
// For chart data (populated by prepare_chart_data)
|
||||
std::vector<std::string> lang_labels;
|
||||
std::vector<float> lang_values;
|
||||
std::vector<std::string> domain_labels;
|
||||
std::vector<float> domain_values;
|
||||
std::vector<std::string> kind_labels;
|
||||
std::vector<float> kind_values;
|
||||
std::vector<std::string> date_labels;
|
||||
std::vector<float> date_values;
|
||||
|
||||
void prepare_chart_data();
|
||||
};
|
||||
|
||||
// Load all data from registry.db. Returns true on success.
|
||||
bool load_registry_data(const char* db_path, RegistryData& out);
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "app_base.h"
|
||||
#include "imgui.h"
|
||||
#include "core/fullscreen_window.h"
|
||||
#include "data.h"
|
||||
#include "views.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
|
||||
static RegistryData g_data;
|
||||
static std::string g_db_path;
|
||||
static bool g_loaded = false;
|
||||
|
||||
static void reload_data() {
|
||||
g_data = RegistryData{};
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
static void render() {
|
||||
if (ImGui::GetIO().UserData != nullptr) {
|
||||
ImGui::GetIO().UserData = nullptr;
|
||||
reload_data();
|
||||
}
|
||||
|
||||
if (!g_loaded) {
|
||||
fullscreen_window_begin("##error");
|
||||
ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1),
|
||||
"Could not open registry.db");
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("Tried: %s", g_db_path.c_str());
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("Usage: registry_dashboard <path1> [path2] [path3] ...");
|
||||
ImGui::Spacing();
|
||||
if (ImGui::Button("Retry")) {
|
||||
reload_data();
|
||||
}
|
||||
fullscreen_window_end();
|
||||
return;
|
||||
}
|
||||
|
||||
draw_dashboard(g_data);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: registry_dashboard <db_path> [fallback_path ...]\n");
|
||||
fprintf(stderr, " Tries each path in order until one opens successfully.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Try each argument in order
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::string candidate = argv[i];
|
||||
if (std::ifstream(candidate).good()) {
|
||||
g_db_path = candidate;
|
||||
fprintf(stdout, "Using: %s\n", g_db_path.c_str());
|
||||
break;
|
||||
}
|
||||
fprintf(stderr, "Not found: %s\n", candidate);
|
||||
}
|
||||
|
||||
if (g_db_path.empty()) {
|
||||
// None found, use last arg so error screen shows something useful
|
||||
g_db_path = argv[argc - 1];
|
||||
}
|
||||
|
||||
reload_data();
|
||||
|
||||
return fn::run_app(
|
||||
{.title = "fn_registry Dashboard", .width = 1600, .height = 1000, .viewports = true},
|
||||
render
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
$exe = Join-Path $PSScriptRoot "registry_dashboard.exe"
|
||||
|
||||
& $exe `
|
||||
"\\wsl.localhost\Ubuntu\home\lucas\fn_registry\registry.db" `
|
||||
"\\wsl$\Ubuntu\home\lucas\fn_registry\registry.db" `
|
||||
"$PSScriptRoot\registry.db"
|
||||
@@ -0,0 +1,224 @@
|
||||
#include "views.h"
|
||||
#include "imgui.h"
|
||||
#include "implot.h"
|
||||
|
||||
#include "viz/kpi_card.h"
|
||||
#include "viz/bar_chart.h"
|
||||
#include "viz/pie_chart.h"
|
||||
#include "viz/table_view.h"
|
||||
#include "viz/sparkline.h"
|
||||
#include "core/dashboard_panel.h"
|
||||
#include "core/dashboard_grid.h"
|
||||
#include "core/fps_overlay.h"
|
||||
#include "core/fullscreen_window.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
static std::vector<const char*> to_cstr(const std::vector<std::string>& v) {
|
||||
std::vector<const char*> out;
|
||||
out.reserve(v.size());
|
||||
for (auto& s : v) out.push_back(s.c_str());
|
||||
return out;
|
||||
}
|
||||
|
||||
void draw_kpi_row(const RegistryStats& 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
|
||||
? 100.0f * stats.pure_functions / stats.total_functions
|
||||
: 0.0f;
|
||||
|
||||
dashboard_grid_begin(6, 12.0f);
|
||||
|
||||
kpi_card("Functions", static_cast<float>(stats.total_functions), 0, nullptr, 0, "%.0f");
|
||||
dashboard_grid_next();
|
||||
kpi_card("Types", static_cast<float>(stats.total_types), 0, nullptr, 0, "%.0f");
|
||||
dashboard_grid_next();
|
||||
kpi_card("Apps", static_cast<float>(stats.total_apps), 0, nullptr, 0, "%.0f");
|
||||
dashboard_grid_next();
|
||||
kpi_card("Analysis", static_cast<float>(stats.total_analysis), 0, nullptr, 0, "%.0f");
|
||||
dashboard_grid_next();
|
||||
kpi_card("Tested %", tested_pct, 0, nullptr, 0, "%.1f%%");
|
||||
dashboard_grid_next();
|
||||
kpi_card("Pure %", pure_pct, 0, nullptr, 0, "%.1f%%");
|
||||
|
||||
dashboard_grid_end();
|
||||
}
|
||||
|
||||
void draw_charts(RegistryData& data) {
|
||||
dashboard_grid_begin(2, 12.0f);
|
||||
|
||||
if (dashboard_panel_begin("Functions by Language", 300, 280)) {
|
||||
auto labels = to_cstr(data.lang_labels);
|
||||
if (!labels.empty())
|
||||
bar_chart("##lang", labels.data(), data.lang_values.data(), static_cast<int>(labels.size()));
|
||||
}
|
||||
dashboard_panel_end();
|
||||
dashboard_grid_next();
|
||||
|
||||
if (dashboard_panel_begin("Functions by Domain", 300, 280)) {
|
||||
auto labels = to_cstr(data.domain_labels);
|
||||
if (!labels.empty())
|
||||
bar_chart("##domain", labels.data(), data.domain_values.data(), static_cast<int>(labels.size()));
|
||||
}
|
||||
dashboard_panel_end();
|
||||
dashboard_grid_next();
|
||||
|
||||
if (dashboard_panel_begin("Purity", 250, 280)) {
|
||||
const char* labels[] = {"Pure", "Impure"};
|
||||
float values[] = {static_cast<float>(data.stats.pure_functions), static_cast<float>(data.stats.impure_functions)};
|
||||
pie_chart("##purity", labels, values, 2);
|
||||
}
|
||||
dashboard_panel_end();
|
||||
dashboard_grid_next();
|
||||
|
||||
if (dashboard_panel_begin("Kind", 250, 280)) {
|
||||
auto labels = to_cstr(data.kind_labels);
|
||||
if (!labels.empty())
|
||||
pie_chart("##kind", labels.data(), data.kind_values.data(), static_cast<int>(labels.size()));
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
void draw_recent_functions(const std::vector<FunctionRow>& funcs) {
|
||||
const char* headers[] = {"Name", "Lang", "Domain", "Kind", "Purity", "Tested", "Created"};
|
||||
constexpr int cols = 7;
|
||||
std::vector<std::string> cell_strings;
|
||||
cell_strings.reserve(funcs.size() * cols);
|
||||
for (auto& f : funcs) {
|
||||
cell_strings.push_back(f.name);
|
||||
cell_strings.push_back(f.lang);
|
||||
cell_strings.push_back(f.domain);
|
||||
cell_strings.push_back(f.kind);
|
||||
cell_strings.push_back(f.purity);
|
||||
cell_strings.push_back(f.tested ? "yes" : "no");
|
||||
cell_strings.push_back(f.created_at.substr(0, 10));
|
||||
}
|
||||
auto cells = to_cstr(cell_strings);
|
||||
table_view("##recent", headers, cols, cells.data(), static_cast<int>(funcs.size()));
|
||||
}
|
||||
|
||||
void draw_apps_list(const std::vector<AppRow>& apps) {
|
||||
const char* headers[] = {"Name", "Lang", "Domain", "Framework", "Description"};
|
||||
constexpr int cols = 5;
|
||||
std::vector<std::string> cell_strings;
|
||||
cell_strings.reserve(apps.size() * cols);
|
||||
for (auto& a : apps) {
|
||||
cell_strings.push_back(a.name);
|
||||
cell_strings.push_back(a.lang);
|
||||
cell_strings.push_back(a.domain);
|
||||
cell_strings.push_back(a.framework);
|
||||
cell_strings.push_back(a.description);
|
||||
}
|
||||
auto cells = to_cstr(cell_strings);
|
||||
table_view("##apps", headers, cols, cells.data(), static_cast<int>(apps.size()));
|
||||
}
|
||||
|
||||
void draw_analysis_list(const std::vector<AnalysisRow>& analyses) {
|
||||
const char* headers[] = {"Name", "Domain", "Description"};
|
||||
constexpr int cols = 3;
|
||||
std::vector<std::string> cell_strings;
|
||||
cell_strings.reserve(analyses.size() * cols);
|
||||
for (auto& a : analyses) {
|
||||
cell_strings.push_back(a.name);
|
||||
cell_strings.push_back(a.domain);
|
||||
cell_strings.push_back(a.description);
|
||||
}
|
||||
auto cells = to_cstr(cell_strings);
|
||||
table_view("##analysis", headers, cols, cells.data(), static_cast<int>(analyses.size()));
|
||||
}
|
||||
|
||||
void draw_types_list(const std::vector<TypeRow>& types) {
|
||||
const char* headers[] = {"Name", "Lang", "Domain", "Algebraic", "Description"};
|
||||
constexpr int cols = 5;
|
||||
std::vector<std::string> cell_strings;
|
||||
cell_strings.reserve(types.size() * cols);
|
||||
for (auto& t : types) {
|
||||
cell_strings.push_back(t.name);
|
||||
cell_strings.push_back(t.lang);
|
||||
cell_strings.push_back(t.domain);
|
||||
cell_strings.push_back(t.algebraic);
|
||||
cell_strings.push_back(t.description);
|
||||
}
|
||||
auto cells = to_cstr(cell_strings);
|
||||
table_view("##types", headers, cols, cells.data(), static_cast<int>(types.size()));
|
||||
}
|
||||
|
||||
void draw_dashboard(RegistryData& data) {
|
||||
fps_overlay();
|
||||
|
||||
fullscreen_window_begin("##dashboard");
|
||||
|
||||
// Header
|
||||
ImGui::Text("fn_registry Dashboard");
|
||||
ImGui::SameLine(ImGui::GetWindowWidth() - 100);
|
||||
if (ImGui::Button("Reload")) {
|
||||
ImGui::GetIO().UserData = reinterpret_cast<void*>(1);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// KPIs
|
||||
draw_kpi_row(data.stats);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Charts
|
||||
draw_charts(data);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Tables in tabs
|
||||
if (ImGui::BeginTabBar("##tables")) {
|
||||
if (ImGui::BeginTabItem("Recent Functions")) {
|
||||
draw_recent_functions(data.recent_funcs);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Apps")) {
|
||||
draw_apps_list(data.apps);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Analysis")) {
|
||||
draw_analysis_list(data.analyses);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Types")) {
|
||||
draw_types_list(data.types);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
fullscreen_window_end();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "data.h"
|
||||
|
||||
// Draw the full dashboard. Call every frame.
|
||||
void draw_dashboard(RegistryData& data);
|
||||
|
||||
// Individual views (called by draw_dashboard)
|
||||
void draw_kpi_row(const RegistryStats& stats);
|
||||
void draw_charts(RegistryData& data);
|
||||
void draw_recent_functions(const std::vector<FunctionRow>& funcs);
|
||||
void draw_apps_list(const std::vector<AppRow>& apps);
|
||||
void draw_analysis_list(const std::vector<AnalysisRow>& analyses);
|
||||
void draw_types_list(const std::vector<TypeRow>& types);
|
||||
Reference in New Issue
Block a user