Skip to content

fix: guard against undefined data volumes in workspace details view#1268

Open
christian-heusel wants to merge 1 commit into
kubeflow:notebooks-v2from
christian-heusel:fix/workspace-details-data-volume-crash
Open

fix: guard against undefined data volumes in workspace details view#1268
christian-heusel wants to merge 1 commit into
kubeflow:notebooks-v2from
christian-heusel:fix/workspace-details-data-volume-crash

Conversation

@christian-heusel

@christian-heusel christian-heusel commented Jul 21, 2026

Copy link
Copy Markdown
Member

The details drawer crashed for any Workspace with no data volume: The
backend omits podTemplate.volumes.data from the JSON response
(omitempty) in that case, but the frontend called .map() on it
unguarded. Fall back to an empty array, matching the pattern already
used for the home volume.

Related to: #1119
Related-to: #1253


The alternative here is to regenerate the frontend client to be aware of the omitempty, however this currently comes with a lot of unrelated changes (even when going for the minimal bump to 3f1d7ec (which does not yet include the secrets changes)):

click to show diff
diff --git a/workspaces/frontend/scripts/swagger.version b/workspaces/frontend/scripts/swagger.version
index e2ca7676..7a1a1d5b 100644
--- a/workspaces/frontend/scripts/swagger.version
+++ b/workspaces/frontend/scripts/swagger.version
@@ -1 +1 @@
-142a30f072f36b814f2bb39b5c6ed2b9bfc390c6
+3f1d7ec5c99674dea0f3aa68b71c470934268153
diff --git a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/yamlEditor/workspaceKindUpdateSchema.json b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/yamlEditor/workspaceKindUpdateSchema.json
index 4debcdc9..6a1ec25e 100644
--- a/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/yamlEditor/workspaceKindUpdateSchema.json
+++ b/workspaces/frontend/src/app/pages/WorkspaceKinds/Form/yamlEditor/workspaceKindUpdateSchema.json
@@ -22,24 +22,24 @@
       "type": "object",
       "required": ["options", "ports", "serviceAccount", "volumeMounts"],
       "properties": {
-        "containerSecurityContext": {
-          "description": "container security context for Workspace Pods (MUTABLE)\n+kubebuilder:validation:Optional",
+        "activityProbe": {
+          "description": "activityProbe configs to determine Workspace activity (MUTABLE)\n+kubebuilder:validation:Optional",
           "allOf": [
             {
-              "$ref": "#/$defs/v1.SecurityContext"
+              "$ref": "#/$defs/v1beta1.ActivityProbe"
             }
           ]
         },
-        "culling": {
-          "description": "culling configs for pausing inactive Workspaces (MUTABLE)\n+kubebuilder:validation:Optional",
+        "containerSecurityContext": {
+          "description": "container security context for Workspace Pods (MUTABLE)\n+kubebuilder:validation:Optional",
           "allOf": [
             {
-              "$ref": "#/$defs/v1beta1.WorkspaceKindCullingConfig"
+              "$ref": "#/$defs/v1.SecurityContext"
             }
           ]
         },
         "extraEnv": {
-          "description": "environment variables for Workspace Pods (MUTABLE)\n - the following go template functions are available:\n    - `httpPathPrefix(portId string)`: returns the HTTP path prefix of the specified port\n+kubebuilder:validation:Optional\n+listType:=\"map\"\n+listMapKey:=\"name\"",
+          "description": "environment variables for Workspace Pods (MUTABLE)\n - the following go template functions are available:\n    - `httpPathPrefix(portId string)`: returns the HTTP path prefix of the specified port\n+kubebuilder:validation:Optional\n+kubebuilder:example:={ \"NB_PREFIX\": \"{{ httpPathPrefix 'jupyterlab' }}\" }\n+listType:=\"map\"\n+listMapKey:=\"name\"",
           "type": "array",
           "items": {
             "$ref": "#/$defs/v1.EnvVar"
@@ -116,6 +116,63 @@
         }
       }
     },
+    "v1beta1.ActivityProbe": {
+      "type": "object",
+      "properties": {
+        "jupyter": {
+          "description": "a Jupyter-specific API probe\n+kubebuilder:validation:Optional",
+          "allOf": [
+            {
+              "$ref": "#/$defs/v1beta1.ActivityProbeJupyter"
+            }
+          ]
+        },
+        "minProbeIntervalSeconds": {
+          "description": "the minimum duration in seconds that must elapse between two consecutive probes.\n- Acts as a rate-limiter for failed probes: if a probe fails, the controller waits at least this long before retrying (requeuing after minProbeInterval).\n- Also acts as a guard: if a reconcile triggers early, the probe is skipped until this interval has elapsed since the last probe.\n+kubebuilder:validation:Minimum:=1\n+kubebuilder:default:=300\n+kubebuilder:validation:Optional",
+          "type": "integer"
+        },
+        "podExec": {
+          "description": "a script-based probe executed in the Pod\n+kubebuilder:validation:Optional",
+          "allOf": [
+            {
+              "$ref": "#/$defs/v1beta1.ActivityProbePodExec"
+            }
+          ]
+        },
+        "probeIntervalSeconds": {
+          "description": "the desired interval in seconds between successful probes.\n- If a probe succeeds, the controller schedules the next probe after this duration (requeuing after probeInterval).\n- Determines the freshness of workspace activity status used for culling inactive workspaces.\n+kubebuilder:validation:Minimum:=1\n+kubebuilder:default:=3600\n+kubebuilder:validation:Optional",
+          "type": "integer"
+        }
+      }
+    },
+    "v1beta1.ActivityProbeJupyter": {
+      "type": "object",
+      "required": ["lastActivity", "portId"],
+      "properties": {
+        "lastActivity": {
+          "description": "if the Jupyter-specific probe is enabled\n+kubebuilder:example=true",
+          "type": "boolean"
+        },
+        "portId": {
+          "description": "the port to probe, referencing a port defined in spec.podTemplate.ports",
+          "type": "string"
+        }
+      }
+    },
+    "v1beta1.ActivityProbePodExec": {
+      "type": "object",
+      "required": ["script"],
+      "properties": {
+        "script": {
+          "description": "script is the script to run inside the Pod to determine if the Workspace is active.\nThe script must meet the following requirements:\n - It must start with a shebang (e.g., \"#!/usr/bin/env bash\" or \"#!/usr/bin/env python\").\n - It must exit with a 0 status code. A non-zero exit code is treated as a probe failure (Workspaces with failing probes are not culled).\n - It should be idempotent and without side effects since it can be run multiple times.\n - If the script wants to report an INACTIVE state, it MUST write a JSON object to the file path\n   supplied in the OUTPUT_JSON_PATH environment variable. The fields are evaluated to update the\n   Workspace status field `status.activity.lastActivity` as follows:\n     - If `has_activity` is explicitly set to `true` (or if the JSON file is empty/omitted): The Workspace is treated as active, and `status.activity.lastActivity` is updated to the probe completion time (ignoring `last_activity`).\n     - If `last_activity` (ISO 8601 string) is provided and `has_activity` is explicitly `false` (or omitted): The Workspace is treated as inactive, and `status.activity.lastActivity` is updated to the `last_activity` timestamp.\n     - If `has_activity` is explicitly `false` and `last_activity` is omitted: The Workspace is treated as inactive, and the existing `status.activity.lastActivity` timestamp is preserved (unchanged).\n+kubebuilder:validation:MinLength:=1\n+kubebuilder:validation:MaxLength:=2048",
+          "type": "string"
+        },
+        "timeoutSeconds": {
+          "description": "the maximum number of seconds the probe is allowed to run\n+kubebuilder:validation:Minimum:=1\n+kubebuilder:default:=60\n+kubebuilder:validation:Optional",
+          "type": "integer"
+        }
+      }
+    },
     "v1.SecurityContext": {
       "type": "object",
       "properties": {
@@ -313,72 +370,6 @@
         }
       }
     },
-    "v1beta1.WorkspaceKindCullingConfig": {
-      "type": "object",
-      "required": ["activityProbe"],
-      "properties": {
-        "activityProbe": {
-          "description": "the probe used to determine if the Workspace is active",
-          "allOf": [
-            {
-              "$ref": "#/$defs/v1beta1.ActivityProbe"
-            }
-          ]
-        },
-        "enabled": {
-          "description": "if the culling feature is enabled\n+kubebuilder:validation:Optional\n+kubebuilder:default=true",
-          "type": "boolean"
-        },
-        "maxInactiveSeconds": {
-          "description": "the maximum number of seconds a Workspace can be inactive\n+kubebuilder:validation:Optional\n+kubebuilder:validation:Minimum:=60\n+kubebuilder:default=86400",
-          "type": "integer"
-        }
-      }
-    },
-    "v1beta1.ActivityProbe": {
-      "type": "object",
-      "properties": {
-        "exec": {
-          "description": "a shell command probe\n - if the Workspace had activity in the last 60 seconds this command\n   should return status 0, otherwise it should return status 1\n+kubebuilder:validation:Optional",
-          "allOf": [
-            {
-              "$ref": "#/$defs/v1beta1.ActivityProbeExec"
-            }
-          ]
-        },
-        "jupyter": {
-          "description": "a Jupyter-specific probe\n - will poll the `/api/status` endpoint of the Jupyter API, and use the `last_activity` field\n - note, users need to be careful that their other probes don't trigger a \"last_activity\" update\n   e.g. they should only check the health of Jupyter using the `/api/status` endpoint\n+kubebuilder:validation:Optional",
-          "allOf": [
-            {
-              "$ref": "#/$defs/v1beta1.ActivityProbeJupyter"
-            }
-          ]
-        }
-      }
-    },
-    "v1beta1.ActivityProbeExec": {
-      "type": "object",
-      "required": ["command"],
-      "properties": {
-        "command": {
-          "description": "the command to run\n+kubebuilder:validation:MinItems:=1\n+kubebuilder:example={\"bash\", \"-c\", \"exit 0\"}",
-          "type": "array",
-          "items": {
-            "type": "string"
-          }
-        }
-      }
-    },
-    "v1beta1.ActivityProbeJupyter": {
-      "type": "object",
-      "required": ["lastActivity"],
-      "properties": {
-        "lastActivity": {
-          "description": "if the Jupyter-specific probe is enabled\n+kubebuilder:example=true",
-          "type": "boolean"
-        }
-      }
-    },
     "v1.EnvVar": {
       "type": "object",
       "required": ["name"],
@@ -2923,7 +2914,7 @@
           "type": "boolean"
         },
         "requestHeaders": {
-          "description": "header manipulation rules for incoming HTTP requests\n - sets the `spec.http[].headers.request` of the Istio VirtualService\n   https://istio.io/latest/docs/reference/config/networking/virtual-service/#Headers-HeaderOperations\n - the following string templates are available:\n    - `.PathPrefix`: the path prefix of the Workspace (e.g. '/workspace/connect/{profile_name}/{workspace_name}/')\n+kubebuilder:validation:Optional",
+          "description": "header manipulation rules for incoming HTTP requests\n - sets the `spec.http[].headers.request` of the Istio VirtualService\n   https://istio.io/latest/docs/reference/config/networking/virtual-service/#Headers-HeaderOperations\n+kubebuilder:validation:Optional",
           "allOf": [
             {
               "$ref": "#/$defs/v1beta1.IstioHeaderOperations"
@@ -2936,7 +2927,7 @@
       "type": "object",
       "properties": {
         "add": {
-          "description": "append the given values to the headers specified by keys (will create a comma-separated list of values)\n+kubebuilder:validation:Optional\n+kubebuilder:example:={ \"My-Header\": \"value-to-append\" }",
+          "description": "append the given values to the headers specified by keys (will create a comma-separated list of values)\n - the following go template functions are available in the values:\n    - `httpPathPrefix(portId string)`: returns the HTTP path prefix of the specified port\n+kubebuilder:validation:Optional\n+kubebuilder:example:={ \"My-Header\": \"value-to-append\" }",
           "type": "object",
           "additionalProperties": {
             "type": "string"
@@ -2950,7 +2941,7 @@
           }
         },
         "set": {
-          "description": "overwrite the headers specified by key with the given values\n+kubebuilder:validation:Optional\n+kubebuilder:example:={ \"X-RStudio-Root-Path\": \"{{ .PathPrefix }}\" }",
+          "description": "overwrite the headers specified by key with the given values\n - the following go template functions are available in the values:\n    - `httpPathPrefix(portId string)`: returns the HTTP path prefix of the specified port\n+kubebuilder:validation:Optional\n+kubebuilder:example:={ \"X-RStudio-Root-Path\": \"{{ httpPathPrefix 'rstudio' }}\" }",
           "type": "object",
           "additionalProperties": {
             "type": "string"
diff --git a/workspaces/frontend/src/generated/Workspaces.ts b/workspaces/frontend/src/generated/Workspaces.ts
index 4f4a2400..31e61baf 100644
--- a/workspaces/frontend/src/generated/Workspaces.ts
+++ b/workspaces/frontend/src/generated/Workspaces.ts
@@ -14,6 +14,7 @@ import {
   ApiErrorEnvelope,
   ApiWorkspaceActionPauseEnvelope,
   ApiWorkspaceCreateEnvelope,
+  ApiWorkspaceDetailsEnvelope,
   ApiWorkspaceEnvelope,
   ApiWorkspaceListEnvelope,
 } from './data-contracts';
@@ -198,4 +199,25 @@ export class Workspaces<SecurityDataType = unknown> extends HttpClient<SecurityD
       format: 'json',
       ...params,
     });
+  /**
+   * @description Returns detail-level data for the workspace details overlay (volumes, secrets, pod info).
+   *
+   * @tags workspaces
+   * @name GetWorkspacePodTemplateDetails
+   * @summary Get workspace pod template details
+   * @request GET:/workspaces/{namespace}/{name}/podtemplate/details
+   * @response `200` `ApiWorkspaceDetailsEnvelope` Successful operation.
+   * @response `401` `ApiErrorEnvelope` Unauthorized.
+   * @response `403` `ApiErrorEnvelope` Forbidden.
+   * @response `404` `ApiErrorEnvelope` Workspace not found.
+   * @response `422` `ApiErrorEnvelope` Unprocessable Entity. Validation error.
+   * @response `500` `ApiErrorEnvelope` Internal server error.
+   */
+  getWorkspacePodTemplateDetails = (namespace: string, name: string, params: RequestParams = {}) =>
+    this.request<ApiWorkspaceDetailsEnvelope, ApiErrorEnvelope>({
+      path: `/workspaces/${namespace}/${name}/podtemplate/details`,
+      method: 'GET',
+      format: 'json',
+      ...params,
+    });
 }
diff --git a/workspaces/frontend/src/generated/data-contracts.ts b/workspaces/frontend/src/generated/data-contracts.ts
index 5d308f12..18836d5a 100644
--- a/workspaces/frontend/src/generated/data-contracts.ts
+++ b/workspaces/frontend/src/generated/data-contracts.ts
@@ -341,6 +341,10 @@ export interface ApiWorkspaceCreateEnvelope {
   data: WorkspacesWorkspaceCreate;
 }
 
+export interface ApiWorkspaceDetailsEnvelope {
+  data: DetailsWorkspaceDetails;
+}
+
 export interface ApiWorkspaceEnvelope {
   data: WorkspacesWorkspaceUpdate;
 }
@@ -374,6 +378,46 @@ export interface CommonAudit {
   updatedBy: string;
 }
 
+export interface CommonPodMetadata {
+  annotations: Record<string, string>;
+  labels: Record<string, string>;
+}
+
+export interface CommonPodSecretInfo {
+  defaultMode?: number;
+  mountPath: string;
+  secretName: string;
+}
+
+export interface CommonPodVolumeInfo {
+  mountPath: string;
+  pvcName: string;
+  readOnly: boolean;
+}
+
+export interface DetailsWorkspaceDetailContainer {
+  name: string;
+}
+
+export interface DetailsWorkspaceDetailPod {
+  containers?: DetailsWorkspaceDetailContainer[];
+  initContainers?: DetailsWorkspaceDetailContainer[];
+  name: string;
+  nodeName: string;
+}
+
+export interface DetailsWorkspaceDetailVolumes {
+  data?: CommonPodVolumeInfo[];
+  home: CommonPodVolumeInfo;
+  secrets?: CommonPodSecretInfo[];
+}
+
+export interface DetailsWorkspaceDetails {
+  pod?: DetailsWorkspaceDetailPod;
+  podMetadata: CommonPodMetadata;
+  volumes: DetailsWorkspaceDetailVolumes;
+}
+
 export interface HealthCheckHealthCheck {
   status: HealthCheckServiceStatus;
   systemInfo: HealthCheckSystemInfo;
@@ -3387,29 +3431,33 @@ export interface V1WindowsSecurityContextOptions {
 
 export interface V1Beta1ActivityProbe {
   /**
-   * a shell command probe
-   *  - if the Workspace had activity in the last 60 seconds this command
-   *    should return status 0, otherwise it should return status 1
+   * a Jupyter-specific API probe
+   * +kubebuilder:validation:Optional
+   */
+  jupyter?: V1Beta1ActivityProbeJupyter;
+  /**
+   * the minimum duration in seconds that must elapse between two consecutive probes.
+   * - Acts as a rate-limiter for failed probes: if a probe fails, the controller waits at least this long before retrying (requeuing after minProbeInterval).
+   * - Also acts as a guard: if a reconcile triggers early, the probe is skipped until this interval has elapsed since the last probe.
+   * +kubebuilder:validation:Minimum:=1
+   * +kubebuilder:default:=300
    * +kubebuilder:validation:Optional
    */
-  exec?: V1Beta1ActivityProbeExec;
+  minProbeIntervalSeconds?: number;
   /**
-   * a Jupyter-specific probe
-   *  - will poll the `/api/status` endpoint of the Jupyter API, and use the `last_activity` field
-   *  - note, users need to be careful that their other probes don't trigger a "last_activity" update
-   *    e.g. they should only check the health of Jupyter using the `/api/status` endpoint
+   * a script-based probe executed in the Pod
    * +kubebuilder:validation:Optional
    */
-  jupyter?: V1Beta1ActivityProbeJupyter;
-}
-
-export interface V1Beta1ActivityProbeExec {
+  podExec?: V1Beta1ActivityProbePodExec;
   /**
-   * the command to run
-   * +kubebuilder:validation:MinItems:=1
-   * +kubebuilder:example={"bash", "-c", "exit 0"}
+   * the desired interval in seconds between successful probes.
+   * - If a probe succeeds, the controller schedules the next probe after this duration (requeuing after probeInterval).
+   * - Determines the freshness of workspace activity status used for culling inactive workspaces.
+   * +kubebuilder:validation:Minimum:=1
+   * +kubebuilder:default:=3600
+   * +kubebuilder:validation:Optional
    */
-  command: string[];
+  probeIntervalSeconds?: number;
 }
 
 export interface V1Beta1ActivityProbeJupyter {
@@ -3418,6 +3466,34 @@ export interface V1Beta1ActivityProbeJupyter {
    * +kubebuilder:example=true
    */
   lastActivity: boolean;
+  /** the port to probe, referencing a port defined in spec.podTemplate.ports */
+  portId: string;
+}
+
+export interface V1Beta1ActivityProbePodExec {
+  /**
+   * script is the script to run inside the Pod to determine if the Workspace is active.
+   * The script must meet the following requirements:
+   *  - It must start with a shebang (e.g., "#!/usr/bin/env bash" or "#!/usr/bin/env python").
+   *  - It must exit with a 0 status code. A non-zero exit code is treated as a probe failure (Workspaces with failing probes are not culled).
+   *  - It should be idempotent and without side effects since it can be run multiple times.
+   *  - If the script wants to report an INACTIVE state, it MUST write a JSON object to the file path
+   *    supplied in the OUTPUT_JSON_PATH environment variable. The fields are evaluated to update the
+   *    Workspace status field `status.activity.lastActivity` as follows:
+   *      - If `has_activity` is explicitly set to `true` (or if the JSON file is empty/omitted): The Workspace is treated as active, and `status.activity.lastActivity` is updated to the probe completion time (ignoring `last_activity`).
+   *      - If `last_activity` (ISO 8601 string) is provided and `has_activity` is explicitly `false` (or omitted): The Workspace is treated as inactive, and `status.activity.lastActivity` is updated to the `last_activity` timestamp.
+   *      - If `has_activity` is explicitly `false` and `last_activity` is omitted: The Workspace is treated as inactive, and the existing `status.activity.lastActivity` timestamp is preserved (unchanged).
+   * +kubebuilder:validation:MinLength:=1
+   * +kubebuilder:validation:MaxLength:=2048
+   */
+  script: string;
+  /**
+   * the maximum number of seconds the probe is allowed to run
+   * +kubebuilder:validation:Minimum:=1
+   * +kubebuilder:default:=60
+   * +kubebuilder:validation:Optional
+   */
+  timeoutSeconds?: number;
 }
 
 export interface V1Beta1HTTPProxy {
@@ -3435,8 +3511,6 @@ export interface V1Beta1HTTPProxy {
    * header manipulation rules for incoming HTTP requests
    *  - sets the `spec.http[].headers.request` of the Istio VirtualService
    *    https://istio.io/latest/docs/reference/config/networking/virtual-service/#Headers-HeaderOperations
-   *  - the following string templates are available:
-   *     - `.PathPrefix`: the path prefix of the Workspace (e.g. '/workspace/connect/{profile_name}/{workspace_name}/')
    * +kubebuilder:validation:Optional
    */
   requestHeaders?: V1Beta1IstioHeaderOperations;
@@ -3524,6 +3598,8 @@ export interface V1Beta1ImagePort {
 export interface V1Beta1IstioHeaderOperations {
   /**
    * append the given values to the headers specified by keys (will create a comma-separated list of values)
+   *  - the following go template functions are available in the values:
+   *     - `httpPathPrefix(portId string)`: returns the HTTP path prefix of the specified port
    * +kubebuilder:validation:Optional
    * +kubebuilder:example:={ "My-Header": "value-to-append" }
    */
@@ -3536,8 +3612,10 @@ export interface V1Beta1IstioHeaderOperations {
   remove?: string[];
   /**
    * overwrite the headers specified by key with the given values
+   *  - the following go template functions are available in the values:
+   *     - `httpPathPrefix(portId string)`: returns the HTTP path prefix of the specified port
    * +kubebuilder:validation:Optional
-   * +kubebuilder:example:={ "X-RStudio-Root-Path": "{{ .PathPrefix }}" }
+   * +kubebuilder:example:={ "X-RStudio-Root-Path": "{{ httpPathPrefix 'rstudio' }}" }
    */
   set?: Record<string, string>;
 }
@@ -3728,24 +3806,6 @@ export interface V1Beta1WorkspaceKindAssetConfigMap {
   namespace: string;
 }
 
-export interface V1Beta1WorkspaceKindCullingConfig {
-  /** the probe used to determine if the Workspace is active */
-  activityProbe: V1Beta1ActivityProbe;
-  /**
-   * if the culling feature is enabled
-   * +kubebuilder:validation:Optional
-   * +kubebuilder:default=true
-   */
-  enabled?: boolean;
-  /**
-   * the maximum number of seconds a Workspace can be inactive
-   * +kubebuilder:validation:Optional
-   * +kubebuilder:validation:Minimum:=60
-   * +kubebuilder:default=86400
-   */
-  maxInactiveSeconds?: number;
-}
-
 export interface V1Beta1WorkspaceKindPodMetadata {
   /**
    * annotations to be applied to the Pod resource
@@ -3768,20 +3828,21 @@ export interface V1Beta1WorkspaceKindPodOptions {
 
 export interface V1Beta1WorkspaceKindPodTemplate {
   /**
-   * container security context for Workspace Pods (MUTABLE)
+   * activityProbe configs to determine Workspace activity (MUTABLE)
    * +kubebuilder:validation:Optional
    */
-  containerSecurityContext?: V1SecurityContext;
+  activityProbe?: V1Beta1ActivityProbe;
   /**
-   * culling configs for pausing inactive Workspaces (MUTABLE)
+   * container security context for Workspace Pods (MUTABLE)
    * +kubebuilder:validation:Optional
    */
-  culling?: V1Beta1WorkspaceKindCullingConfig;
+  containerSecurityContext?: V1SecurityContext;
   /**
    * environment variables for Workspace Pods (MUTABLE)
    *  - the following go template functions are available:
    *     - `httpPathPrefix(portId string)`: returns the HTTP path prefix of the specified port
    * +kubebuilder:validation:Optional
+   * +kubebuilder:example:={ "NB_PREFIX": "{{ httpPathPrefix 'jupyterlab' }}" }
    * +listType:="map"
    * +listMapKey:="name"
    */
@@ -4050,22 +4111,11 @@ export interface WorkspacesPodConfig {
   redirectChain?: WorkspacesRedirectStep[];
 }
 
-export interface WorkspacesPodMetadata {
-  annotations: Record<string, string>;
-  labels: Record<string, string>;
-}
-
 export interface WorkspacesPodMetadataMutate {
   annotations: Record<string, string>;
   labels: Record<string, string>;
 }
 
-export interface WorkspacesPodSecretInfo {
-  defaultMode?: number;
-  mountPath: string;
-  secretName: string;
-}
-
 export interface WorkspacesPodSecretMount {
   defaultMode?: number;
   mountPath: string;
@@ -4074,7 +4124,7 @@ export interface WorkspacesPodSecretMount {
 
 export interface WorkspacesPodTemplate {
   options: WorkspacesPodTemplateOptions;
-  podMetadata: WorkspacesPodMetadata;
+  podMetadata: CommonPodMetadata;
   volumes: WorkspacesPodVolumes;
 }
 
@@ -4094,12 +4144,6 @@ export interface WorkspacesPodTemplateOptionsMutate {
   podConfig: string;
 }
 
-export interface WorkspacesPodVolumeInfo {
-  mountPath: string;
-  pvcName: string;
-  readOnly: boolean;
-}
-
 export interface WorkspacesPodVolumeMount {
   mountPath: string;
   pvcName: string;
@@ -4107,9 +4151,9 @@ export interface WorkspacesPodVolumeMount {
 }
 
 export interface WorkspacesPodVolumes {
-  data: WorkspacesPodVolumeInfo[];
-  home?: WorkspacesPodVolumeInfo;
-  secrets?: WorkspacesPodSecretInfo[];
+  data?: CommonPodVolumeInfo[];
+  home?: CommonPodVolumeInfo;
+  secrets?: CommonPodSecretInfo[];
 }
 
 export interface WorkspacesPodVolumesMutate {

Hence bumping the client version requires to implement the new schemas for activityProbe (previously culling).

The details drawer crashed for any Workspace with no data volume: The
backend omits `podTemplate.volumes.data` from the JSON response
(omitempty) in that case, but the frontend called `.map()` on it
unguarded. Fall back to an empty array, matching the pattern already
used for the home volume.

Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Christian Heusel <christian@heusel.eu>
@github-project-automation github-project-automation Bot moved this to Needs Triage in Kubeflow Notebooks Jul 21, 2026
@google-oss-prow google-oss-prow Bot added the area/frontend area - related to frontend components label Jul 21, 2026
@google-oss-prow
google-oss-prow Bot requested a review from caponetto July 21, 2026 21:55
@google-oss-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign paulovmr for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@google-oss-prow
google-oss-prow Bot requested a review from thaorell July 21, 2026 21:55
@google-oss-prow google-oss-prow Bot added area/v2 area - version - kubeflow notebooks v2 size/XS labels Jul 21, 2026
@christian-heusel

Copy link
Copy Markdown
Member Author

/cc @Snehadas2005

@google-oss-prow
google-oss-prow Bot requested a review from Snehadas2005 July 21, 2026 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/frontend area - related to frontend components area/v2 area - version - kubeflow notebooks v2 size/XS

Projects

Status: Needs Triage

Development

Successfully merging this pull request may close these issues.

1 participant