Skip to content

Commit 3a92a08

Browse files
feat: [PRODUCT-608], [ENG-2210] add filter on sf nodes list (#230)
Co-authored-by: Matt Nappo <matt@modal.com>
1 parent 3ce495d commit 3a92a08

2 files changed

Lines changed: 68 additions & 11 deletions

File tree

src/lib/nodes/list.tsx

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from "react";
2-
import { Command } from "@commander-js/extra-typings";
2+
import { Command, Option } from "@commander-js/extra-typings";
33
import { brightBlack, gray } from "jsr:@std/fmt/colors";
44
import console from "node:console";
55
import ora from "ora";
@@ -20,6 +20,7 @@ import { handleNodesError, nodesClient } from "../../nodesClient.ts";
2020
import { Row } from "../Row.tsx";
2121
import {
2222
createNodesTable,
23+
DEFAULT_NODE_LS_LIMIT,
2324
getLastVM,
2425
getStatusColor,
2526
getVMStatusColor,
@@ -32,6 +33,16 @@ dayjs.extend(utc);
3233
dayjs.extend(advanced);
3334
dayjs.extend(timezone);
3435

36+
// Valid node status values for filtering
37+
const VALID_STATES = [
38+
"pending",
39+
"awaitingcapacity",
40+
"running",
41+
"released",
42+
"failed",
43+
"terminated",
44+
] as const;
45+
3546
// Helper component to display VMs in a table format using Ink
3647
function VMTable({ vms }: { vms: NonNullable<SFCNodes.Node["vms"]>["data"] }) {
3748
const sortedVms = vms.sort((a, b) => b.updated_at - a.updated_at);
@@ -447,27 +458,36 @@ async function listNodesAction(options: ReturnType<typeof list.opts>) {
447458

448459
spinner.stop();
449460

461+
const filteredNodes = (options.status?.length)
462+
? nodes.filter((n) =>
463+
options.status?.length &&
464+
options.status.includes(n.status)
465+
)
466+
: nodes;
467+
450468
if (options.json) {
451-
console.log(JSON.stringify(nodes, null, 2));
469+
console.log(JSON.stringify(filteredNodes, null, 2));
452470
return;
453471
}
454472

455-
if (nodes.length === 0) {
473+
if (filteredNodes.length === 0) {
456474
console.log("No nodes found.");
457475
console.log(gray("\nCreate your first node:"));
458476
console.log(" sf nodes create my-first-node");
459477
return;
460478
}
461479

462480
if (options.verbose) {
463-
render(<NodesVerboseDisplay nodes={nodes} />);
481+
render(
482+
<NodesVerboseDisplay nodes={filteredNodes.slice(0, options.limit)} />,
483+
);
464484
} else {
465-
console.log(createNodesTable(nodes));
485+
console.log(createNodesTable(filteredNodes, options.limit));
466486
console.log(
467487
gray(
468-
`\nFound ${nodes.length} ${
469-
pluralizeNodes(nodes.length)
470-
}. Use --verbose for detailed information, such as previous virtual machines.`,
488+
`\nFound ${filteredNodes.length} ${
489+
pluralizeNodes(filteredNodes.length)
490+
} total. Use --verbose for detailed information, such as previous virtual machines.`,
471491
),
472492
);
473493

@@ -476,7 +496,7 @@ async function listNodesAction(options: ReturnType<typeof list.opts>) {
476496
const seenLabels = new Set<string>();
477497

478498
// Sort nodes by created_at (newest first), fallback to index for consistent ordering
479-
const sortedNodes = [...nodes].sort((a, b) => {
499+
const sortedNodes = [...filteredNodes].sort((a, b) => {
480500
const aTime = a.created_at || 0;
481501
const bTime = b.created_at || 0;
482502
return bTime - aTime; // Newest first
@@ -513,6 +533,16 @@ const list = new Command("list")
513533
.description("List all compute nodes")
514534
.showHelpAfterError()
515535
.option("--verbose", "Show detailed information for each node")
536+
.option(
537+
"--limit <number>",
538+
"Limit the number of nodes to display",
539+
Number.parseInt,
540+
DEFAULT_NODE_LS_LIMIT,
541+
)
542+
.addOption(
543+
new Option("--status <status...>", "Filter by node status")
544+
.choices(VALID_STATES as (readonly SFCNodes.Status[])),
545+
)
516546
.addOption(jsonOption)
517547
.addHelpText(
518548
"after",
@@ -524,6 +554,12 @@ Next Steps:\n
524554
\x1b[2m# List all nodes with detailed information\x1b[0m
525555
$ sf nodes list --verbose
526556
557+
\x1b[2m# List up to 100 nodes\x1b[0m
558+
$ sf nodes list --limit 100
559+
560+
\x1b[2m# List pending or running nodes\x1b[0m
561+
$ sf nodes list --status pending running
562+
527563
\x1b[2m# List nodes in JSON format\x1b[0m
528564
$ sf nodes list --json
529565
`,

src/lib/nodes/utils.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,18 @@ export function getLastVM(node: SFCNodes.Node) {
9595
).at(0);
9696
}
9797

98+
export const DEFAULT_NODE_LS_LIMIT = 12 as const;
99+
98100
/**
99101
* Creates a formatted table display of nodes
100102
* @param nodes Array of nodes to display
103+
* @param limit Optional limit on number of nodes to display (default: show all)
101104
* @returns Formatted table string
102105
*/
103-
export function createNodesTable(nodes: SFCNodes.Node[]): string {
106+
export function createNodesTable(
107+
nodes: SFCNodes.Node[],
108+
limit: number = DEFAULT_NODE_LS_LIMIT,
109+
): string {
104110
const table = new Table({
105111
head: [
106112
cyan("NAME"),
@@ -118,7 +124,9 @@ export function createNodesTable(nodes: SFCNodes.Node[]): string {
118124
},
119125
});
120126

121-
for (const node of nodes) {
127+
const nodesToShow = limit ? nodes.slice(0, limit) : nodes;
128+
129+
for (const node of nodesToShow) {
122130
const startDate = node.start_at ? dayjs.unix(node.start_at) : null;
123131
const endDate = node.end_at ? dayjs.unix(node.end_at) : null;
124132

@@ -142,6 +150,19 @@ export function createNodesTable(nodes: SFCNodes.Node[]): string {
142150
]);
143151
}
144152

153+
if (limit && nodes.length > limit) {
154+
table.push([
155+
{
156+
colSpan: 8,
157+
content: brightBlack(
158+
`${nodes.length - limit} older ${
159+
pluralizeNodes(nodes.length - limit)
160+
} not shown. Use sf nodes list --limit ${nodes.length} or sf nodes list --json to list all nodes.`,
161+
),
162+
},
163+
]);
164+
}
165+
145166
return table.toString();
146167
}
147168

0 commit comments

Comments
 (0)