@@ -150,11 +152,45 @@ export default function EnvironmentCard({
);
}
+function GlobalEnvironmentSessionImageBadge({
+ launcher,
+}: {
+ launcher: SessionLauncher;
+}) {
+ const environment = launcher.environment;
+ const { data, isLoading } = useGetSessionsImagesQuery(
+ environment && environment.container_image
+ ? { imageUrl: environment.container_image }
+ : skipToken
+ );
+ const { data: resourcePools, isLoading: isLoadingResourcePools } =
+ computeResourcesApi.endpoints.getResourcePools.useQueryState({});
+ const resourcePool = useMemo(() => {
+ if (launcher?.resource_class_id == null || resourcePools == null) {
+ return undefined;
+ }
+ return resourcePools.find(({ classes }) =>
+ classes.some(({ id }) => id === launcher.resource_class_id)
+ );
+ }, [launcher?.resource_class_id, resourcePools]);
+
+ return (
+
-
+ {showImageBadge && (
+
+ )}
{!isLoading && data?.accessible === false && (
{!data.connection && !data.provider ? (
@@ -336,6 +391,23 @@ function CustomBuildEnvironmentValues({
}
);
+ const { data: imageCheck, isLoading: isLoadingContainerImage } =
+ useGetSessionsImagesQuery(
+ environment.container_image != null
+ ? { imageUrl: environment.container_image }
+ : skipToken
+ );
+ const { data: resourcePools, isLoading: isLoadingResourcePools } =
+ computeResourcesApi.endpoints.getResourcePools.useQueryState({});
+ const resourcePool = useMemo(() => {
+ if (launcher?.resource_class_id == null || resourcePools == null) {
+ return undefined;
+ }
+ return resourcePools.find(({ classes }) =>
+ classes.some(({ id }) => id === launcher.resource_class_id)
+ );
+ }, [launcher?.resource_class_id, resourcePools]);
+
// Invalidate launchers if the container image is not the same as the
// image from the last successful build
const dispatch = useAppDispatch();
@@ -368,7 +440,12 @@ function CustomBuildEnvironmentValues({
) : (
<>
-
+
{lastSuccessfulBuild && (
-
+
)}
@@ -505,25 +582,6 @@ function EnvironmentJSONArrayRowWithLabel({
);
}
-function ReadyStatusBadge() {
- return (
-
-
- Ready
-
- );
-}
-
function NotReadyStatusBadge() {
return (
{
+ if (imageCheck == null || resourcePool == null) {
+ return "unknown";
+ }
+ return isImageCompatibleWith(imageCheck, resourcePool.platform);
+ }, [imageCheck, resourcePool]);
+
const badgeIcon =
- status === "in_progress" ? (
+ buildStatus === "in_progress" ? (
) : (
);
const badgeText =
- status === "in_progress"
+ isCompatible === false
+ ? "Image incompatible"
+ : buildStatus === "in_progress"
? "Build in progress"
- : status === "cancelled"
+ : buildStatus === "cancelled"
? "Build cancelled"
- : status === "succeeded"
+ : buildStatus === "succeeded"
? "Build succeeded"
: "Build failed";
const badgeColorClasses =
- status === "in_progress"
+ isCompatible === false
+ ? ["border-danger", "bg-danger-subtle", "text-danger-emphasis"]
+ : buildStatus === "in_progress"
? ["border-warning", "bg-warning-subtle", "text-warning-emphasis"]
- : status === "succeeded"
+ : buildStatus === "succeeded"
? ["border-success", "bg-success-subtle", "text-success-emphasis"]
: ["border-danger", "bg-danger-subtle", "text-danger-emphasis"];
diff --git a/client/src/features/sessionsV2/components/SessionStatus/SessionImageBadge.tsx b/client/src/features/sessionsV2/components/SessionStatus/SessionImageBadge.tsx
index 7f2eaead09..6f9e3820e8 100644
--- a/client/src/features/sessionsV2/components/SessionStatus/SessionImageBadge.tsx
+++ b/client/src/features/sessionsV2/components/SessionStatus/SessionImageBadge.tsx
@@ -17,26 +17,43 @@
*/
import cx from "classnames";
+import { useMemo } from "react";
import { CircleFill } from "react-bootstrap-icons";
import { Loader } from "~/components/Loader";
import RenkuBadge from "~/components/renkuBadge/RenkuBadge";
-import { ImageCheckResponse } from "../../api/sessionsV2.generated-api";
+import type { ResourcePoolWithId } from "../../api/computeResources.api";
+import type { ImageCheckResponse } from "../../api/sessionsV2.api";
+import { isImageCompatibleWith } from "../../session.utils";
interface SessionImageBadgeProps {
data?: ImageCheckResponse | null;
- loading: boolean;
+ isLoading: boolean;
+
+ resourcePool?: ResourcePoolWithId;
+ isLoadingResourcePools?: boolean;
}
export default function SessionImageBadge({
data,
- loading,
+ isLoading,
+ resourcePool,
+ isLoadingResourcePools,
}: SessionImageBadgeProps) {
+ const isCompatible = useMemo(() => {
+ if (data == null || resourcePool == null) {
+ return "unknown";
+ }
+ return isImageCompatibleWith(data, resourcePool.platform);
+ }, [data, resourcePool]);
+
return (
- {loading ? (
+ {isLoading || isLoadingResourcePools ? (
<>
Checking image status.
@@ -55,7 +72,11 @@ export default function SessionImageBadge({
) : (
<>
- {data?.accessible
+ {isCompatible === false
+ ? `Image incompatible${
+ resourcePool?.platform ? ` with ${resourcePool.platform}` : ""
+ }`
+ : data?.accessible
? "Image accessible"
: data?.provider?.id &&
(!data?.connection || data?.connection?.status !== "connected")
diff --git a/client/src/features/sessionsV2/session.utils.ts b/client/src/features/sessionsV2/session.utils.ts
index 00ea9d3130..a0577a9673 100644
--- a/client/src/features/sessionsV2/session.utils.ts
+++ b/client/src/features/sessionsV2/session.utils.ts
@@ -18,12 +18,14 @@
import { FaviconStatus } from "../display/display.types";
import { SessionStatusState } from "../session/sessions.types";
+import type { ResourcePoolWithId } from "./api/computeResources.api";
import type {
EnvironmentList as SessionEnvironmentList,
SessionLauncher,
SessionLauncherEnvironmentParams,
SessionLauncherEnvironmentPatchParams,
} from "./api/sessionLaunchersV2.api";
+import type { ImageCheckResponse } from "./api/sessionsV2.api";
import {
BUILDER_PLATFORMS,
DEFAULT_URL,
@@ -402,3 +404,16 @@ export function validateEnvVariableName(name: string): true | string {
}
return true;
}
+
+export function isImageCompatibleWith(
+ image: ImageCheckResponse,
+ platform: ResourcePoolWithId["platform"]
+): boolean | "unknown" {
+ if (image.platforms == null) {
+ return "unknown";
+ }
+ const imagePlatforms = image.platforms?.map(
+ ({ os, architecture }) => `${os}/${architecture}`
+ );
+ return imagePlatforms.some((p) => p === platform);
+}