2020from rich .console import Console
2121from rich .table import Table
2222from rich .panel import Panel
23- from typing import Tuple , Optional
23+ from typing import Tuple , Optional , List
2424
25- from .status import RayJobDeploymentStatus , RayJobInfo
25+ from .status import RayJobDeploymentStatus , RayJobInfo , KueueWorkloadInfo
2626
2727
2828def print_job_status (job_info : RayJobInfo ):
@@ -37,6 +37,10 @@ def print_job_status(job_info: RayJobInfo):
3737 table .add_row (f"[bold]Status:[/bold] { job_info .status .value } " )
3838 table .add_row (f"[bold]RayCluster:[/bold] { job_info .cluster_name } " )
3939 table .add_row (f"[bold]Namespace:[/bold] { job_info .namespace } " )
40+
41+ # Add cluster management info
42+ managed_text = "[bold green]Yes (Job-managed)[/bold green]" if job_info .is_managed_cluster else "[dim]No (Existing cluster)[/dim]"
43+ table .add_row (f"[bold]Managed Cluster:[/bold] { managed_text } " )
4044
4145 # Add timing information if available
4246 if job_info .start_time :
@@ -47,6 +51,23 @@ def print_job_status(job_info: RayJobInfo):
4751 if job_info .failed_attempts > 0 :
4852 table .add_row (f"[bold]Failed Attempts:[/bold] { job_info .failed_attempts } " )
4953
54+ # Add Kueue information if available
55+ if job_info .kueue_workload :
56+ table .add_row ()
57+ table .add_row ("[bold blue]🎯 Kueue Integration[/bold blue]" )
58+ table .add_row (f"[bold]Local Queue:[/bold] { job_info .local_queue } " )
59+ table .add_row (f"[bold]Workload Status:[/bold] [bold green]{ job_info .kueue_workload .status } [/bold green]" )
60+ table .add_row (f"[bold]Workload Name:[/bold] { job_info .kueue_workload .name } " )
61+ if job_info .kueue_workload .priority is not None :
62+ table .add_row (f"[bold]Priority:[/bold] { job_info .kueue_workload .priority } " )
63+ if job_info .kueue_workload .admission_time :
64+ table .add_row (f"[bold]Admitted:[/bold] { job_info .kueue_workload .admission_time } " )
65+ elif job_info .local_queue :
66+ table .add_row ()
67+ table .add_row ("[bold blue]🎯 Kueue Integration[/bold blue]" )
68+ table .add_row (f"[bold]Local Queue:[/bold] { job_info .local_queue } " )
69+ table .add_row ("[dim]Workload information not available[/dim]" )
70+
5071 _print_table_in_panel (table )
5172
5273
@@ -66,6 +87,162 @@ def print_no_job_found(job_name: str, namespace: str):
6687 _print_table_in_panel (table )
6788
6889
90+ def print_jobs_list (job_list : List [RayJobInfo ], namespace : str , pagination_info : Optional [dict ] = None ):
91+ """
92+ Pretty print a list of RayJobs using Rich formatting with pagination support.
93+
94+ Args:
95+ job_list: List of RayJobInfo objects (for current page)
96+ namespace: Kubernetes namespace
97+ pagination_info: Optional pagination information dict
98+ """
99+ if not job_list :
100+ # Create table for no jobs found
101+ table = _create_info_table (
102+ "[white on yellow][bold]Namespace" , namespace , "[bold yellow]No RayJobs found"
103+ )
104+ table .add_row ()
105+ table .add_row ("No RayJobs found in this namespace." )
106+ table .add_row ("Jobs may have been deleted or completed with TTL cleanup." )
107+ _print_table_in_panel (table )
108+ return
109+
110+ # Create main table for job list
111+ console = Console ()
112+
113+ # Create title with pagination info
114+ title = f"[bold blue]🚀 RayJobs in namespace: { namespace } [/bold blue]"
115+ if pagination_info and pagination_info ["total_pages" ] > 1 :
116+ title += f" [dim](Page { pagination_info ['current_page' ]} of { pagination_info ['total_pages' ]} )[/dim]"
117+
118+ # Create jobs table with responsive width
119+ jobs_table = Table (
120+ title = title ,
121+ show_header = True ,
122+ header_style = "bold magenta" ,
123+ border_style = "blue" ,
124+ expand = True , # Allow table to expand to terminal width
125+ min_width = 120 , # Minimum width for readability
126+ )
127+
128+ # Add columns with flexible width allocation and wrapping
129+ jobs_table .add_column ("Status" , style = "bold" , min_width = 12 , max_width = 16 )
130+ jobs_table .add_column ("Job Name" , style = "bold cyan" , min_width = 15 , max_width = 30 )
131+ jobs_table .add_column ("Job ID" , style = "dim" , min_width = 12 , max_width = 25 )
132+ jobs_table .add_column ("Cluster" , min_width = 12 , max_width = 25 )
133+ jobs_table .add_column ("Managed" , min_width = 8 , max_width = 12 )
134+ jobs_table .add_column ("Queue" , min_width = 10 , max_width = 18 )
135+ jobs_table .add_column ("Kueue Status" , min_width = 8 , max_width = 15 )
136+ jobs_table .add_column ("Start Time" , style = "dim" , min_width = 8 , max_width = 12 )
137+
138+ # Add rows for each job
139+ for job_info in job_list :
140+ status_display , _ = _get_status_display (job_info .status )
141+
142+ # Format start time more compactly
143+ if job_info .start_time :
144+ try :
145+ # Extract just time portion and make it compact
146+ start_time = job_info .start_time .split ('T' )[1 ][:8 ] # HH:MM:SS
147+ except (IndexError , AttributeError ):
148+ start_time = "N/A"
149+ else :
150+ start_time = "N/A"
151+
152+ # Truncate long values intelligently (Rich will handle further truncation if needed)
153+ job_name = _truncate_text (job_info .name , 28 )
154+ job_id = _truncate_text (job_info .job_id , 23 )
155+ cluster_name = _truncate_text (job_info .cluster_name , 23 )
156+
157+ # Cluster management info
158+ managed_display = "✅ Yes" if job_info .is_managed_cluster else "❌ No"
159+ managed_style = "bold green" if job_info .is_managed_cluster else "dim"
160+
161+ # Kueue information
162+ queue_display = _truncate_text (job_info .local_queue or "N/A" , 16 )
163+ kueue_status_display = "N/A"
164+ kueue_status_style = "dim"
165+
166+ if job_info .kueue_workload :
167+ kueue_status_display = job_info .kueue_workload .status
168+ if job_info .kueue_workload .status == "Admitted" :
169+ kueue_status_style = "bold green"
170+ elif job_info .kueue_workload .status == "Pending" :
171+ kueue_status_style = "bold yellow"
172+ elif job_info .kueue_workload .status == "Finished" :
173+ kueue_status_style = "bold blue"
174+
175+ jobs_table .add_row (
176+ status_display ,
177+ job_name ,
178+ job_id ,
179+ cluster_name ,
180+ f"[{ managed_style } ]{ managed_display } [/{ managed_style } ]" ,
181+ queue_display ,
182+ f"[{ kueue_status_style } ]{ kueue_status_display } [/{ kueue_status_style } ]" ,
183+ start_time ,
184+ )
185+
186+ # Print the table
187+ console .print (jobs_table )
188+
189+ # Add pagination information
190+ if pagination_info :
191+ console .print () # Add spacing
192+ if pagination_info ["total_pages" ] > 1 :
193+ console .print (
194+ f"[dim]Showing { pagination_info ['showing_start' ]} -{ pagination_info ['showing_end' ]} "
195+ f"of { pagination_info ['total_jobs' ]} jobs "
196+ f"(Page { pagination_info ['current_page' ]} of { pagination_info ['total_pages' ]} )[/dim]"
197+ )
198+
199+ # Navigation hints
200+ nav_hints = []
201+ if pagination_info ['current_page' ] > 1 :
202+ nav_hints .append (f"Previous: RayJob.List(page={ pagination_info ['current_page' ] - 1 } )" )
203+ if pagination_info ['current_page' ] < pagination_info ['total_pages' ]:
204+ nav_hints .append (f"Next: RayJob.List(page={ pagination_info ['current_page' ] + 1 } )" )
205+
206+ if nav_hints :
207+ console .print (f"[dim]Navigation: { ' | ' .join (nav_hints )} [/dim]" )
208+
209+ # Add summary information
210+ kueue_jobs = [job for job in job_list if job .local_queue ]
211+ managed_jobs = [job for job in job_list if job .is_managed_cluster ]
212+
213+ if kueue_jobs or managed_jobs :
214+ console .print () # Add spacing
215+ if managed_jobs :
216+ total_managed = len (managed_jobs )
217+ total_jobs = pagination_info ["total_jobs" ] if pagination_info else len (job_list )
218+ console .print (f"[bold green]🏗️ { total_managed } of { total_jobs } jobs use job-managed clusters[/bold green]" )
219+ if kueue_jobs :
220+ total_kueue = len (kueue_jobs )
221+ total_jobs = pagination_info ["total_jobs" ] if pagination_info else len (job_list )
222+ console .print (f"[bold blue]🎯 { total_kueue } of { total_jobs } jobs are managed by Kueue[/bold blue]" )
223+
224+
225+ def _truncate_text (text : str , max_length : int ) -> str :
226+ """
227+ Truncate text intelligently with ellipsis if needed.
228+
229+ Args:
230+ text: Text to truncate
231+ max_length: Maximum length including ellipsis
232+
233+ Returns:
234+ Truncated text with ellipsis if needed
235+ """
236+ if len (text ) <= max_length :
237+ return text
238+
239+ # Leave room for ellipsis
240+ if max_length <= 3 :
241+ return text [:max_length ]
242+
243+ return text [:max_length - 3 ] + "..."
244+
245+
69246def _get_status_display (status : RayJobDeploymentStatus ) -> Tuple [str , str ]:
70247 """
71248 Get the display string and header color for a given status.
0 commit comments