feat: tabs DAG List / Detail / Run Detail via data_table_cpp_viz (issue 0095 step 5)
- tabs.{h,cpp}: 3 paneles que renderizan TableInput con data_table::render() + RowDoubleClick events para drill-down (DAG -> Detail -> Run Detail).
- main.cpp: arranca con auto-fetch DAGs y los 3 tabs visibles por defecto. Panel Main diagnostico apagado.
- CMakeLists.txt: linka empty_state.cpp del registry.
- app.md: uses_functions completo (data_table_cpp_viz + stack TQL + empty_state). Tags: [imgui, dashboard, dag, scheduler, http, websocket].
Funcionalidades:
- DAG List: tabla con Name/Schedule/Last Status/Tags/Valid/File. Status combina last_run (REST) + live_runs (WS). Double-click selecciona DAG.
- DAG Detail: header + Run Now (POST /api/dags/{name}/run) + tabla recent runs. Double-click run abre Run Detail.
- Run Detail: header del run + tabla steps (name/status/exit/duration/started) + CollapsingHeader por step con stdout/stderr.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,8 @@ add_imgui_app(dag_engine_ui
|
||||
http_client.cpp
|
||||
data_http.cpp
|
||||
ws_client.cpp
|
||||
tabs.cpp
|
||||
${CMAKE_SOURCE_DIR}/functions/core/empty_state.cpp
|
||||
)
|
||||
target_include_directories(dag_engine_ui PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
|
||||
@@ -3,21 +3,21 @@ name: dag_engine_ui
|
||||
lang: cpp
|
||||
domain: tools
|
||||
description: "Frontend ImGui para dag_engine. Lista, lanza e inspecciona DAGs con live updates via WS."
|
||||
tags: [imgui]
|
||||
tags: [imgui, dashboard, dag, scheduler, http, websocket]
|
||||
uses_functions:
|
||||
# Uncomment when using data_table::render() — provided via fn_table_viz:
|
||||
# - data_table_cpp_viz
|
||||
# - viz_render_cpp_viz
|
||||
# - compute_stage_cpp_core
|
||||
# - compute_pipeline_cpp_core
|
||||
# - compute_column_stats_cpp_core
|
||||
# - auto_detect_type_cpp_core
|
||||
# - tql_emit_cpp_core
|
||||
# - tql_apply_cpp_core
|
||||
# - lua_engine_cpp_core
|
||||
# - join_tables_cpp_core
|
||||
# - tql_to_sql_cpp_core
|
||||
# - llm_anthropic_cpp_core
|
||||
- data_table_cpp_viz
|
||||
- viz_render_cpp_viz
|
||||
- compute_stage_cpp_core
|
||||
- compute_pipeline_cpp_core
|
||||
- compute_column_stats_cpp_core
|
||||
- auto_detect_type_cpp_core
|
||||
- tql_emit_cpp_core
|
||||
- tql_apply_cpp_core
|
||||
- lua_engine_cpp_core
|
||||
- join_tables_cpp_core
|
||||
- tql_to_sql_cpp_core
|
||||
- llm_anthropic_cpp_core
|
||||
- empty_state_cpp_core
|
||||
uses_types: []
|
||||
framework: "imgui"
|
||||
entry_point: "main.cpp"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "core/logger.h"
|
||||
#include "data_http.h"
|
||||
#include "ws_client.h"
|
||||
#include "tabs.h"
|
||||
#include "vendor/nlohmann/json.hpp"
|
||||
|
||||
#include <string>
|
||||
@@ -29,8 +30,14 @@ static std::string g_last_error;
|
||||
static WsClient g_ws;
|
||||
|
||||
// Toggles de paneles (visibles desde el menu View del menubar canonico)
|
||||
static bool g_show_main = true;
|
||||
static bool g_show_live = true;
|
||||
static bool g_show_main = false; // diagnostico, off por defecto
|
||||
static bool g_show_live = true;
|
||||
static bool g_show_dag_list = true;
|
||||
static bool g_show_dag_detail = true;
|
||||
static bool g_show_run_detail = true;
|
||||
|
||||
// Auto-fetch DAG list una vez al arrancar.
|
||||
static bool g_initial_fetched = false;
|
||||
|
||||
// Upsert por id en g_live_runs.
|
||||
static void upsert_live_run(const dag_ui::DagRunRow& r) {
|
||||
@@ -115,6 +122,12 @@ static void draw_live() {
|
||||
}
|
||||
|
||||
static void render() {
|
||||
// Auto-fetch DAGs on first frame.
|
||||
if (!g_initial_fetched) {
|
||||
g_initial_fetched = true;
|
||||
dag_ui::list_dags_http(g_api_url, g_dags);
|
||||
}
|
||||
|
||||
// Drain WS messages this frame (cheap, max 64).
|
||||
{
|
||||
std::vector<std::string> msgs;
|
||||
@@ -122,8 +135,11 @@ static void render() {
|
||||
for (auto& m : msgs) parse_ws_payload(m);
|
||||
}
|
||||
|
||||
if (g_show_main) draw_main();
|
||||
if (g_show_live) draw_live();
|
||||
if (g_show_dag_list) dag_ui_tabs::draw_dag_list(g_api_url, g_dags, g_live_runs);
|
||||
if (g_show_dag_detail) dag_ui_tabs::draw_dag_detail(g_api_url);
|
||||
if (g_show_run_detail) dag_ui_tabs::draw_run_detail(g_api_url);
|
||||
if (g_show_main) draw_main();
|
||||
if (g_show_live) draw_live();
|
||||
}
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
@@ -131,8 +147,11 @@ int main(int /*argc*/, char** /*argv*/) {
|
||||
g_ws.start(g_ws_host, g_ws_port, g_ws_path);
|
||||
|
||||
static fn_ui::PanelToggle panels[] = {
|
||||
{ "Main", nullptr, &g_show_main },
|
||||
{ "Live (WS)", nullptr, &g_show_live },
|
||||
{ "DAGs", nullptr, &g_show_dag_list },
|
||||
{ "DAG Detail", nullptr, &g_show_dag_detail },
|
||||
{ "Run Detail", nullptr, &g_show_run_detail },
|
||||
{ "Live (WS)", nullptr, &g_show_live },
|
||||
{ "Main (diag)", nullptr, &g_show_main },
|
||||
};
|
||||
|
||||
fn::AppConfig cfg;
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
#include "tabs.h"
|
||||
#include "viz/data_table.h"
|
||||
#include "core/data_table_types.h"
|
||||
#include "core/icons_tabler.h"
|
||||
#include "core/empty_state.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
namespace dag_ui_tabs {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Globals
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Selection& selection() {
|
||||
static Selection s;
|
||||
return s;
|
||||
}
|
||||
|
||||
Caches& caches() {
|
||||
static Caches c;
|
||||
return c;
|
||||
}
|
||||
|
||||
// data_table::State persistente por panel (issue 0081-J pattern).
|
||||
static data_table::State g_st_dag_list;
|
||||
static data_table::State g_st_dag_runs;
|
||||
static data_table::State g_st_run_steps;
|
||||
|
||||
// Backing storage para cells de cada tabla. Owner del char* const* en TableInput.
|
||||
static std::vector<std::string> g_back_dag_list;
|
||||
static std::vector<const char*> g_ptrs_dag_list;
|
||||
|
||||
static std::vector<std::string> g_back_dag_runs;
|
||||
static std::vector<const char*> g_ptrs_dag_runs;
|
||||
|
||||
static std::vector<std::string> g_back_run_steps;
|
||||
static std::vector<const char*> g_ptrs_run_steps;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void cells_to_ptrs(const std::vector<std::string>& backing,
|
||||
std::vector<const char*>& ptrs) {
|
||||
ptrs.resize(backing.size());
|
||||
for (size_t i = 0; i < backing.size(); i++) ptrs[i] = backing[i].c_str();
|
||||
}
|
||||
|
||||
static std::string format_duration(long long ms) {
|
||||
if (ms <= 0) return "-";
|
||||
if (ms < 1000) return std::to_string(ms) + "ms";
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof(buf), "%.2fs", ms / 1000.0);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static std::string status_for_dag(const std::string& dag_name,
|
||||
const dag_ui::DagInfo& info,
|
||||
const std::vector<dag_ui::DagRunRow>& live_runs) {
|
||||
// Find most recent live run that matches this DAG.
|
||||
const dag_ui::DagRunRow* best = nullptr;
|
||||
for (auto& r : live_runs) {
|
||||
if (r.dag_name != dag_name) continue;
|
||||
if (!best || r.started_at > best->started_at) best = &r;
|
||||
}
|
||||
if (best) return best->status;
|
||||
if (info.has_last_run) return info.last_run_status;
|
||||
return "-";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DAG List
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void draw_dag_list(const std::string& api_url,
|
||||
const std::vector<dag_ui::DagInfo>& dags,
|
||||
const std::vector<dag_ui::DagRunRow>& live_runs)
|
||||
{
|
||||
if (!ImGui::Begin(TI_LIST " DAGs")) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
if (dags.empty()) {
|
||||
empty_state("( no DAGs )", "Empty registry",
|
||||
"Place a YAML in apps/dag_engine/dags_migrated/ and reload the server.");
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
// Build TableInput
|
||||
data_table::TableInput ti;
|
||||
ti.name = "dags";
|
||||
ti.headers = {"Name", "Schedule", "Last Status", "Tags", "Valid", "File"};
|
||||
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,
|
||||
};
|
||||
ti.rows = static_cast<int>(dags.size());
|
||||
ti.cols = static_cast<int>(ti.headers.size());
|
||||
|
||||
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));
|
||||
std::string tags_csv;
|
||||
for (size_t i = 0; i < d.tags.size(); i++) {
|
||||
if (i) tags_csv += ",";
|
||||
tags_csv += d.tags[i];
|
||||
}
|
||||
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();
|
||||
|
||||
std::vector<data_table::TableEvent> events;
|
||||
ImGui::BeginChild("##dag_list_wrap", ImVec2(-1, -1));
|
||||
data_table::render("##dt_dag_list", {ti}, g_st_dag_list, &events);
|
||||
ImGui::EndChild();
|
||||
|
||||
// Handle row events -> select DAG.
|
||||
for (auto& ev : events) {
|
||||
if (ev.kind == data_table::TableEventKind::RowDoubleClick && ev.row >= 0 &&
|
||||
ev.row < static_cast<int>(dags.size())) {
|
||||
selection().dag_name = dags[ev.row].name;
|
||||
caches().dag_detail_loaded = false; // force reload
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DAG Detail
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void load_dag_detail(const std::string& api_url) {
|
||||
auto& sel = selection();
|
||||
auto& c = caches();
|
||||
c.dag_detail = {};
|
||||
if (sel.dag_name.empty()) {
|
||||
c.dag_detail_loaded = false;
|
||||
return;
|
||||
}
|
||||
if (dag_ui::get_dag_http(api_url, sel.dag_name, c.dag_detail)) {
|
||||
c.dag_detail_loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_dag_detail(const std::string& api_url) {
|
||||
if (!ImGui::Begin(TI_INFO_CIRCLE " DAG Detail")) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
auto& sel = selection();
|
||||
auto& c = caches();
|
||||
|
||||
if (sel.dag_name.empty()) {
|
||||
empty_state("( nothing selected )", "Pick a DAG",
|
||||
"Double-click a row in the DAG list to inspect it here.");
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c.dag_detail_loaded) load_dag_detail(api_url);
|
||||
|
||||
auto& info = c.dag_detail.info;
|
||||
ImGui::Text("%s %s", TI_HASH, info.name.empty() ? sel.dag_name.c_str() : info.name.c_str());
|
||||
if (!info.description.empty()) ImGui::TextWrapped("%s", info.description.c_str());
|
||||
if (!info.schedule.empty()) ImGui::Text("Schedule: %s", info.schedule[0].c_str());
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::Button(TI_PLAYER_PLAY " Run Now")) {
|
||||
std::string run_id, err;
|
||||
if (dag_ui::trigger_dag_http(api_url, sel.dag_name, run_id, err)) {
|
||||
sel.run_id = run_id;
|
||||
c.run_detail_loaded = false;
|
||||
} else {
|
||||
// Surface error via console; UI banner could be added later.
|
||||
fprintf(stderr, "[dag_detail] trigger failed: %s\n", err.c_str());
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(TI_REFRESH " Refresh")) {
|
||||
c.dag_detail_loaded = false;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted("Recent runs:");
|
||||
|
||||
auto& runs = c.dag_detail.recent_runs;
|
||||
if (runs.empty()) {
|
||||
ImGui::TextDisabled("( no runs yet )");
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
data_table::TableInput ti;
|
||||
ti.name = "runs";
|
||||
ti.headers = {"Run ID", "Status", "Trigger", "Started", "Finished", "Error"};
|
||||
ti.types = {
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::Date,
|
||||
data_table::ColumnType::Date,
|
||||
data_table::ColumnType::String,
|
||||
};
|
||||
ti.rows = static_cast<int>(runs.size());
|
||||
ti.cols = static_cast<int>(ti.headers.size());
|
||||
|
||||
g_back_dag_runs.clear();
|
||||
g_back_dag_runs.reserve(runs.size() * ti.cols);
|
||||
for (auto& r : runs) {
|
||||
g_back_dag_runs.push_back(r.id);
|
||||
g_back_dag_runs.push_back(r.status);
|
||||
g_back_dag_runs.push_back(r.trigger);
|
||||
g_back_dag_runs.push_back(r.started_at);
|
||||
g_back_dag_runs.push_back(r.finished_at);
|
||||
g_back_dag_runs.push_back(r.error);
|
||||
}
|
||||
cells_to_ptrs(g_back_dag_runs, g_ptrs_dag_runs);
|
||||
ti.cells = g_ptrs_dag_runs.data();
|
||||
|
||||
std::vector<data_table::TableEvent> events;
|
||||
ImGui::BeginChild("##dag_runs_wrap", ImVec2(-1, -1));
|
||||
data_table::render("##dt_dag_runs", {ti}, g_st_dag_runs, &events);
|
||||
ImGui::EndChild();
|
||||
|
||||
for (auto& ev : events) {
|
||||
if (ev.kind == data_table::TableEventKind::RowDoubleClick && ev.row >= 0 &&
|
||||
ev.row < static_cast<int>(runs.size())) {
|
||||
sel.run_id = runs[ev.row].id;
|
||||
caches().run_detail_loaded = false;
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Run Detail
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void load_run_detail(const std::string& api_url) {
|
||||
auto& sel = selection();
|
||||
auto& c = caches();
|
||||
c.run_detail = {};
|
||||
if (sel.run_id.empty()) {
|
||||
c.run_detail_loaded = false;
|
||||
return;
|
||||
}
|
||||
if (dag_ui::get_run_http(api_url, sel.run_id, c.run_detail)) {
|
||||
c.run_detail_loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_run_detail(const std::string& api_url) {
|
||||
if (!ImGui::Begin(TI_CLIPBOARD_LIST " Run Detail")) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
auto& sel = selection();
|
||||
auto& c = caches();
|
||||
|
||||
if (sel.run_id.empty()) {
|
||||
empty_state("( nothing selected )", "Pick a run",
|
||||
"Double-click a row in DAG Detail recent runs.");
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c.run_detail_loaded) load_run_detail(api_url);
|
||||
|
||||
auto& run = c.run_detail.run;
|
||||
ImGui::Text("%s %s", TI_HASH, run.id.empty() ? sel.run_id.c_str() : run.id.c_str());
|
||||
ImGui::Text("Status: %s | Trigger: %s", run.status.c_str(), run.trigger.c_str());
|
||||
ImGui::Text("Started: %s | Finished: %s",
|
||||
run.started_at.c_str(), run.finished_at.empty() ? "-" : run.finished_at.c_str());
|
||||
if (!run.error.empty()) {
|
||||
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "Error: %s", run.error.c_str());
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(TI_REFRESH " Refresh##run")) {
|
||||
c.run_detail_loaded = false;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
auto& steps = c.run_detail.steps;
|
||||
if (steps.empty()) {
|
||||
ImGui::TextDisabled("( no steps yet )");
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
data_table::TableInput ti;
|
||||
ti.name = "steps";
|
||||
ti.headers = {"Step", "Status", "Exit", "Duration", "Started"};
|
||||
ti.types = {
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::Int,
|
||||
data_table::ColumnType::String,
|
||||
data_table::ColumnType::Date,
|
||||
};
|
||||
ti.rows = static_cast<int>(steps.size());
|
||||
ti.cols = static_cast<int>(ti.headers.size());
|
||||
|
||||
g_back_run_steps.clear();
|
||||
g_back_run_steps.reserve(steps.size() * ti.cols);
|
||||
for (auto& s : steps) {
|
||||
g_back_run_steps.push_back(s.step_name);
|
||||
g_back_run_steps.push_back(s.status);
|
||||
g_back_run_steps.push_back(std::to_string(s.exit_code));
|
||||
g_back_run_steps.push_back(format_duration(s.duration_ms));
|
||||
g_back_run_steps.push_back(s.started_at);
|
||||
}
|
||||
cells_to_ptrs(g_back_run_steps, g_ptrs_run_steps);
|
||||
ti.cells = g_ptrs_run_steps.data();
|
||||
|
||||
ImGui::BeginChild("##run_steps_wrap", ImVec2(-1, ImGui::GetContentRegionAvail().y * 0.5f));
|
||||
data_table::render("##dt_run_steps", {ti}, g_st_run_steps);
|
||||
ImGui::EndChild();
|
||||
|
||||
// stdout/stderr expandible por step.
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted("Step output:");
|
||||
for (size_t i = 0; i < steps.size(); i++) {
|
||||
char hdr[256];
|
||||
std::snprintf(hdr, sizeof(hdr), "%s##step_%zu", steps[i].step_name.c_str(), i);
|
||||
if (ImGui::CollapsingHeader(hdr)) {
|
||||
if (!steps[i].stdout_text.empty()) {
|
||||
ImGui::TextUnformatted("stdout:");
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.08f, 0.08f, 0.08f, 1));
|
||||
ImGui::BeginChild((std::string("##stdout_") + std::to_string(i)).c_str(),
|
||||
ImVec2(-1, 80), false);
|
||||
ImGui::TextUnformatted(steps[i].stdout_text.c_str());
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
if (!steps[i].stderr_text.empty()) {
|
||||
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "stderr:");
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.06f, 0.06f, 1));
|
||||
ImGui::BeginChild((std::string("##stderr_") + std::to_string(i)).c_str(),
|
||||
ImVec2(-1, 80), false);
|
||||
ImGui::TextUnformatted(steps[i].stderr_text.c_str());
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace dag_ui_tabs
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
// Tabs / panels para dag_engine_ui:
|
||||
// - DAG List (lista todos los DAGs, double-click -> selecciona)
|
||||
// - DAG Detail (header + Run Now + recent runs como data_table)
|
||||
// - Run Detail (steps con duracion/status + stdout/stderr expandible)
|
||||
//
|
||||
// Todos usan data_table_cpp_viz (issue 0081). State persistente por tab.
|
||||
|
||||
#include "data_http.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace dag_ui_tabs {
|
||||
|
||||
// Estado global cross-tab: que DAG/run esta seleccionado actualmente.
|
||||
struct Selection {
|
||||
std::string dag_name; // "" = nada seleccionado
|
||||
std::string run_id; // "" = nada seleccionado
|
||||
};
|
||||
|
||||
Selection& selection();
|
||||
|
||||
// Cache: detalle actual del DAG seleccionado + del run seleccionado.
|
||||
// Se rellena bajo demanda al cambiar la seleccion (o boton Refresh).
|
||||
struct Caches {
|
||||
dag_ui::DagDetail dag_detail;
|
||||
dag_ui::DagRunDetail run_detail;
|
||||
bool dag_detail_loaded = false;
|
||||
bool run_detail_loaded = false;
|
||||
};
|
||||
|
||||
Caches& caches();
|
||||
|
||||
// Render cada tab. api_url es el endpoint dag_engine.
|
||||
// `live_runs` es el cache global mantenido por WS (sirve para DAG List status).
|
||||
void draw_dag_list(const std::string& api_url,
|
||||
const std::vector<dag_ui::DagInfo>& dags,
|
||||
const std::vector<dag_ui::DagRunRow>& live_runs);
|
||||
|
||||
void draw_dag_detail(const std::string& api_url);
|
||||
|
||||
void draw_run_detail(const std::string& api_url);
|
||||
|
||||
} // namespace dag_ui_tabs
|
||||
Reference in New Issue
Block a user