feat: 5 puntitos de status en DAG List (R1..R5 columnas Badge)
- data_http: parsea last_runs[] del /api/dags y guarda d.last_runs_status (max 5, mas reciente primero). - tabs.cpp DAG List: 5 columnas R1..R5 con CellRenderer::Badge + BadgeRule por status (success=verde, failed=rojo, running=amarillo, pending/cancelled=gris, "-"=tenue). - main.cpp: g_refresh_pending. WS auto-trigger refresh /api/dags cuando ve un run con status terminal -> last_runs se actualiza sin pulsar nada. - main + tabs: extern "C" dag_list_request_refresh() para el boton Refresh manual. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -94,6 +94,11 @@ static void parse_dag_info(const json& j, DagInfo& d) {
|
||||
d.last_run_started_at = lr.started_at;
|
||||
d.last_run_finished_at = lr.finished_at;
|
||||
}
|
||||
if (j.contains("last_runs") && j["last_runs"].is_array()) {
|
||||
for (auto& r : j["last_runs"]) {
|
||||
d.last_runs_status.push_back(get_str(r, "status"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool list_dags_http(const std::string& api_url, std::vector<DagInfo>& out) {
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace dag_ui {
|
||||
|
||||
// --- Modelo de datos (mirror JSON shape de apps/dag_engine) ---
|
||||
|
||||
struct DagRunRow; // fwd
|
||||
|
||||
struct DagInfo {
|
||||
std::string name;
|
||||
std::string description;
|
||||
@@ -24,6 +26,9 @@ struct DagInfo {
|
||||
std::string last_run_status;
|
||||
std::string last_run_started_at;
|
||||
std::string last_run_finished_at;
|
||||
// last_runs[] (max 5, most recent first). Empty if no runs yet.
|
||||
// Populated from /api/dags `last_runs` array.
|
||||
std::vector<std::string> last_runs_status; // parallel: status of each (size <= 5)
|
||||
};
|
||||
|
||||
struct DagRunRow {
|
||||
|
||||
@@ -38,6 +38,12 @@ static bool g_show_run_detail = true;
|
||||
|
||||
// Auto-fetch DAG list una vez al arrancar.
|
||||
static bool g_initial_fetched = false;
|
||||
// Flag set by tabs::draw_dag_list cuando el usuario pulsa Refresh, o cuando
|
||||
// WS notifica que un run termino (status != running) — re-fetch /api/dags
|
||||
// para actualizar last_runs.
|
||||
static bool g_refresh_pending = false;
|
||||
|
||||
extern "C" void dag_list_request_refresh() { g_refresh_pending = true; }
|
||||
|
||||
// Upsert por id en g_live_runs.
|
||||
static void upsert_live_run(const dag_ui::DagRunRow& r) {
|
||||
@@ -69,6 +75,12 @@ static void parse_ws_payload(const std::string& payload) {
|
||||
r.finished_at = rj.value("finished_at", "");
|
||||
r.error = rj.value("error", "");
|
||||
upsert_live_run(r);
|
||||
// Cuando un run termina, refresca DAG List para que last_runs
|
||||
// refleje la nueva ejecucion en R1..R5.
|
||||
if (r.status == "success" || r.status == "failed" ||
|
||||
r.status == "cancelled") {
|
||||
g_refresh_pending = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,9 +134,10 @@ static void draw_live() {
|
||||
}
|
||||
|
||||
static void render() {
|
||||
// Auto-fetch DAGs on first frame.
|
||||
if (!g_initial_fetched) {
|
||||
// Auto-fetch DAGs on first frame or on explicit refresh.
|
||||
if (!g_initial_fetched || g_refresh_pending) {
|
||||
g_initial_fetched = true;
|
||||
g_refresh_pending = false;
|
||||
dag_ui::list_dags_http(g_api_url, g_dags);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,9 @@ static std::string status_for_dag(const std::string& dag_name,
|
||||
// DAG List
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Forward decl — main.cpp owns the cache and the refresh trigger.
|
||||
extern "C" void dag_list_request_refresh();
|
||||
|
||||
void draw_dag_list(const std::string& api_url,
|
||||
const std::vector<dag_ui::DagInfo>& dags,
|
||||
const std::vector<dag_ui::DagRunRow>& live_runs)
|
||||
@@ -84,6 +87,12 @@ void draw_dag_list(const std::string& api_url,
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::Button(TI_REFRESH " Refresh##dag_list")) {
|
||||
dag_list_request_refresh();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Double-click row -> inspect. R1..R5 = last 5 runs.");
|
||||
|
||||
if (dags.empty()) {
|
||||
empty_state("( no DAGs )", "Empty registry",
|
||||
"Place a YAML in apps/dag_engine/dags_migrated/ and reload the server.");
|
||||
@@ -91,27 +100,62 @@ void draw_dag_list(const std::string& api_url,
|
||||
return;
|
||||
}
|
||||
|
||||
// Build TableInput
|
||||
// Build TableInput. Columnas R1..R5 muestran status de las ultimas 5 runs
|
||||
// como badges coloreadas (verde/rojo/amarillo/gris) — issue 0095.
|
||||
data_table::TableInput ti;
|
||||
ti.name = "dags";
|
||||
ti.headers = {"Name", "Schedule", "Last Status", "Tags", "Valid", "File"};
|
||||
ti.headers = {"Name", "Schedule", "Last Status",
|
||||
"R1", "R2", "R3", "R4", "R5",
|
||||
"Tags", "Valid"};
|
||||
ti.types = {
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::String, // Name
|
||||
data_table::ColumnType::String, // Schedule
|
||||
data_table::ColumnType::String, // Last Status
|
||||
data_table::ColumnType::String, // R1
|
||||
data_table::ColumnType::String, // R2
|
||||
data_table::ColumnType::String, // R3
|
||||
data_table::ColumnType::String, // R4
|
||||
data_table::ColumnType::String, // R5
|
||||
data_table::ColumnType::String, // Tags
|
||||
data_table::ColumnType::String, // Valid
|
||||
};
|
||||
ti.rows = static_cast<int>(dags.size());
|
||||
ti.cols = static_cast<int>(ti.headers.size());
|
||||
|
||||
// BadgeRule por status: misma config para R1..R5.
|
||||
auto run_status_badges = [](){
|
||||
std::vector<data_table::BadgeRule> rules;
|
||||
rules.push_back({"success", "#22c55e", "●"}); // verde
|
||||
rules.push_back({"failed", "#ef4444", "●"}); // rojo
|
||||
rules.push_back({"running", "#eab308", "●"}); // amarillo
|
||||
rules.push_back({"pending", "#94a3b8", "●"}); // gris azulado
|
||||
rules.push_back({"cancelled", "#6b7280", "●"}); // gris
|
||||
rules.push_back({"-", "#1f2937", "·"}); // dot tenue cuando no hay run
|
||||
return rules;
|
||||
};
|
||||
|
||||
// ColumnSpec por columna. Solo R1..R5 (indices 3..7) son Badge.
|
||||
ti.column_specs.resize(ti.cols);
|
||||
for (int i = 0; i < ti.cols; i++) ti.column_specs[i].id = ti.headers[i];
|
||||
for (int i = 3; i <= 7; i++) {
|
||||
ti.column_specs[i].renderer = data_table::CellRenderer::Badge;
|
||||
ti.column_specs[i].badges = run_status_badges();
|
||||
}
|
||||
|
||||
g_back_dag_list.clear();
|
||||
g_back_dag_list.reserve(dags.size() * ti.cols);
|
||||
for (auto& d : dags) {
|
||||
g_back_dag_list.push_back(d.name);
|
||||
g_back_dag_list.push_back(d.schedule.empty() ? "-" : d.schedule[0]);
|
||||
g_back_dag_list.push_back(status_for_dag(d.name, d, live_runs));
|
||||
// R1..R5 — most recent first; "-" si menos de 5 runs.
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (i < static_cast<int>(d.last_runs_status.size())) {
|
||||
g_back_dag_list.push_back(d.last_runs_status[i]);
|
||||
} else {
|
||||
g_back_dag_list.push_back("-");
|
||||
}
|
||||
}
|
||||
std::string tags_csv;
|
||||
for (size_t i = 0; i < d.tags.size(); i++) {
|
||||
if (i) tags_csv += ",";
|
||||
@@ -119,7 +163,6 @@ void draw_dag_list(const std::string& api_url,
|
||||
}
|
||||
g_back_dag_list.push_back(tags_csv);
|
||||
g_back_dag_list.push_back(d.valid ? "yes" : "no");
|
||||
g_back_dag_list.push_back(d.file_path);
|
||||
}
|
||||
cells_to_ptrs(g_back_dag_list, g_ptrs_dag_list);
|
||||
ti.cells = g_ptrs_dag_list.data();
|
||||
|
||||
Reference in New Issue
Block a user