diff --git a/app.md b/app.md index 605affd..418c07a 100644 --- a/app.md +++ b/app.md @@ -2,21 +2,10 @@ name: navegator_dashboard lang: cpp domain: tools +version: 0.1.0 description: "Cuadro de mandos para gestionar instancias Chrome con remote debugging. Lista navegadores corriendo (visibles + headless), permite lanzar/matar perfiles, inspeccionar pestañas, ejecutar JS, ver peticiones de red. Puente WSL→Windows que centraliza el control que hoy hacemos por scripts dispersos." tags: [imgui, browser, cdp, dashboard, windows, navegator, auto-extract, recipes, picker] uses_functions: - - data_table_cpp_viz - - viz_render_cpp_viz - - compute_stage_cpp_core - - compute_pipeline_cpp_core - - tql_emit_cpp_core - - tql_apply_cpp_core - - lua_engine_cpp_core - - join_tables_cpp_core - - auto_detect_type_cpp_core - - compute_column_stats_cpp_core - - llm_anthropic_cpp_core - - tql_to_sql_cpp_core - claude_cli_prompt_py_infra - cdp_get_ax_tree_py_pipelines - llm_propose_scraping_schema_py_infra @@ -132,3 +121,13 @@ Casos de uso: - v1: CDP HTTP/WS in-process + Tabs + Tab Detail + Network panel. - v2: HTTP API local + integracion con `cdp-cli` (cdp-cli puede delegar al dashboard si esta vivo). - v3: streaming live de pestañas (CDP `Page.startScreencast`) — arquitectura ya prevista en issue 0038. + + +## 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 c0ca60c..e3cb7fb 100644 Binary files a/appicon.ico and b/appicon.ico differ diff --git a/autoextract_panel.cpp b/autoextract_panel.cpp index 6276f4b..02c7a75 100644 --- a/autoextract_panel.cpp +++ b/autoextract_panel.cpp @@ -525,6 +525,7 @@ void render_autoextract_panel(bool* p_open) { sc_copy = g_ax.schema; } + // LAYOUT-TABLE — schema editor form con InputText/Checkbox editables inline; keep BeginTable inline. if (ImGui::BeginTable("##ax_schema", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { ImGui::TableSetupColumn("field"); ImGui::TableSetupColumn("selector"); diff --git a/recipes_panel.cpp b/recipes_panel.cpp index 0ed863b..cbc7765 100644 --- a/recipes_panel.cpp +++ b/recipes_panel.cpp @@ -10,6 +10,8 @@ #include "imgui.h" #include "core/icons_tabler.h" #include "core/tokens.h" +#include "data_table/data_table.h" +#include "core/data_table_types.h" #include "py_subprocess.h" #include "session_state.h" @@ -235,52 +237,94 @@ void render_recipes_panel(bool* p_open) { if (rows_copy.empty()) { ImGui::TextDisabled("No recipes in projects/navegator/profiles/default/recipes/."); ImGui::TextDisabled("Use AutoExtract panel to create one."); - } else if (ImGui::BeginTable("##recipes_tbl", 6, - ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { - ImGui::TableSetupColumn("name"); - ImGui::TableSetupColumn("url_pattern"); - ImGui::TableSetupColumn("last_status"); - ImGui::TableSetupColumn("last_at"); - ImGui::TableSetupColumn("rows"); - ImGui::TableSetupColumn("actions"); - ImGui::TableHeadersRow(); + } else { + // Tabla de recetas — migrado a data_table::render (issue 0107g, Patron B). + // Las acciones Run/Edit/Delete/Open se mapean a columnas Button con action_id. + // ev.row indexa rows_copy directamente para recuperar yaml_path. + static data_table::State g_st_recipes; + static std::vector g_back_recipes; + static std::vector g_ptrs_recipes; - for (size_t i = 0; i < rows_copy.size(); ++i) { - const RecipeRow& r = rows_copy[i]; - ImGui::TableNextRow(); - ImGui::PushID((int)i); - ImGui::TableNextColumn(); - ImGui::TextUnformatted(r.name.c_str()); - ImGui::TableNextColumn(); - ImGui::TextWrapped("%s", r.url_pattern.c_str()); - ImGui::TableNextColumn(); - ImGui::TextUnformatted(r.last_run_status.empty() ? "-" : r.last_run_status.c_str()); - ImGui::TableNextColumn(); - ImGui::TextUnformatted(r.last_run_at.empty() ? "-" : r.last_run_at.c_str()); - ImGui::TableNextColumn(); - ImGui::Text("%d", r.rows_last_run); - ImGui::TableNextColumn(); - if (ImGui::SmallButton("Run")) run_recipe_async(r.yaml_path); - ImGui::SameLine(); - if (ImGui::SmallButton("Edit")) { + g_back_recipes.clear(); + for (const auto& r : rows_copy) { + g_back_recipes.push_back(r.name); + g_back_recipes.push_back(r.url_pattern); + g_back_recipes.push_back(r.last_run_status.empty() ? "-" : r.last_run_status); + g_back_recipes.push_back(r.last_run_at.empty() ? "-" : r.last_run_at); + g_back_recipes.push_back(std::to_string(r.rows_last_run)); + g_back_recipes.push_back("Run"); // col 5: accion run + g_back_recipes.push_back("Edit"); // col 6: accion edit + g_back_recipes.push_back("Delete"); // col 7: accion delete + g_back_recipes.push_back("Open"); // col 8: accion open_df + } + g_ptrs_recipes.clear(); + for (const auto& s : g_back_recipes) g_ptrs_recipes.push_back(s.c_str()); + + data_table::TableInput tbl; + tbl.name = "recipes_tbl"; + tbl.headers = {"name", "url_pattern", "last_status", "last_at", "rows", + "run", "edit", "delete", "open"}; + tbl.types = { + data_table::ColumnType::String, data_table::ColumnType::String, + data_table::ColumnType::String, data_table::ColumnType::String, + data_table::ColumnType::Int, + data_table::ColumnType::String, data_table::ColumnType::String, + data_table::ColumnType::String, data_table::ColumnType::String, + }; + tbl.cells = g_ptrs_recipes.data(); + tbl.rows = (int)rows_copy.size(); + tbl.cols = 9; + + tbl.column_specs.resize(tbl.cols); + for (int i = 0; i < tbl.cols; i++) tbl.column_specs[i].id = tbl.headers[i]; + // last_status → CategoricalChip + tbl.column_specs[2].renderer = data_table::CellRenderer::CategoricalChip; + tbl.column_specs[2].chips = { + {"ok", "#22c55e"}, {"success", "#22c55e"}, + {"error", "#ef4444"}, {"failed", "#ef4444"}, + {"-", "#a3a3a3"}, + }; + // Botones de accion + tbl.column_specs[5].renderer = data_table::CellRenderer::Button; + tbl.column_specs[5].button_action = "run_recipe"; + tbl.column_specs[5].button_label = "Run"; + tbl.column_specs[5].button_color_hex = "#3b82f6"; + tbl.column_specs[6].renderer = data_table::CellRenderer::Button; + tbl.column_specs[6].button_action = "edit_recipe"; + tbl.column_specs[6].button_label = "Edit"; + tbl.column_specs[7].renderer = data_table::CellRenderer::Button; + tbl.column_specs[7].button_action = "delete_recipe"; + tbl.column_specs[7].button_label = "Delete"; + tbl.column_specs[7].button_color_hex = "#ef4444"; + tbl.column_specs[8].renderer = data_table::CellRenderer::Button; + tbl.column_specs[8].button_action = "open_df"; + tbl.column_specs[8].button_label = "Open"; + + std::vector rec_events; + ImGui::BeginChild("##recipes_tbl_host", ImVec2(-1, 300)); + data_table::render("##recipes_dt", {tbl}, g_st_recipes, &rec_events); + ImGui::EndChild(); + + for (const auto& ev : rec_events) { + if (ev.kind != data_table::TableEventKind::ButtonClick) continue; + if (ev.row < 0 || ev.row >= (int)rows_copy.size()) continue; + const RecipeRow& r = rows_copy[ev.row]; + if (ev.action_id == "run_recipe") { + run_recipe_async(r.yaml_path); + } else if (ev.action_id == "edit_recipe") { std::string body = slurp(r.yaml_path); std::lock_guard lk(g_rs.mu); - g_rs.editing_idx = (int)i; + g_rs.editing_idx = ev.row; g_rs.edit_buf = body; std::snprintf(g_rs.edit_textarea, sizeof(g_rs.edit_textarea), "%s", body.c_str()); - } - ImGui::SameLine(); - if (ImGui::SmallButton("Delete")) delete_recipe(r.yaml_path); - ImGui::SameLine(); - if (ImGui::SmallButton("Open in data_factory")) { - // placeholder — solo loguea + } else if (ev.action_id == "delete_recipe") { + delete_recipe(r.yaml_path); + } else if (ev.action_id == "open_df") { std::lock_guard lk(g_rs.mu); g_rs.status = "open in data_factory: " + r.name + " (not wired)"; } - ImGui::PopID(); } - ImGui::EndTable(); } if (editing_idx >= 0 && editing_idx < (int)rows_copy.size()) {