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 */