migrate Jobs table to data_table::render with Progress/Duration/Badge renderers (issue 0081-J)
- Replace inline ImGui::BeginTable (6 cols) with data_table::render for 5 data columns: status (Badge), enricher (Text), target (Text), progress (Progress 0..1), time_ms (Duration warn=1000ms error=10000ms). - Add AppState::jobs_dt_state (data_table::State) for persistent filter/sort state. - Keep Cancel/Delete action buttons via separate small ImGui::BeginTable (option a from 0081-J spec); data_table::State does not expose selected_row_idx yet. TODO(Phase 2): migrate actions to selected-row toolbar when State exposes it. - Pre-filter cells by g_jobs_cache.filter_idx before passing to data_table so the combo filter and the declarative table filter are both respected. - Linux build: OK. Tests: 125/125 pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -252,6 +252,10 @@ struct AppState {
|
|||||||
// Persiste filters/sort/stages entre frames; uno por instancia de render.
|
// Persiste filters/sort/stages entre frames; uno por instancia de render.
|
||||||
data_table::State table_dt_state;
|
data_table::State table_dt_state;
|
||||||
|
|
||||||
|
// data_table::State para el panel Jobs (issue 0081-J Phase 1).
|
||||||
|
// Persiste filters/sort/stages entre frames para la tabla de jobs.
|
||||||
|
data_table::State jobs_dt_state;
|
||||||
|
|
||||||
// ---- Type Editor (issue 0007) ------------------------------------------
|
// ---- Type Editor (issue 0007) ------------------------------------------
|
||||||
// Draft del editor de tipos. Se inicializa con una copia de parsed_types
|
// Draft del editor de tipos. Se inicializa con una copia de parsed_types
|
||||||
// tras cargar el grafo. Save reescribe `types.yaml` y dispara
|
// tras cargar el grafo. Save reescribe `types.yaml` y dispara
|
||||||
|
|||||||
+275
-111
@@ -1,14 +1,33 @@
|
|||||||
|
// views_jobs.cpp — Jobs panel (issue 0026).
|
||||||
|
//
|
||||||
|
// Migration history (issue 0081-J):
|
||||||
|
// v1: ImGui::BeginTable inline with 6 columns (status, enricher, target,
|
||||||
|
// progress, time, ##actions).
|
||||||
|
// v2: data_table::render for 5 data columns (status, enricher, target,
|
||||||
|
// progress, time) with declarative CellRenderer (Badge, Progress,
|
||||||
|
// Duration). Action buttons (Cancel/Delete) rendered in a separate
|
||||||
|
// small ImGui::BeginTable below — option (a) from issue 0081-J spec.
|
||||||
|
// Rationale: data_table::State does not expose selected_row_idx yet
|
||||||
|
// (Phase 2 feature); a dedicated actions bar keeps all action logic
|
||||||
|
// isolated and works with any filter state in the main table.
|
||||||
|
// TODO(0081-J Phase 2): when data_table::State gets selected_row_idx,
|
||||||
|
// migrate actions to option (b) toolbar per selected row.
|
||||||
|
|
||||||
#include "views.h"
|
#include "views.h"
|
||||||
#include "jobs.h"
|
#include "jobs.h"
|
||||||
|
|
||||||
#include "core/icons_tabler.h"
|
#include "core/icons_tabler.h"
|
||||||
#include "core/tokens.h"
|
#include "core/tokens.h"
|
||||||
|
#include "core/data_table_types.h"
|
||||||
|
#include "viz/data_table.h"
|
||||||
|
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
|
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace ge {
|
namespace ge {
|
||||||
|
|
||||||
@@ -20,19 +39,9 @@ struct JobsCache {
|
|||||||
std::vector<JobRow> rows;
|
std::vector<JobRow> rows;
|
||||||
int last_frame_refresh = -100;
|
int last_frame_refresh = -100;
|
||||||
int filter_idx = 0; // 0=all 1=active 2=done 3=error
|
int filter_idx = 0; // 0=all 1=active 2=done 3=error
|
||||||
char buf[8] = {};
|
|
||||||
};
|
};
|
||||||
JobsCache g_jobs_cache;
|
JobsCache g_jobs_cache;
|
||||||
|
|
||||||
const char* status_icon(const std::string& s) {
|
|
||||||
if (s == "queued") return TI_HOURGLASS;
|
|
||||||
if (s == "running") return TI_PLAYER_PLAY;
|
|
||||||
if (s == "done") return TI_CHECK;
|
|
||||||
if (s == "error") return TI_ALERT_CIRCLE;
|
|
||||||
if (s == "cancelled") return TI_X;
|
|
||||||
return TI_QUESTION_MARK;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImVec4 status_color(const std::string& s) {
|
ImVec4 status_color(const std::string& s) {
|
||||||
if (s == "running") return ImVec4(0.36f, 0.78f, 1.0f, 1.0f);
|
if (s == "running") return ImVec4(0.36f, 0.78f, 1.0f, 1.0f);
|
||||||
if (s == "done") return ImVec4(0.40f, 0.85f, 0.55f, 1.0f);
|
if (s == "done") return ImVec4(0.40f, 0.85f, 0.55f, 1.0f);
|
||||||
@@ -42,31 +51,27 @@ ImVec4 status_color(const std::string& s) {
|
|||||||
return ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
|
return ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string format_duration(long long started, long long finished) {
|
|
||||||
if (started <= 0) return "—";
|
|
||||||
long long end = finished > 0 ? finished
|
|
||||||
: std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::system_clock::now().time_since_epoch())
|
|
||||||
.count();
|
|
||||||
long long ms = end - started;
|
|
||||||
if (ms < 0) ms = 0;
|
|
||||||
char b[32];
|
|
||||||
if (ms < 1000) std::snprintf(b, sizeof(b), "%lld ms", ms);
|
|
||||||
else if (ms < 60'000) std::snprintf(b, sizeof(b), "%.1f s", ms / 1000.0);
|
|
||||||
else std::snprintf(b, sizeof(b), "%.1f m", ms / 60'000.0);
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool filter_match(const std::string& status, int idx) {
|
bool filter_match(const std::string& status, int idx) {
|
||||||
switch (idx) {
|
switch (idx) {
|
||||||
case 0: return true; // all
|
case 0: return true;
|
||||||
case 1: return status == "queued" || status == "running"; // active
|
case 1: return status == "queued" || status == "running";
|
||||||
case 2: return status == "done";
|
case 2: return status == "done";
|
||||||
case 3: return status == "error" || status == "cancelled";
|
case 3: return status == "error" || status == "cancelled";
|
||||||
default: return true;
|
default: return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Duration helper: computes elapsed ms for running jobs (finished_at=0).
|
||||||
|
long long job_duration_ms(const JobRow& r) {
|
||||||
|
if (r.started_at <= 0) return 0;
|
||||||
|
long long end = r.finished_at > 0
|
||||||
|
? r.finished_at
|
||||||
|
: std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||||
|
long long ms = end - r.started_at;
|
||||||
|
return ms < 0 ? 0 : ms;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void views_jobs(AppState& app) {
|
void views_jobs(AppState& app) {
|
||||||
@@ -106,93 +111,252 @@ void views_jobs(AppState& app) {
|
|||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
// Tabla.
|
// -------------------------------------------------------------------------
|
||||||
ImGuiTableFlags tflags = ImGuiTableFlags_Borders |
|
// Actions mini-table (option a — Fase 1; TODO: migrate to selected-row
|
||||||
ImGuiTableFlags_RowBg |
|
// toolbar when data_table::State exposes selected_row_idx in Phase 2).
|
||||||
ImGuiTableFlags_SizingStretchProp |
|
//
|
||||||
ImGuiTableFlags_ScrollY;
|
// Shows Cancel/Delete buttons for every row that passes the current filter.
|
||||||
if (ImGui::BeginTable("jobs_table", 6, tflags,
|
// Fixed-height child so it doesn't push the data_table below the panel.
|
||||||
ImVec2(0, ImGui::GetContentRegionAvail().y - 4))) {
|
// We pre-compute the list of actionable rows (active or terminal).
|
||||||
ImGui::TableSetupScrollFreeze(0, 1);
|
// -------------------------------------------------------------------------
|
||||||
ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 90);
|
|
||||||
ImGui::TableSetupColumn("Enricher", ImGuiTableColumnFlags_WidthStretch, 1.5f);
|
|
||||||
ImGui::TableSetupColumn("Target", ImGuiTableColumnFlags_WidthStretch, 2.0f);
|
|
||||||
ImGui::TableSetupColumn("Progress", ImGuiTableColumnFlags_WidthStretch, 2.0f);
|
|
||||||
ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_WidthFixed, 70);
|
|
||||||
ImGui::TableSetupColumn("##actions",ImGuiTableColumnFlags_WidthFixed, 80);
|
|
||||||
ImGui::TableHeadersRow();
|
|
||||||
|
|
||||||
for (const auto& r : g_jobs_cache.rows) {
|
// Collect visible rows for actions.
|
||||||
if (!filter_match(r.status, g_jobs_cache.filter_idx)) continue;
|
std::vector<const JobRow*> visible;
|
||||||
ImGui::PushID(r.id.c_str());
|
visible.reserve(g_jobs_cache.rows.size());
|
||||||
ImGui::TableNextRow();
|
for (const auto& r : g_jobs_cache.rows) {
|
||||||
|
if (filter_match(r.status, g_jobs_cache.filter_idx))
|
||||||
// Status.
|
visible.push_back(&r);
|
||||||
ImGui::TableSetColumnIndex(0);
|
|
||||||
ImGui::TextColored(status_color(r.status), "%s %s",
|
|
||||||
status_icon(r.status), r.status.c_str());
|
|
||||||
|
|
||||||
// Enricher.
|
|
||||||
ImGui::TableSetColumnIndex(1);
|
|
||||||
ImGui::TextUnformatted(r.enricher_id.c_str());
|
|
||||||
|
|
||||||
// Target.
|
|
||||||
ImGui::TableSetColumnIndex(2);
|
|
||||||
if (!r.node_name.empty()) {
|
|
||||||
ImGui::TextUnformatted(r.node_name.c_str());
|
|
||||||
} else if (!r.node_id.empty()) {
|
|
||||||
ImGui::TextDisabled("%s", r.node_id.c_str());
|
|
||||||
} else {
|
|
||||||
ImGui::TextDisabled("(global)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Progress.
|
|
||||||
ImGui::TableSetColumnIndex(3);
|
|
||||||
if (r.status == "running" || r.status == "queued") {
|
|
||||||
ImGui::ProgressBar((float)r.progress, ImVec2(-FLT_MIN, 0),
|
|
||||||
r.stage.empty() ? nullptr : r.stage.c_str());
|
|
||||||
} else if (r.status == "error" && !r.error.empty()) {
|
|
||||||
ImGui::TextColored(status_color("error"), "%s",
|
|
||||||
r.error.size() > 64
|
|
||||||
? (r.error.substr(0, 64) + "…").c_str()
|
|
||||||
: r.error.c_str());
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("%s", r.error.c_str());
|
|
||||||
}
|
|
||||||
} else if (r.status == "done" && !r.result_json.empty()) {
|
|
||||||
ImGui::TextDisabled("%s",
|
|
||||||
r.result_json.size() > 80
|
|
||||||
? (r.result_json.substr(0, 80) + "…").c_str()
|
|
||||||
: r.result_json.c_str());
|
|
||||||
if (ImGui::IsItemHovered() && r.result_json.size() > 80) {
|
|
||||||
ImGui::SetTooltip("%s", r.result_json.c_str());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ImGui::TextDisabled("—");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time.
|
|
||||||
ImGui::TableSetColumnIndex(4);
|
|
||||||
ImGui::TextDisabled("%s",
|
|
||||||
format_duration(r.started_at, r.finished_at).c_str());
|
|
||||||
|
|
||||||
// Actions.
|
|
||||||
ImGui::TableSetColumnIndex(5);
|
|
||||||
if (r.status == "queued" || r.status == "running") {
|
|
||||||
if (ImGui::SmallButton("Cancel")) {
|
|
||||||
jobs_cancel(r.id.c_str());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ImGui::SmallButton("Delete")) {
|
|
||||||
jobs_delete(r.id.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actions bar height: up to 5 rows visible, ~22px each + header ~22px.
|
||||||
|
// Only render if there are any rows with actionable buttons.
|
||||||
|
bool any_actionable = false;
|
||||||
|
for (const auto* rp : visible) {
|
||||||
|
if (rp->status == "queued" || rp->status == "running" ||
|
||||||
|
rp->status == "done" || rp->status == "error" ||
|
||||||
|
rp->status == "cancelled") {
|
||||||
|
any_actionable = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (any_actionable && !visible.empty()) {
|
||||||
|
int visible_rows = (int)visible.size();
|
||||||
|
int capped = visible_rows < 5 ? visible_rows : 5;
|
||||||
|
float row_h = ImGui::GetTextLineHeightWithSpacing();
|
||||||
|
float child_h = row_h * (float)(capped + 1) + ImGui::GetStyle().ItemSpacing.y * 2.0f;
|
||||||
|
child_h = child_h < 120.0f ? child_h : 120.0f; // hard cap
|
||||||
|
|
||||||
|
ImGui::TextDisabled("Actions");
|
||||||
|
if (ImGui::BeginChild("##jobs_actions", ImVec2(0.0f, child_h),
|
||||||
|
ImGuiChildFlags_Borders)) {
|
||||||
|
ImGuiTableFlags aflags = ImGuiTableFlags_Borders |
|
||||||
|
ImGuiTableFlags_RowBg |
|
||||||
|
ImGuiTableFlags_SizingStretchProp |
|
||||||
|
ImGuiTableFlags_ScrollY;
|
||||||
|
if (ImGui::BeginTable("##jobs_actions_tbl", 3, aflags)) {
|
||||||
|
ImGui::TableSetupScrollFreeze(0, 1);
|
||||||
|
ImGui::TableSetupColumn("Job", ImGuiTableColumnFlags_WidthStretch, 3.0f);
|
||||||
|
ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 70.0f);
|
||||||
|
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 64.0f);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
for (const auto* rp : visible) {
|
||||||
|
const JobRow& r = *rp;
|
||||||
|
ImGui::PushID(r.id.c_str());
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
|
||||||
|
// Job label (enricher + target truncated).
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
std::string label = r.enricher_id;
|
||||||
|
if (!r.node_name.empty()) label += " / " + r.node_name;
|
||||||
|
else if (!r.node_id.empty()) label += " / " + r.node_id;
|
||||||
|
if (label.size() > 40) label = label.substr(0, 38) + "…";
|
||||||
|
ImGui::TextUnformatted(label.c_str());
|
||||||
|
|
||||||
|
// Status badge.
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::TextColored(status_color(r.status), "%s", r.status.c_str());
|
||||||
|
|
||||||
|
// Action button.
|
||||||
|
ImGui::TableSetColumnIndex(2);
|
||||||
|
if (r.status == "queued" || r.status == "running") {
|
||||||
|
if (ImGui::SmallButton("Cancel")) {
|
||||||
|
jobs_cancel(r.id.c_str());
|
||||||
|
// Invalidate cache immediately.
|
||||||
|
g_jobs_cache.last_frame_refresh = -100;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ImGui::SmallButton("Delete")) {
|
||||||
|
jobs_delete(r.id.c_str());
|
||||||
|
g_jobs_cache.last_frame_refresh = -100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::Spacing();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Main data table via data_table::render (issue 0081-J migration).
|
||||||
|
//
|
||||||
|
// Columns:
|
||||||
|
// 0: status — Badge renderer (queued/running/done/error/cancelled)
|
||||||
|
// 1: enricher — Text (default)
|
||||||
|
// 2: target — Text (default)
|
||||||
|
// 3: progress — Progress renderer, 0..1 scale
|
||||||
|
// 4: time_ms — Duration renderer, warn=1000ms error=10000ms
|
||||||
|
//
|
||||||
|
// Rows are filtered by g_jobs_cache.filter_idx BEFORE populating
|
||||||
|
// cells, so data_table only sees the subset the user wants.
|
||||||
|
//
|
||||||
|
// Cell backing: static vectors rebuilt when cache refreshes.
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static const std::vector<std::string> k_headers = {
|
||||||
|
"status", "enricher", "target", "progress", "time_ms"
|
||||||
|
};
|
||||||
|
constexpr int k_ncols = 5;
|
||||||
|
|
||||||
|
static std::vector<std::string> s_cell_backing;
|
||||||
|
static std::vector<const char*> s_cells;
|
||||||
|
static int s_rows_cached = -1;
|
||||||
|
static int s_filter_cached = -1;
|
||||||
|
|
||||||
|
// Rebuild when cache was refreshed or filter changed.
|
||||||
|
bool needs_rebuild = (s_rows_cached != (int)g_jobs_cache.rows.size()) ||
|
||||||
|
(s_filter_cached != g_jobs_cache.filter_idx) ||
|
||||||
|
(frame - g_jobs_cache.last_frame_refresh <= 10); // just refreshed
|
||||||
|
|
||||||
|
if (needs_rebuild) {
|
||||||
|
// Collect filtered rows.
|
||||||
|
std::vector<const JobRow*> frows;
|
||||||
|
frows.reserve(g_jobs_cache.rows.size());
|
||||||
|
for (const auto& r : g_jobs_cache.rows) {
|
||||||
|
if (filter_match(r.status, g_jobs_cache.filter_idx))
|
||||||
|
frows.push_back(&r);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int nrows = (int)frows.size();
|
||||||
|
s_cell_backing.resize((size_t)nrows * k_ncols);
|
||||||
|
s_cells.resize((size_t)nrows * k_ncols);
|
||||||
|
|
||||||
|
for (int i = 0; i < nrows; ++i) {
|
||||||
|
const JobRow& r = *frows[(size_t)i];
|
||||||
|
const size_t base = (size_t)i * k_ncols;
|
||||||
|
|
||||||
|
// status
|
||||||
|
s_cell_backing[base + 0] = r.status;
|
||||||
|
|
||||||
|
// enricher
|
||||||
|
s_cell_backing[base + 1] = r.enricher_id;
|
||||||
|
|
||||||
|
// target: node_name > node_id > "(global)"
|
||||||
|
if (!r.node_name.empty())
|
||||||
|
s_cell_backing[base + 2] = r.node_name;
|
||||||
|
else if (!r.node_id.empty())
|
||||||
|
s_cell_backing[base + 2] = r.node_id;
|
||||||
|
else
|
||||||
|
s_cell_backing[base + 2] = "(global)";
|
||||||
|
|
||||||
|
// progress: float 0..1 -> string (data_table Progress renderer
|
||||||
|
// parses via strtof; value is in [0,1]).
|
||||||
|
char pbuf[16];
|
||||||
|
std::snprintf(pbuf, sizeof(pbuf), "%.4f", (float)r.progress);
|
||||||
|
s_cell_backing[base + 3] = pbuf;
|
||||||
|
|
||||||
|
// time_ms: duration in milliseconds as float string.
|
||||||
|
char tbuf[24];
|
||||||
|
long long ms = job_duration_ms(r);
|
||||||
|
std::snprintf(tbuf, sizeof(tbuf), "%lld", ms);
|
||||||
|
s_cell_backing[base + 4] = tbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t k = 0; k < s_cell_backing.size(); ++k)
|
||||||
|
s_cells[k] = s_cell_backing[k].c_str();
|
||||||
|
|
||||||
|
s_rows_cached = (int)g_jobs_cache.rows.size();
|
||||||
|
s_filter_cached = g_jobs_cache.filter_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build column specs with declarative renderers.
|
||||||
|
static const std::vector<data_table::ColumnSpec> k_col_specs = []() {
|
||||||
|
std::vector<data_table::ColumnSpec> specs(k_ncols);
|
||||||
|
|
||||||
|
// Col 0: status — Badge
|
||||||
|
specs[0].id = "status";
|
||||||
|
specs[0].renderer = data_table::CellRenderer::Badge;
|
||||||
|
specs[0].badges = {
|
||||||
|
{ "running", "#3b82f6", "Running" },
|
||||||
|
{ "done", "#22c55e", "Done" },
|
||||||
|
{ "error", "#ef4444", "Error" },
|
||||||
|
{ "cancelled", "#9ca3af", "Cancelled" },
|
||||||
|
{ "queued", "#f59e0b", "Queued" },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Col 1: enricher — Text (default)
|
||||||
|
specs[1].id = "enricher";
|
||||||
|
specs[1].renderer = data_table::CellRenderer::Text;
|
||||||
|
|
||||||
|
// Col 2: target — Text (default)
|
||||||
|
specs[2].id = "target";
|
||||||
|
specs[2].renderer = data_table::CellRenderer::Text;
|
||||||
|
|
||||||
|
// Col 3: progress — Progress bar, values are 0..1
|
||||||
|
specs[3].id = "progress";
|
||||||
|
specs[3].renderer = data_table::CellRenderer::Progress;
|
||||||
|
specs[3].progress_scale_100 = false;
|
||||||
|
|
||||||
|
// Col 4: time_ms — Duration, warn=1000ms error=10000ms
|
||||||
|
specs[4].id = "time_ms";
|
||||||
|
specs[4].renderer = data_table::CellRenderer::Duration;
|
||||||
|
specs[4].duration_warn_ms = 1000.0f;
|
||||||
|
specs[4].duration_error_ms = 10000.0f;
|
||||||
|
|
||||||
|
return specs;
|
||||||
|
}();
|
||||||
|
|
||||||
|
// Compute available height for main table.
|
||||||
|
float avail_h = ImGui::GetContentRegionAvail().y - 4.0f;
|
||||||
|
if (avail_h < 80.0f) avail_h = 80.0f;
|
||||||
|
|
||||||
|
ImGui::TextDisabled("Data");
|
||||||
|
ImGui::BeginChild("##jobs_data", ImVec2(0.0f, avail_h), ImGuiChildFlags_None);
|
||||||
|
|
||||||
|
int nrows_vis = s_rows_cached; // total cached — filtered subset
|
||||||
|
// Recompute actual filtered row count for the data child.
|
||||||
|
int nrows_data = 0;
|
||||||
|
for (const auto& r : g_jobs_cache.rows) {
|
||||||
|
if (filter_match(r.status, g_jobs_cache.filter_idx)) ++nrows_data;
|
||||||
|
}
|
||||||
|
(void)nrows_vis;
|
||||||
|
|
||||||
|
if (nrows_data == 0) {
|
||||||
|
ImGui::TextDisabled("(no jobs match current filter)");
|
||||||
|
} else {
|
||||||
|
data_table::TableInput tbl;
|
||||||
|
tbl.name = "jobs";
|
||||||
|
tbl.headers = k_headers;
|
||||||
|
tbl.types = {
|
||||||
|
data_table::ColumnType::String, // status
|
||||||
|
data_table::ColumnType::String, // enricher
|
||||||
|
data_table::ColumnType::String, // target
|
||||||
|
data_table::ColumnType::Float, // progress
|
||||||
|
data_table::ColumnType::Float, // time_ms
|
||||||
|
};
|
||||||
|
tbl.cells = s_cells.empty() ? nullptr : s_cells.data();
|
||||||
|
tbl.rows = nrows_data;
|
||||||
|
tbl.cols = k_ncols;
|
||||||
|
tbl.column_specs = k_col_specs;
|
||||||
|
|
||||||
|
data_table::render("##jobs_dt", {tbl}, app.jobs_dt_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user