From 5f2d2a9ac1bbb880b0836b61a0d83607b19e3e30 Mon Sep 17 00:00:00 2001 From: Swapnil Date: Fri, 7 Aug 2026 19:56:49 +0530 Subject: [PATCH 1/6] CONSOLE-5451: Display projected volume sources with navigable links in Volumes table Projected volumes previously showed a blank or plain-text "Projected" label in the Type column, preventing users from clicking through to constituent ConfigMaps and Secrets. This enhances the VolumeType component to iterate projected sources and render ResourceLinks for ConfigMap/Secret entries, while displaying text labels for non-navigable sources (ServiceAccountToken, DownwardAPI, ClusterTrustBundle). Also adds missing volume types (downwardAPI, CSI, ephemeral) to the VolumeSource map so they display proper labels. Co-authored-by: Cursor --- .../public/components/utils/volume-type.tsx | 67 ++++++++++++++++++- frontend/public/module/k8s/pods.ts | 24 +++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/frontend/public/components/utils/volume-type.tsx b/frontend/public/components/utils/volume-type.tsx index 3cfb01e3ffd..082e12b645f 100644 --- a/frontend/public/components/utils/volume-type.tsx +++ b/frontend/public/components/utils/volume-type.tsx @@ -1,9 +1,70 @@ -import type { FC } from 'react'; +import type { FC, ReactNode } from 'react'; import * as _ from 'lodash'; +import { useTranslation } from 'react-i18next'; import type { Volume } from '../../module/k8s'; import { getVolumeLocation, getVolumeType } from '../../module/k8s/pods'; import { ResourceLink } from './resource-link'; +const ProjectedVolumeSources: FC<{ volume: Volume; namespace: string }> = ({ + volume, + namespace, +}) => { + const { t } = useTranslation('public'); + const sources = volume.projected?.sources; + if (!Array.isArray(sources) || sources.length === 0) { + return <>{t('Projected')}; + } + + const sourceElements: ReactNode[] = []; + sources.forEach((source) => { + if (source.configMap) { + sourceElements.push( + , + ); + } else if (source.secret) { + sourceElements.push( + , + ); + } else if (source.serviceAccountToken) { + sourceElements.push( + + {t('ServiceAccountToken')} + , + ); + } else if (source.downwardAPI) { + sourceElements.push( + + {t('DownwardAPI')} + , + ); + } else if (source.clusterTrustBundle) { + sourceElements.push( + + {t('ClusterTrustBundle')} + , + ); + } + }); + + return sourceElements.length > 0 ? <>{sourceElements} : <>{t('Projected')}; +}; + export const VolumeType: FC = ({ volume, namespace }) => { if (volume) { if (volume.secret) { @@ -23,6 +84,10 @@ export const VolumeType: FC = ({ volume, namespace }) => { /> ); } + + if (volume.projected) { + return ; + } } const type = getVolumeType(volume); diff --git a/frontend/public/module/k8s/pods.ts b/frontend/public/module/k8s/pods.ts index 12ae296b9b0..de20b0dedca 100644 --- a/frontend/public/module/k8s/pods.ts +++ b/frontend/public/module/k8s/pods.ts @@ -97,6 +97,27 @@ const VolumeSource = { 'public~A projected volume maps several existing volume sources into the same directory.', ), }, + downwardAPI: { + id: 'downwardAPI', + label: i18next.t('public~DownwardAPI'), + description: i18next.t( + 'public~Exposes pod and container fields to a running container as files.', + ), + }, + csi: { + id: 'csi', + label: i18next.t('public~CSI'), + description: i18next.t( + 'public~Volume provided by an external Container Storage Interface driver.', + ), + }, + ephemeral: { + id: 'ephemeral', + label: i18next.t('public~Ephemeral'), + description: i18next.t( + 'public~Volume that is handled by a cluster storage driver and follows the lifecycle of the pod.', + ), + }, }; export const getVolumeType = (volume: Volume) => { @@ -138,6 +159,9 @@ export const getVolumeLocation = (volume: Volume) => { case VolumeSource.emptyDir.id: case VolumeSource.secret.id: case VolumeSource.projected.id: + case VolumeSource.downwardAPI.id: + case VolumeSource.csi.id: + case VolumeSource.ephemeral.id: return null; // Defaults to space separated sorted keys. default: From 83a1fe8c4b8e443999da4736c25be2f1ba45a970 Mon Sep 17 00:00:00 2001 From: Swapnil Date: Fri, 7 Aug 2026 20:16:16 +0530 Subject: [PATCH 2/6] CONSOLE-5451: Ensure unique React keys for projected volume sources Use a deduplicated key generation approach to guarantee unique keys even when the same ConfigMap or Secret appears multiple times in a projected volume's sources, while satisfying the no-array-index-key ESLint rule. Co-authored-by: Cursor --- .../public/components/utils/volume-type.tsx | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/frontend/public/components/utils/volume-type.tsx b/frontend/public/components/utils/volume-type.tsx index 082e12b645f..e2b334393f3 100644 --- a/frontend/public/components/utils/volume-type.tsx +++ b/frontend/public/components/utils/volume-type.tsx @@ -16,11 +16,23 @@ const ProjectedVolumeSources: FC<{ volume: Volume; namespace: string }> = ({ } const sourceElements: ReactNode[] = []; + const usedKeys = new Set(); + const getUniqueKey = (base: string): string => { + let key = base; + let counter = 1; + while (usedKeys.has(key)) { + key = `${base}-${counter}`; + counter++; + } + usedKeys.add(key); + return key; + }; + sources.forEach((source) => { if (source.configMap) { sourceElements.push( = ({ } else if (source.secret) { sourceElements.push( = ({ } else if (source.serviceAccountToken) { sourceElements.push( {t('ServiceAccountToken')} @@ -46,14 +58,17 @@ const ProjectedVolumeSources: FC<{ volume: Volume; namespace: string }> = ({ ); } else if (source.downwardAPI) { sourceElements.push( - + {t('DownwardAPI')} , ); } else if (source.clusterTrustBundle) { sourceElements.push( {t('ClusterTrustBundle')} From 3d59faaf5bac9cd64d49aa62281c1f02a384a3d5 Mon Sep 17 00:00:00 2001 From: Swapnil Date: Sat, 8 Aug 2026 09:41:05 +0530 Subject: [PATCH 3/6] CONSOLE-5451: Update i18n keys for new volume type strings Run yarn i18n to register translation keys for newly added volume type labels (ServiceAccountToken, DownwardAPI, ClusterTrustBundle) and descriptions (CSI, Ephemeral, DownwardAPI). Co-authored-by: Cursor --- frontend/public/locales/en/public.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index e19dcaa4a47..5a6bd26fffb 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -371,6 +371,7 @@ "ClusterRoleBinding": "ClusterRoleBinding", "ClusterRoleBindings": "ClusterRoleBindings", "ClusterRoles": "ClusterRoles", + "ClusterTrustBundle": "ClusterTrustBundle", "ClusterVersion": "ClusterVersion", "ClusterVersion details": "ClusterVersion details", "ClusterVersions": "ClusterVersions", @@ -482,6 +483,7 @@ "CronJob": "CronJob", "CronJob details": "CronJob details", "CronJobs": "CronJobs", + "CSI": "public~CSI", "CSIDriver": "CSIDriver", "CSIDrivers": "CSIDrivers", "Current": "Current", @@ -579,6 +581,7 @@ "Download command-line tools": "Download command-line tools", "Download YAML": "Download YAML", "Downloading...": "Downloading...", + "DownwardAPI": "public~DownwardAPI", "Drag and drop file with your private SSH key here or browse to upload it.": "Drag and drop file with your private SSH key here or browse to upload it.", "Drag and drop file with your value here or browse to upload it.": "Drag and drop file with your value here or browse to upload it.", "Drag and drop YAML or JSON files into the editor, or manually enter files and use <2>--- to separate each definition.": "Drag and drop YAML or JSON files into the editor, or manually enter files and use <2>--- to separate each definition.", @@ -630,6 +633,7 @@ "Environment": "Environment", "Environment variables": "Environment variables", "Environment variables set from parent": "Environment variables set from parent", + "Ephemeral": "public~Ephemeral", "Error": "Error", "Error connecting to event stream": "Error connecting to event stream", "Error connecting to event stream: {{ error }}": "Error connecting to event stream: {{ error }}", @@ -661,6 +665,7 @@ "Explore new features and resources within the developer perspective.": "Explore new features and resources within the developer perspective.", "Export as CSV": "Export as CSV", "Exposed ports": "Exposed ports", + "Exposes pod and container fields to a running container as files.": "public~Exposes pod and container fields to a running container as files.", "Extra scopes": "Extra scopes", "Failed": "Failed", "Failed pods": "Failed pods", @@ -1402,6 +1407,7 @@ "ServiceAccount": "ServiceAccount", "ServiceAccount details": "ServiceAccount details", "ServiceAccounts": "ServiceAccounts", + "ServiceAccountToken": "ServiceAccountToken", "ServiceMonitor": "ServiceMonitor", "ServiceMonitors": "ServiceMonitors", "Services": "Services", @@ -1734,6 +1740,8 @@ "Volume": "Volume", "Volume binding mode": "Volume binding mode", "Volume mode": "Volume mode", + "Volume provided by an external Container Storage Interface driver.": "public~Volume provided by an external Container Storage Interface driver.", + "Volume that is handled by a cluster storage driver and follows the lifecycle of the pod.": "public~Volume that is handled by a cluster storage driver and follows the lifecycle of the pod.", "VolumeAttributesClass": "VolumeAttributesClass", "VolumeAttributesClass \"{{target}}\" is pending application.": "VolumeAttributesClass \"{{target}}\" is pending application.", "VolumeAttributesClass application pending": "VolumeAttributesClass application pending", From 992222a12930bc2fa461e50de38b13f558d4eef0 Mon Sep 17 00:00:00 2001 From: Swapnil Date: Sat, 8 Aug 2026 09:42:42 +0530 Subject: [PATCH 4/6] CONSOLE-5451: Address review feedback - use _.uniqueId for keys, remove unnecessary fragments Use lodash uniqueId for React key generation as suggested by reviewer. Remove unnecessary React fragments when returning plain strings from the component. Co-authored-by: Cursor --- .../public/components/utils/volume-type.tsx | 35 ++++--------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/frontend/public/components/utils/volume-type.tsx b/frontend/public/components/utils/volume-type.tsx index e2b334393f3..7cc24d391cb 100644 --- a/frontend/public/components/utils/volume-type.tsx +++ b/frontend/public/components/utils/volume-type.tsx @@ -12,27 +12,15 @@ const ProjectedVolumeSources: FC<{ volume: Volume; namespace: string }> = ({ const { t } = useTranslation('public'); const sources = volume.projected?.sources; if (!Array.isArray(sources) || sources.length === 0) { - return <>{t('Projected')}; + return t('Projected'); } const sourceElements: ReactNode[] = []; - const usedKeys = new Set(); - const getUniqueKey = (base: string): string => { - let key = base; - let counter = 1; - while (usedKeys.has(key)) { - key = `${base}-${counter}`; - counter++; - } - usedKeys.add(key); - return key; - }; - sources.forEach((source) => { if (source.configMap) { sourceElements.push( = ({ } else if (source.secret) { sourceElements.push( = ({ ); } else if (source.serviceAccountToken) { sourceElements.push( - + {t('ServiceAccountToken')} , ); } else if (source.downwardAPI) { sourceElements.push( - + {t('DownwardAPI')} , ); } else if (source.clusterTrustBundle) { sourceElements.push( - + {t('ClusterTrustBundle')} , ); } }); - return sourceElements.length > 0 ? <>{sourceElements} : <>{t('Projected')}; + return sourceElements.length > 0 ? <>{sourceElements} : t('Projected'); }; export const VolumeType: FC = ({ volume, namespace }) => { From f58b7f20510784a9fdb5b0a36feea7fa424b1ba1 Mon Sep 17 00:00:00 2001 From: Swapnil Date: Mon, 10 Aug 2026 19:28:54 +0530 Subject: [PATCH 5/6] CONSOLE-5451: Remove public~ prefix from i18n values The locale JSON values should contain plain translated strings, not the namespace-prefixed keys used in source code t() calls. Co-authored-by: Cursor --- frontend/public/locales/en/public.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index 5a6bd26fffb..c6fca5d3caa 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -581,7 +581,7 @@ "Download command-line tools": "Download command-line tools", "Download YAML": "Download YAML", "Downloading...": "Downloading...", - "DownwardAPI": "public~DownwardAPI", + "DownwardAPI": "DownwardAPI", "Drag and drop file with your private SSH key here or browse to upload it.": "Drag and drop file with your private SSH key here or browse to upload it.", "Drag and drop file with your value here or browse to upload it.": "Drag and drop file with your value here or browse to upload it.", "Drag and drop YAML or JSON files into the editor, or manually enter files and use <2>--- to separate each definition.": "Drag and drop YAML or JSON files into the editor, or manually enter files and use <2>--- to separate each definition.", @@ -665,7 +665,7 @@ "Explore new features and resources within the developer perspective.": "Explore new features and resources within the developer perspective.", "Export as CSV": "Export as CSV", "Exposed ports": "Exposed ports", - "Exposes pod and container fields to a running container as files.": "public~Exposes pod and container fields to a running container as files.", + "Exposes pod and container fields to a running container as files.": "Exposes pod and container fields to a running container as files.", "Extra scopes": "Extra scopes", "Failed": "Failed", "Failed pods": "Failed pods", @@ -1740,8 +1740,8 @@ "Volume": "Volume", "Volume binding mode": "Volume binding mode", "Volume mode": "Volume mode", - "Volume provided by an external Container Storage Interface driver.": "public~Volume provided by an external Container Storage Interface driver.", - "Volume that is handled by a cluster storage driver and follows the lifecycle of the pod.": "public~Volume that is handled by a cluster storage driver and follows the lifecycle of the pod.", + "Volume provided by an external Container Storage Interface driver.": "Volume provided by an external Container Storage Interface driver.", + "Volume that is handled by a cluster storage driver and follows the lifecycle of the pod.": "Volume that is handled by a cluster storage driver and follows the lifecycle of the pod.", "VolumeAttributesClass": "VolumeAttributesClass", "VolumeAttributesClass \"{{target}}\" is pending application.": "VolumeAttributesClass \"{{target}}\" is pending application.", "VolumeAttributesClass application pending": "VolumeAttributesClass application pending", From 1846e45b8feb56e9bf4c10a5677df23df4f80cd3 Mon Sep 17 00:00:00 2001 From: Swapnil Date: Mon, 10 Aug 2026 19:56:57 +0530 Subject: [PATCH 6/6] CONSOLE-5451: Remove remaining public~ prefixes from i18n values Remove public~ prefix from CSI and Ephemeral entries that were missed in the previous commit. Co-authored-by: Cursor --- frontend/public/locales/en/public.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index c6fca5d3caa..ae8f3f06e33 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -483,7 +483,7 @@ "CronJob": "CronJob", "CronJob details": "CronJob details", "CronJobs": "CronJobs", - "CSI": "public~CSI", + "CSI": "CSI", "CSIDriver": "CSIDriver", "CSIDrivers": "CSIDrivers", "Current": "Current", @@ -633,7 +633,7 @@ "Environment": "Environment", "Environment variables": "Environment variables", "Environment variables set from parent": "Environment variables set from parent", - "Ephemeral": "public~Ephemeral", + "Ephemeral": "Ephemeral", "Error": "Error", "Error connecting to event stream": "Error connecting to event stream", "Error connecting to event stream: {{ error }}": "Error connecting to event stream: {{ error }}",