Skip to content

Commit 8abfa73

Browse files
fix node detail wide columns
1 parent 876624a commit 8abfa73

1 file changed

Lines changed: 129 additions & 95 deletions

File tree

src/ui/mod.rs

Lines changed: 129 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,43 +1623,150 @@ fn render_node_detail(
16231623

16241624
let jobs = app.visible_node_jobs();
16251625
let resource_scale = JobResourceScale::from_jobs(&jobs);
1626-
let headers = vec![
1627-
Line::from("Job ID"),
1628-
Line::from("User"),
1629-
Line::from("State"),
1630-
Line::from("Runtime"),
1631-
Line::from("Resource footprint"),
1632-
Line::from("Partition"),
1633-
Line::from("Where"),
1634-
Line::from("Why"),
1635-
Line::from("Name"),
1626+
let resource_texts = jobs
1627+
.iter()
1628+
.map(|job| resource_footprint_text(job, &resource_scale))
1629+
.collect::<Vec<_>>();
1630+
let resource_segments = max_segments(&resource_texts, RESOURCE_SEGMENT_WIDTH);
1631+
let name_segments = max_segments(
1632+
&jobs.iter().map(|job| job.name.clone()).collect::<Vec<_>>(),
1633+
NAME_SEGMENT_WIDTH,
1634+
);
1635+
let partition_width = jobs
1636+
.iter()
1637+
.map(|job| job.partition_raw.chars().count())
1638+
.max()
1639+
.unwrap_or(11)
1640+
.clamp(11, 32) as u16;
1641+
let where_width = jobs
1642+
.iter()
1643+
.map(|job| job.location_or_reason.chars().count())
1644+
.max()
1645+
.unwrap_or(18)
1646+
.clamp(18, 72) as u16;
1647+
let why_width = jobs
1648+
.iter()
1649+
.map(|job| {
1650+
if job.pending {
1651+
job.location_or_reason.chars().count()
1652+
} else {
1653+
job.state.chars().count()
1654+
}
1655+
})
1656+
.max()
1657+
.unwrap_or(14)
1658+
.clamp(14, 48) as u16;
1659+
let mut headers = vec![
1660+
sortable_header(
1661+
"Job ID",
1662+
app.job_sort.column == JobColumn::JobId,
1663+
app.job_sort.direction,
1664+
theme,
1665+
),
1666+
sortable_header(
1667+
"User",
1668+
app.job_sort.column == JobColumn::User,
1669+
app.job_sort.direction,
1670+
theme,
1671+
),
1672+
sortable_header(
1673+
"State",
1674+
app.job_sort.column == JobColumn::State,
1675+
app.job_sort.direction,
1676+
theme,
1677+
),
1678+
sortable_header(
1679+
"Runtime",
1680+
app.job_sort.column == JobColumn::Runtime,
1681+
app.job_sort.direction,
1682+
theme,
1683+
),
1684+
sortable_header(
1685+
"Partition",
1686+
app.job_sort.column == JobColumn::Partition,
1687+
app.job_sort.direction,
1688+
theme,
1689+
),
1690+
sortable_header("Where", false, app.job_sort.direction, theme),
1691+
sortable_header("Why", false, app.job_sort.direction, theme),
16361692
];
1637-
let column_widths = vec![8, 10, 11, 10, 22, 11, 16, 16, 18];
1693+
let mut column_widths = vec![8, 10, 11, 10, partition_width, where_width, why_width];
1694+
let mut header_hits = vec![
1695+
Some(MouseHit::JobHeader(JobColumn::JobId)),
1696+
Some(MouseHit::JobHeader(JobColumn::User)),
1697+
Some(MouseHit::JobHeader(JobColumn::State)),
1698+
Some(MouseHit::JobHeader(JobColumn::Runtime)),
1699+
Some(MouseHit::JobHeader(JobColumn::Partition)),
1700+
None,
1701+
None,
1702+
];
1703+
for segment in 0..resource_segments {
1704+
headers.push(sortable_header(
1705+
if resource_segments > 1 && segment > 0 {
1706+
"Resource footprint →"
1707+
} else {
1708+
"Resource footprint"
1709+
},
1710+
false,
1711+
app.job_sort.direction,
1712+
theme,
1713+
));
1714+
column_widths.push(RESOURCE_SEGMENT_WIDTH as u16);
1715+
header_hits.push(None);
1716+
}
1717+
for segment in 0..name_segments {
1718+
headers.push(sortable_header(
1719+
if name_segments > 1 && segment > 0 {
1720+
"Name →"
1721+
} else {
1722+
"Name"
1723+
},
1724+
app.job_sort.column == JobColumn::Name,
1725+
app.job_sort.direction,
1726+
theme,
1727+
));
1728+
column_widths.push(NAME_SEGMENT_WIDTH as u16);
1729+
header_hits.push(Some(MouseHit::JobHeader(JobColumn::Name)));
1730+
}
16381731
let full_rows: Vec<Vec<Cell>> = jobs
16391732
.iter()
1640-
.map(|job| {
1733+
.zip(resource_texts.iter())
1734+
.map(|(job, resource_text)| {
16411735
let why_text = if job.pending {
16421736
job.location_or_reason.clone()
16431737
} else {
16441738
job.state.clone()
16451739
};
1646-
vec![
1740+
let mut cells = vec![
16471741
Cell::from(job.job_id.clone()),
16481742
Cell::from(Line::from(vec![Span::styled(
16491743
job.user.clone(),
16501744
if job.is_mine { theme.mine } else { theme.other },
16511745
)])),
16521746
Cell::from(job.state.clone()),
16531747
Cell::from(job.runtime_raw.clone()),
1654-
Cell::from(job_resource_cell(job, &resource_scale, theme)),
16551748
Cell::from(Line::from(vec![Span::styled(
16561749
job.partition_raw.clone(),
16571750
theme.partition_style(job.primary_partition()),
16581751
)])),
16591752
Cell::from(job.location_or_reason.clone()),
16601753
Cell::from(why_text),
1661-
Cell::from(job.name.clone()),
1662-
]
1754+
];
1755+
for segment in 0..resource_segments {
1756+
cells.push(Cell::from(segment_text(
1757+
resource_text,
1758+
segment,
1759+
RESOURCE_SEGMENT_WIDTH,
1760+
)));
1761+
}
1762+
for segment in 0..name_segments {
1763+
cells.push(Cell::from(segment_text(
1764+
&job.name,
1765+
segment,
1766+
NAME_SEGMENT_WIDTH,
1767+
)));
1768+
}
1769+
cells
16631770
})
16641771
.collect();
16651772
let (visible_indices, hidden_left, hidden_right) = visible_column_indices(
@@ -1675,6 +1782,10 @@ fn render_node_detail(
16751782
.iter()
16761783
.map(|index| headers[*index].clone())
16771784
.collect::<Vec<_>>();
1785+
let visible_hits = visible_indices
1786+
.iter()
1787+
.map(|index| header_hits[*index].clone())
1788+
.collect::<Vec<_>>();
16781789
let table_rows: Vec<Row> = jobs
16791790
.iter()
16801791
.zip(full_rows.iter())
@@ -1696,7 +1807,7 @@ fn render_node_detail(
16961807
Some(node.selected_job.min(jobs.len().saturating_sub(1)))
16971808
});
16981809
frame.render_stateful_widget(
1699-
Table::new(table_rows, constraints)
1810+
Table::new(table_rows, constraints.clone())
17001811
.header(Row::new(visible_headers).style(theme.title.add_modifier(Modifier::BOLD)))
17011812
.block(Block::default().borders(Borders::ALL).title(format!(
17021813
"Jobs on this Node Hidden left: {} Hidden right: {}",
@@ -1708,6 +1819,7 @@ fn render_node_detail(
17081819
&mut table_state,
17091820
);
17101821
register_table_rows(hit_map, sections[2], jobs.len(), RowKind::NodeJobs);
1822+
register_header_hits(hit_map, sections[2], &constraints, &visible_hits);
17111823
}
17121824

17131825
#[allow(dead_code)]
@@ -2890,43 +3002,6 @@ fn partition_summary_line(usage: &UserUsage, theme: &Theme) -> Line<'static> {
28903002
}
28913003
}
28923004

2893-
fn job_resource_cell(job: &JobRecord, scale: &JobResourceScale, theme: &Theme) -> Line<'static> {
2894-
let nodes = metric_segment_spans(
2895-
"Node",
2896-
Some(job.nodes),
2897-
scale.max_nodes,
2898-
scale.node_digits(),
2899-
5,
2900-
theme.accent,
2901-
theme,
2902-
);
2903-
let cpus = metric_segment_spans(
2904-
"CPU",
2905-
job.cpus,
2906-
scale.max_cpus,
2907-
scale.cpu_digits(),
2908-
5,
2909-
theme.warning,
2910-
theme,
2911-
);
2912-
let gpus = metric_segment_spans(
2913-
"GPU",
2914-
job.requested_gpus,
2915-
scale.max_gpus,
2916-
scale.gpu_digits(),
2917-
5,
2918-
theme.mine,
2919-
theme,
2920-
);
2921-
let mut spans = Vec::new();
2922-
spans.extend(nodes);
2923-
spans.push(Span::raw(" "));
2924-
spans.extend(cpus);
2925-
spans.push(Span::raw(" "));
2926-
spans.extend(gpus);
2927-
Line::from(spans)
2928-
}
2929-
29303005
fn resource_footprint_text(job: &JobRecord, scale: &JobResourceScale) -> String {
29313006
[
29323007
metric_segment_text(
@@ -2975,47 +3050,6 @@ fn user_resource_footprint_text(usage: &UserUsage, scale: &UserResourceScale) ->
29753050
.join(" ")
29763051
}
29773052

2978-
fn metric_segment_spans(
2979-
label: &str,
2980-
value: Option<u32>,
2981-
max_value: u32,
2982-
digits: usize,
2983-
width: usize,
2984-
style: Style,
2985-
theme: &Theme,
2986-
) -> Vec<Span<'static>> {
2987-
let mut spans = vec![Span::raw(format!("{label:<4} "))];
2988-
match value {
2989-
Some(value) => spans.extend(metric_bar_spans(
2990-
value, max_value, width, digits, style, theme,
2991-
)),
2992-
None => spans.extend(metric_na_spans(theme)),
2993-
}
2994-
spans
2995-
}
2996-
2997-
fn metric_bar_spans(
2998-
value: u32,
2999-
max_value: u32,
3000-
width: usize,
3001-
digits: usize,
3002-
style: Style,
3003-
theme: &Theme,
3004-
) -> Vec<Span<'static>> {
3005-
let filled = scaled_bar_width(u64::from(value), u64::from(max_value.max(1)), width);
3006-
vec![
3007-
Span::styled("[".to_string(), theme.muted),
3008-
Span::styled("█".repeat(filled), style),
3009-
Span::styled("·".repeat(width.saturating_sub(filled)), theme.muted),
3010-
Span::styled("]".to_string(), theme.muted),
3011-
Span::raw(format!(" {:>digits$}", value)),
3012-
]
3013-
}
3014-
3015-
fn metric_na_spans(theme: &Theme) -> Vec<Span<'static>> {
3016-
vec![Span::styled("[n/a]", theme.muted)]
3017-
}
3018-
30193053
fn metric_bar_text(value: u32, max_value: u32, width: usize, digits: usize) -> String {
30203054
let filled = scaled_bar_width(u64::from(value), u64::from(max_value.max(1)), width);
30213055
format!(

0 commit comments

Comments
 (0)