diff --git a/src/lib/contracts/ContractDisplay.tsx b/src/lib/contracts/ContractDisplay.tsx index 61ddfc4..a64f397 100644 --- a/src/lib/contracts/ContractDisplay.tsx +++ b/src/lib/contracts/ContractDisplay.tsx @@ -140,7 +140,7 @@ export function ContractDisplay(props: { contract: Contract }) { {intervalData.map((data, index) => { return ( {index === 0 && ( diff --git a/src/lib/images/get.tsx b/src/lib/images/get.tsx index 6dae00c..854981a 100644 --- a/src/lib/images/get.tsx +++ b/src/lib/images/get.tsx @@ -26,7 +26,7 @@ function ImageDisplay({ upload_status: string; sha256_hash: string | null; }; - download: { download_url: string; expires_at: number } | null; + download: { url: string; expires_at: number } | null; }) { const expiresAt = download?.expires_at ? new Date(download.expires_at * 1000) @@ -51,8 +51,8 @@ function ImageDisplay({ value={ Use curl or wget to download. - - {download.download_url} + + {download.url} } @@ -116,11 +116,11 @@ const get = new Command("get") // Fetch download URL if image is completed let download = null; if (image.upload_status === "completed") { - const { data: downloadData } = await client.GET( - "/v2/images/{id}/download", - { params: { path: { id } } }, - ); - if (downloadData) { + const { data: downloadData, response: downloadResponse } = + await client.GET("/v2/images/{id}/download", { + params: { path: { id } }, + }); + if (downloadResponse.ok && downloadData) { download = downloadData; } } diff --git a/src/lib/nodes/image/show.tsx b/src/lib/nodes/image/show.tsx index 9456fe8..733e32e 100644 --- a/src/lib/nodes/image/show.tsx +++ b/src/lib/nodes/image/show.tsx @@ -1,6 +1,5 @@ import console from "node:console"; import { Command } from "@commander-js/extra-typings"; -import type SFCNodes from "@sfcompute/nodes-sdk-alpha"; import chalk from "chalk"; import dayjs from "dayjs"; import advanced from "dayjs/plugin/advancedFormat"; @@ -8,8 +7,9 @@ import timezone from "dayjs/plugin/timezone"; import utc from "dayjs/plugin/utc"; import { Box, render, Text } from "ink"; import Link from "ink-link"; +import { apiClient } from "../../../apiClient.ts"; +import { logAndQuit } from "../../../helpers/errors.ts"; import { formatDate } from "../../../helpers/format-time.ts"; -import { handleNodesError, nodesClient } from "../../../nodesClient.ts"; import { Row } from "../../Row.tsx"; dayjs.extend(utc); @@ -18,20 +18,25 @@ dayjs.extend(timezone); export function ImageDisplay({ image, + download, }: { - image: SFCNodes.VMs.ImageGetResponse; + image: { + name: string; + id: string; + upload_status: string; + }; + download: { url: string; expires_at: number } | null; }) { - const expiresAt = image.expires_at ? new Date(image.expires_at) : null; + const expiresAt = download?.expires_at + ? new Date(download.expires_at * 1000) + : null; const isExpired = expiresAt ? expiresAt < new Date() : false; - const statusColor = isExpired ? "red" : "green"; - const statusText = isExpired ? "Expired" : "Ready"; - return ( - Image: {image.name} ({image.image_id}) + Image: {image.name} ({image.id}) @@ -40,62 +45,113 @@ export function ImageDisplay({ head="Status: " value={ - {statusText} + + {formatStatusText(image.upload_status)} + } /> - - Use curl or wget to download. - - {image.download_url} - - - } - /> - {expiresAt && ( - - - {expiresAt.toISOString()}{" "} - {chalk.blackBright( - `(${formatDate(dayjs(expiresAt).toDate())} ${dayjs( - expiresAt, - ).format("z")})`, - )} - - {isExpired && (Expired)} - - } - /> + {download && ( + <> + + Use curl or wget to download. + + {download.url} + + + } + /> + {expiresAt && ( + + + {expiresAt.toISOString()}{" "} + {chalk.blackBright( + `(${formatDate(dayjs(expiresAt).toDate())} ${dayjs( + expiresAt, + ).format("z")})`, + )} + + {isExpired && (Expired)} + + } + /> + )} + )} ); } +function formatStatusColor(status: string): string { + switch (status) { + case "started": + return "green"; + case "uploading": + return "yellow"; + case "completed": + return "cyan"; + case "failed": + return "red"; + default: + return "gray"; + } +} + +function formatStatusText(status: string): string { + switch (status) { + case "started": + return "Started"; + case "uploading": + return "Uploading"; + case "completed": + return "Completed"; + case "failed": + return "Failed"; + default: + return "Unknown"; + } +} + const show = new Command("show") .description("Show VM image details and download URL") .argument("", "ID of the image") .option("--json", "Output JSON") .action(async (imageId, opts) => { - try { - const client = await nodesClient(); - const data = await client.vms.images.get(imageId); + const client = await apiClient(); - if (opts.json) { - console.log(JSON.stringify(data, null, 2)); - return; + const { data: image, response } = await client.GET("/v2/images/{id}", { + params: { path: { id: imageId } }, + }); + if (!response.ok || !image) { + logAndQuit( + `Failed to get image: ${response.status} ${response.statusText}`, + ); + } + + let download = null; + if (image.upload_status === "completed") { + const { data: downloadData, response: downloadResponse } = + await client.GET("/v2/images/{id}/download", { + params: { path: { id: imageId } }, + }); + if (downloadResponse.ok && downloadData) { + download = downloadData; } + } - render(); - } catch (err) { - handleNodesError(err); + if (opts.json) { + console.log(JSON.stringify({ ...image, download }, null, 2)); + return; } + + render(); }); export default show; diff --git a/src/schema.ts b/src/schema.ts index d567aa8..0c80087 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -2204,7 +2204,7 @@ export interface components { */ vmorch_ImageDiscriminator: "image"; vmorch_ImageDownloadResponse: { - download_url: string; + url: string; expires_at: components["schemas"]["vmorch_UnixEpoch"]; sha256_hash: string; /** Format: u-int64 */