@@ -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-
29303005fn 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-
30193053fn 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