From 89c0b3ab16ab90846fbde546901ee6cce357ce95 Mon Sep 17 00:00:00 2001 From: Matt Nappo Date: Mon, 3 Nov 2025 23:16:44 +0000 Subject: [PATCH] Add filter on sf nodes list --- src/lib/nodes/list.tsx | 46 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/lib/nodes/list.tsx b/src/lib/nodes/list.tsx index a28986e..037cee3 100644 --- a/src/lib/nodes/list.tsx +++ b/src/lib/nodes/list.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Command } from "@commander-js/extra-typings"; +import { Command, Option } from "@commander-js/extra-typings"; import { brightBlack, gray } from "jsr:@std/fmt/colors"; import console from "node:console"; import ora from "ora"; @@ -32,6 +32,17 @@ dayjs.extend(utc); dayjs.extend(advanced); dayjs.extend(timezone); +// Valid node status values for filtering +const VALID_STATES = [ + "pending", + "awaitingcapacity", + "running", + "released", + "failed", + "terminated", + "deleted", +] as const; + // Helper component to display VMs in a table format using Ink function VMTable({ vms }: { vms: NonNullable["data"] }) { const sortedVms = vms.sort((a, b) => b.updated_at - a.updated_at); @@ -455,12 +466,18 @@ async function listNodesAction(options: ReturnType) { spinner.stop(); + // Apply optional state filter if provided + const selectedStates = (options as unknown as { state?: string[] }).state; + const filteredNodes = Array.isArray(selectedStates) && selectedStates.length > 0 + ? nodes.filter((n) => selectedStates.includes(n.status)) + : nodes; + if (options.json) { - console.log(JSON.stringify(nodes, null, 2)); + console.log(JSON.stringify(filteredNodes, null, 2)); return; } - if (nodes.length === 0) { + if (filteredNodes.length === 0) { console.log("No nodes found."); console.log(gray("\nCreate your first node:")); console.log(" sf nodes create my-first-node"); @@ -468,13 +485,13 @@ async function listNodesAction(options: ReturnType) { } if (options.verbose) { - render(); + render(); } else { - console.log(createNodesTable(nodes)); + console.log(createNodesTable(filteredNodes)); console.log( gray( - `\nFound ${nodes.length} ${ - pluralizeNodes(nodes.length) + `\nFound ${filteredNodes.length} ${ + pluralizeNodes(filteredNodes.length) }. Use --verbose for detailed information, such as previous virtual machines.`, ), ); @@ -484,7 +501,7 @@ async function listNodesAction(options: ReturnType) { const seenLabels = new Set(); // Sort nodes by created_at (newest first), fallback to index for consistent ordering - const sortedNodes = [...nodes].sort((a, b) => { + const sortedNodes = [...filteredNodes].sort((a, b) => { const aTime = a.created_at || 0; const bTime = b.created_at || 0; return bTime - aTime; // Newest first @@ -521,6 +538,16 @@ const list = new Command("list") .description("List all compute nodes") .showHelpAfterError() .option("--verbose", "Show detailed information for each node") + .addOption( + new Option("--state ", "Filter by node status") + .choices([...VALID_STATES] as unknown as string[]) + .argParser((value: (typeof VALID_STATES)[number], previous?: string[]) => { + const acc = previous ?? []; + acc.push(value); + return acc; + }) + .default([] as string[]), + ) .addOption(jsonOption) .addHelpText( "after", @@ -532,6 +559,9 @@ Next Steps:\n \x1b[2m# List all nodes with detailed information\x1b[0m $ sf nodes list --verbose + \x1b[2m# List pending or running nodes\x1b[0m + $ sf nodes list --state pending --state running + \x1b[2m# List nodes in JSON format\x1b[0m $ sf nodes list --json `,