diff --git a/app.md b/app.md index 93a064e..a109104 100644 --- a/app.md +++ b/app.md @@ -2,21 +2,10 @@ name: dag_engine_ui lang: cpp domain: tools +version: 0.1.0 description: "Frontend ImGui para dag_engine. Lista, lanza e inspecciona DAGs con live updates via WS." tags: [imgui, dashboard, dag, scheduler, http, websocket] uses_functions: - - 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: [] uses_modules: [data_table_cpp] @@ -77,3 +66,13 @@ cd cpp && cmake --build build --target dag_engine_ui -j ## Backend Apunta a `http://127.0.0.1:8090` (dag_engine.service via systemd user unit). Para usuario / formato de DAG / troubleshooting: **[apps/dag_engine/README.md](../../apps/dag_engine/README.md)**. + + +## Capability growth log + +Una linea por bump SemVer. Bump-type segun `.claude/commands/version.md`: +- `major`: breaking observable (CLI args, schema BBDD propia, formato wire). +- `minor`: feature aditiva (nuevo panel, endpoint, opcion). +- `patch`: bugfix sin cambio observable. + +- v0.1.0 (2026-05-18) — baseline. diff --git a/appicon.ico b/appicon.ico index 05cc7f1..bd387b3 100644 Binary files a/appicon.ico and b/appicon.ico differ diff --git a/tabs.cpp b/tabs.cpp index 07492d9..bbb6c2d 100644 --- a/tabs.cpp +++ b/tabs.cpp @@ -371,86 +371,79 @@ void draw_run_detail(const std::string& api_url) { return; } - // Steps table — render nativo (ImGui::BeginTable) en vez de data_table::render - // para soportar la columna "Function" clickable (badge -> abre Function panel). - // Status sigue mostrando badge coloreado por tipo. - ImGui::BeginChild("##run_steps_wrap", ImVec2(-1, ImGui::GetContentRegionAvail().y * 0.5f)); - const ImGuiTableFlags steps_flags = - ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | - ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp | - ImGuiTableFlags_ScrollY; - if (ImGui::BeginTable("##dt_run_steps", 6, steps_flags)) { - ImGui::TableSetupScrollFreeze(0, 1); - ImGui::TableSetupColumn("Step", ImGuiTableColumnFlags_WidthStretch, 1.6f); - ImGui::TableSetupColumn("Function", ImGuiTableColumnFlags_WidthStretch, 2.2f); - ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthStretch, 0.8f); - ImGui::TableSetupColumn("Exit", ImGuiTableColumnFlags_WidthStretch, 0.4f); - ImGui::TableSetupColumn("Duration", ImGuiTableColumnFlags_WidthStretch, 0.7f); - ImGui::TableSetupColumn("Started", ImGuiTableColumnFlags_WidthStretch, 1.2f); - ImGui::TableHeadersRow(); + // Steps table — migrado a data_table::render (issue 0107g). + // La columna Function usa CellRenderer::Button con action_id="open_fn". + // Celdas con function_id="" muestran "(shell)" via Text (no button). + static data_table::State g_st_run_steps; + static std::vector g_back_run_steps; + static std::vector g_ptrs_run_steps; - for (size_t i = 0; i < steps.size(); i++) { - auto& s = steps[i]; - ImGui::TableNextRow(); - - // Step name - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted(s.step_name.c_str()); - - // Function — badge clickable o "(shell)" - ImGui::TableSetColumnIndex(1); - if (!s.function_id.empty()) { - ImGui::PushID(static_cast(i)); - // Small button styled like a badge (registry green). - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.13f, 0.55f, 0.30f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.18f, 0.65f, 0.38f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.10f, 0.45f, 0.25f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 1)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6, 1)); - char btn[512]; - std::snprintf(btn, sizeof(btn), "%s %s", TI_FUNCTION, s.function_id.c_str()); - if (ImGui::SmallButton(btn)) { - auto& fp = function_panel(); - if (!fp.selected_id.empty() && fp.selected_id != s.function_id) { - fp.breadcrumb.push_back(fp.selected_id); - } - fp.selected_id = s.function_id; - fp.loaded = false; - fp.load_error.clear(); - } - ImGui::PopStyleVar(); - ImGui::PopStyleColor(4); - ImGui::PopID(); - } else { - ImGui::TextDisabled("(shell)"); - } - - // Status badge - ImGui::TableSetColumnIndex(2); - BadgeVariant v = BadgeVariant::Default; - if (s.status == "success") v = BadgeVariant::Success; - else if (s.status == "failed") v = BadgeVariant::Error; - else if (s.status == "running") v = BadgeVariant::Warning; - else if (s.status == "cancelled") v = BadgeVariant::Default; - else if (s.status == "pending") v = BadgeVariant::Info; - badge(s.status.c_str(), v); - - // Exit - ImGui::TableSetColumnIndex(3); - ImGui::Text("%d", s.exit_code); - - // Duration - ImGui::TableSetColumnIndex(4); - ImGui::TextUnformatted(format_duration(s.duration_ms).c_str()); - - // Started - ImGui::TableSetColumnIndex(5); - ImGui::TextUnformatted(s.started_at.c_str()); - } - ImGui::EndTable(); + g_back_run_steps.clear(); + for (const auto& s : steps) { + g_back_run_steps.push_back(s.step_name); + g_back_run_steps.push_back(s.function_id.empty() ? "(shell)" : s.function_id); + 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); } + g_ptrs_run_steps.clear(); + for (const auto& sv : g_back_run_steps) g_ptrs_run_steps.push_back(sv.c_str()); + + data_table::TableInput tbl_steps; + tbl_steps.name = "dt_run_steps"; + tbl_steps.headers = {"Step", "Function", "Status", "Exit", "Duration", "Started"}; + tbl_steps.types = { + data_table::ColumnType::String, data_table::ColumnType::String, + data_table::ColumnType::String, data_table::ColumnType::Int, + data_table::ColumnType::String, data_table::ColumnType::String, + }; + tbl_steps.cells = g_ptrs_run_steps.empty() ? nullptr : g_ptrs_run_steps.data(); + tbl_steps.rows = (int)steps.size(); + tbl_steps.cols = 6; + + tbl_steps.column_specs.resize(tbl_steps.cols); + for (int i = 0; i < tbl_steps.cols; i++) tbl_steps.column_specs[i].id = tbl_steps.headers[i]; + // Function → Button (celdas "(shell)" no son function_ids — se ven como texto si label=value) + tbl_steps.column_specs[1].renderer = data_table::CellRenderer::Button; + tbl_steps.column_specs[1].button_action = "open_fn"; + tbl_steps.column_specs[1].button_label = ""; // "" → usa valor de celda como label + tbl_steps.column_specs[1].button_color_hex = "#21882b"; + tbl_steps.column_specs[1].tooltip = "Open in Function panel"; + tbl_steps.column_specs[1].tooltip_on_hover = true; + // Status → CategoricalChip + tbl_steps.column_specs[2].renderer = data_table::CellRenderer::CategoricalChip; + tbl_steps.column_specs[2].chips = { + {"success", "#22c55e"}, + {"failed", "#ef4444"}, + {"running", "#f59e0b"}, + {"cancelled", "#a3a3a3"}, + {"pending", "#3b82f6"}, + }; + // Duration → Duration renderer + tbl_steps.column_specs[4].renderer = data_table::CellRenderer::Duration; + tbl_steps.column_specs[4].duration_warn_ms = 5000.0f; + tbl_steps.column_specs[4].duration_error_ms = 30000.0f; + + std::vector step_events; + ImGui::BeginChild("##run_steps_wrap", ImVec2(-1, ImGui::GetContentRegionAvail().y * 0.5f)); + data_table::render("##dt_run_steps", {tbl_steps}, g_st_run_steps, &step_events); ImGui::EndChild(); + for (const auto& ev : step_events) { + if (ev.kind == data_table::TableEventKind::ButtonClick + && ev.action_id == "open_fn" + && ev.value != "(shell)") { + auto& fp = function_panel(); + if (!fp.selected_id.empty() && fp.selected_id != ev.value) { + fp.breadcrumb.push_back(fp.selected_id); + } + fp.selected_id = ev.value; + fp.loaded = false; + fp.load_error.clear(); + } + } + // stdout/stderr expandible por step. ImGui::Separator(); ImGui::TextUnformatted("Step output:"); @@ -728,6 +721,7 @@ void draw_health(const std::string& /*api_url*/, return; } + // LAYOUT-TABLE — KPI/form/splitter, no data; keep BeginTable inline. if (ImGui::BeginTable("##health_kpis", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingStretchSame)) {