Skip to content

Commit e350147

Browse files
committed
better display
1 parent 5135ac5 commit e350147

1 file changed

Lines changed: 159 additions & 26 deletions

File tree

  • packages/b2c-cli/src/commands/ods

packages/b2c-cli/src/commands/ods/list.ts

Lines changed: 159 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)