@@ -14,6 +14,72 @@ interface OdsListResponse {
1414 data : SandboxModel [ ] ;
1515}
1616
17+ /**
18+ * Column definition for table output.
19+ */
20+ interface ColumnDef {
21+ /** Column header label */
22+ header : string ;
23+ /** Minimum width in characters */
24+ minWidth ?: number ;
25+ /** Function to extract value from sandbox */
26+ get : ( s : SandboxModel ) => string ;
27+ /** Whether this column is only shown with --extended */
28+ extended ?: boolean ;
29+ }
30+
31+ /**
32+ * Available columns for sandbox list output.
33+ */
34+ const COLUMNS : Record < string , ColumnDef > = {
35+ realm : {
36+ header : 'Realm' ,
37+ get : ( s ) => s . realm || '-' ,
38+ } ,
39+ instance : {
40+ header : 'Num' ,
41+ get : ( s ) => s . instance || '-' ,
42+ } ,
43+ state : {
44+ header : 'State' ,
45+ get : ( s ) => s . state || '-' ,
46+ } ,
47+ profile : {
48+ header : 'Profile' ,
49+ get : ( s ) => s . resourceProfile || '-' ,
50+ } ,
51+ created : {
52+ header : 'Created' ,
53+ get : ( s ) => ( s . createdAt ? new Date ( s . createdAt ) . toISOString ( ) . slice ( 0 , 10 ) : '-' ) ,
54+ } ,
55+ eol : {
56+ header : 'EOL' ,
57+ get : ( s ) => ( s . eol ? new Date ( s . eol ) . toISOString ( ) . slice ( 0 , 10 ) : '-' ) ,
58+ } ,
59+ id : {
60+ header : 'ID' ,
61+ get : ( s ) => s . id || '-' ,
62+ } ,
63+ hostname : {
64+ header : 'Hostname' ,
65+ get : ( s ) => s . hostName || '-' ,
66+ extended : true ,
67+ } ,
68+ createdBy : {
69+ header : 'Created By' ,
70+ get : ( s ) => s . createdBy || '-' ,
71+ extended : true ,
72+ } ,
73+ autoScheduled : {
74+ header : 'Auto' ,
75+ get : ( s ) => ( s . autoScheduled ? 'Yes' : 'No' ) ,
76+ extended : true ,
77+ } ,
78+ } ;
79+
80+ /** Default columns shown without --extended */
81+ const DEFAULT_COLUMNS = [ 'realm' , 'instance' , 'state' , 'profile' , 'created' , 'eol' , 'id' ] ;
82+
1783/**
1884 * Command to list all on-demand sandboxes.
1985 */
@@ -27,6 +93,8 @@ export default class OdsList extends OdsCommand<typeof OdsList> {
2793 '<%= config.bin %> <%= command.id %> --realm abcd' ,
2894 '<%= config.bin %> <%= command.id %> --filter-params "realm=abcd&state=started"' ,
2995 '<%= config.bin %> <%= command.id %> --show-deleted' ,
96+ '<%= config.bin %> <%= command.id %> --extended' ,
97+ '<%= config.bin %> <%= command.id %> --columns realm,instance,state,hostname' ,
3098 '<%= config.bin %> <%= command.id %> --json' ,
3199 ] ;
32100
@@ -42,6 +110,15 @@ export default class OdsList extends OdsCommand<typeof OdsList> {
42110 description : 'Include deleted sandboxes in the list' ,
43111 default : false ,
44112 } ) ,
113+ columns : Flags . string ( {
114+ char : 'c' ,
115+ description : `Columns to display (comma-separated). Available: ${ Object . keys ( COLUMNS ) . join ( ', ' ) } ` ,
116+ } ) ,
117+ extended : Flags . boolean ( {
118+ char : 'x' ,
119+ description : 'Show all columns including extended fields' ,
120+ default : false ,
121+ } ) ,
45122 } ;
46123
47124 async run ( ) : Promise < OdsListResponse > {
@@ -99,42 +176,98 @@ export default class OdsList extends OdsCommand<typeof OdsList> {
99176 return response ;
100177 }
101178
102- this . printSandboxesTable ( sandboxes ) ;
179+ this . printSandboxesTable ( sandboxes , this . getSelectedColumns ( ) ) ;
103180
104181 return response ;
105182 }
106183
107- private printSandboxesTable ( sandboxes : SandboxModel [ ] ) : void {
108- const ui = cliui ( { width : process . stdout . columns || 120 } ) ;
184+ /**
185+ * Determines which columns to display based on flags.
186+ */
187+ private getSelectedColumns ( ) : string [ ] {
188+ const columnsFlag = this . flags . columns ;
189+ const extended = this . flags . extended ;
190+
191+ if ( columnsFlag ) {
192+ // User specified explicit columns
193+ const requested = columnsFlag . split ( ',' ) . map ( ( c ) => c . trim ( ) ) ;
194+ const valid = requested . filter ( ( c ) => c in COLUMNS ) ;
195+ if ( valid . length === 0 ) {
196+ this . warn ( `No valid columns specified. Available: ${ Object . keys ( COLUMNS ) . join ( ', ' ) } ` ) ;
197+ return DEFAULT_COLUMNS ;
198+ }
199+ return valid ;
200+ }
201+
202+ if ( extended ) {
203+ // Show all columns
204+ return Object . keys ( COLUMNS ) ;
205+ }
206+
207+ // Default columns (non-extended)
208+ return DEFAULT_COLUMNS ;
209+ }
210+
211+ /**
212+ * Calculate dynamic column widths based on content.
213+ * Each column width = max(header length, max data length) + padding
214+ */
215+ private calculateColumnWidths ( sandboxes : SandboxModel [ ] , columnKeys : string [ ] ) : Map < string , number > {
216+ const widths = new Map < string , number > ( ) ;
217+ const padding = 2 ; // Space between columns
109218
110- // Header
111- ui . div (
112- { text : 'Realm' , width : 8 , padding : [ 0 , 1 , 0 , 0 ] } ,
113- { text : 'Instance' , width : 10 , padding : [ 0 , 1 , 0 , 0 ] } ,
114- { text : 'State' , width : 12 , padding : [ 0 , 1 , 0 , 0 ] } ,
115- { text : 'Profile' , width : 10 , padding : [ 0 , 1 , 0 , 0 ] } ,
116- { text : 'Created' , width : 12 , padding : [ 0 , 1 , 0 , 0 ] } ,
117- { text : 'EOL' , width : 12 , padding : [ 0 , 1 , 0 , 0 ] } ,
118- { text : 'ID' } ,
119- ) ;
219+ for ( const key of columnKeys ) {
220+ const col = COLUMNS [ key ] ;
221+ // Start with header length
222+ let maxWidth = col . header . length ;
223+
224+ // Check all data values
225+ for ( const sandbox of sandboxes ) {
226+ const value = col . get ( sandbox ) ;
227+ maxWidth = Math . max ( maxWidth , value . length ) ;
228+ }
229+
230+ // Apply minimum width if specified, add padding
231+ const minWidth = col . minWidth || 0 ;
232+ widths . set ( key , Math . max ( maxWidth , minWidth ) + padding ) ;
233+ }
234+
235+ return widths ;
236+ }
237+
238+ private printSandboxesTable ( sandboxes : SandboxModel [ ] , columnKeys : string [ ] ) : void {
239+ const termWidth = process . stdout . columns || 120 ;
240+ const ui = cliui ( { width : termWidth } ) ;
241+
242+ // Calculate dynamic widths based on content
243+ const widths = this . calculateColumnWidths ( sandboxes , columnKeys ) ;
244+
245+ // Build header row
246+ const headerCols = columnKeys . map ( ( key ) => {
247+ const col = COLUMNS [ key ] ;
248+ return {
249+ text : col . header ,
250+ width : widths . get ( key ) ,
251+ padding : [ 0 , 1 , 0 , 0 ] as [ number , number , number , number ] ,
252+ } ;
253+ } ) ;
254+ ui . div ( ...headerCols ) ;
120255
121256 // Separator
122- ui . div ( { text : '─' . repeat ( 100 ) , padding : [ 0 , 0 , 0 , 0 ] } ) ;
257+ const totalWidth = Array . from ( widths . values ( ) ) . reduce ( ( sum , w ) => sum + w , 0 ) ;
258+ ui . div ( { text : '─' . repeat ( Math . min ( totalWidth , termWidth ) ) , padding : [ 0 , 0 , 0 , 0 ] } ) ;
123259
124260 // Rows
125261 for ( const sandbox of sandboxes ) {
126- const createdAt = sandbox . createdAt ? new Date ( sandbox . createdAt ) . toISOString ( ) . slice ( 0 , 10 ) : '-' ;
127- const eol = sandbox . eol ? new Date ( sandbox . eol ) . toISOString ( ) . slice ( 0 , 10 ) : '-' ;
128-
129- ui . div (
130- { text : sandbox . realm || '-' , width : 8 , padding : [ 0 , 1 , 0 , 0 ] } ,
131- { text : sandbox . instance || '-' , width : 10 , padding : [ 0 , 1 , 0 , 0 ] } ,
132- { text : sandbox . state || '-' , width : 12 , padding : [ 0 , 1 , 0 , 0 ] } ,
133- { text : sandbox . resourceProfile || '-' , width : 10 , padding : [ 0 , 1 , 0 , 0 ] } ,
134- { text : createdAt , width : 12 , padding : [ 0 , 1 , 0 , 0 ] } ,
135- { text : eol , width : 12 , padding : [ 0 , 1 , 0 , 0 ] } ,
136- { text : sandbox . id || '-' } ,
137- ) ;
262+ const rowCols = columnKeys . map ( ( key ) => {
263+ const col = COLUMNS [ key ] ;
264+ return {
265+ text : col . get ( sandbox ) ,
266+ width : widths . get ( key ) ,
267+ padding : [ 0 , 1 , 0 , 0 ] as [ number , number , number , number ] ,
268+ } ;
269+ } ) ;
270+ ui . div ( ...rowCols ) ;
138271 }
139272
140273 ux . stdout ( ui . toString ( ) ) ;
0 commit comments