diff --git a/frontend/public/components/persistent-volume-claim.tsx b/frontend/public/components/persistent-volume-claim.tsx index 00b58e60a23..853ae6951e9 100644 --- a/frontend/public/components/persistent-volume-claim.tsx +++ b/frontend/public/components/persistent-volume-claim.tsx @@ -1,9 +1,11 @@ -import { useMemo, useCallback, Suspense } from 'react'; +import { useMemo, useCallback, Suspense, useState, useEffect } from 'react'; import * as _ from 'lodash'; import i18next, { TFunction } from 'i18next'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; import { + Alert, + AlertActionCloseButton, DescriptionList, DescriptionListDescription, DescriptionListGroup, @@ -76,6 +78,30 @@ const tableColumnInfo = [ { id: '' }, ]; +/** + * Determines the VAC (VolumeAttributesClass) modification state for a PVC + * @param pvc - The PersistentVolumeClaim resource + * @returns Object containing error condition and pending state + */ +const getVACAlertState = (pvc: PersistentVolumeClaimKind) => { + const volumeAttributesClassName = pvc?.spec?.volumeAttributesClassName; + const currentVolumeAttributesClassName = pvc?.status?.currentVolumeAttributesClassName; + const conditions = pvc?.status?.conditions; + + // Check for explicit ModifyVolumeError condition + const hasVacErrorCondition = conditions?.some( + (condition) => condition.type === 'ModifyVolumeError' && condition.status === 'True', + ); + + // Determine if modification is pending + const isVacPending = + !hasVacErrorCondition && + volumeAttributesClassName && + volumeAttributesClassName !== currentVolumeAttributesClassName; + + return { hasVacErrorCondition, isVacPending }; +}; + export const PVCStatusComponent: React.FCC = ({ pvc }) => { const { t } = useTranslation(); const [pvcStatusExtensions, resolved] = useResolvedExtensions(isPVCStatus); @@ -249,6 +275,16 @@ const PVCDetails: React.FCC = ({ obj: pvc }) => { const volumeMode = pvc?.spec?.volumeMode; const conditions = pvc?.status?.conditions; + // State to track dismissed alerts + const [isErrorAlertDismissed, setIsErrorAlertDismissed] = useState(false); + const [isInfoAlertDismissed, setIsInfoAlertDismissed] = useState(false); + + // Reset alert dismiss states when PVC changes + useEffect(() => { + setIsErrorAlertDismissed(false); + setIsInfoAlertDismissed(false); + }, [pvc?.metadata?.uid]); + const query = name && namespace ? `kubelet_volume_stats_used_bytes{persistentvolumeclaim='${name}',namespace='${namespace}'}` @@ -288,10 +324,51 @@ const PVCDetails: React.FCC = ({ obj: pvc }) => { ({ properties: { alert: AlertComponent }, uid }) => , ); + // Get VAC modification state using helper function + const { hasVacErrorCondition, isVacPending } = getVACAlertState(pvc); + return ( <> {alertComponents} + {isVACSupported && hasVacErrorCondition && !isErrorAlertDismissed && ( + setIsErrorAlertDismissed(true)} />} + > + {t( + 'public~VolumeAttributesClass modification failed. Your volume settings could not be updated. Please try again.', + )} + + )} + {isVACSupported && isVacPending && !isInfoAlertDismissed && ( + setIsInfoAlertDismissed(true)} />} + > + {!currentVolumeAttributesClassName + ? t('public~VolumeAttributesClass "{{target}}" is pending application.', { + target: volumeAttributesClassName, + }) + : t( + 'public~Your volume settings are being updated from "{{current}}" to "{{target}}". This may take a few moments.', + { + current: currentVolumeAttributesClassName, + target: volumeAttributesClassName, + }, + )} + + )} {totalCapacityMetric && !loading && (
@@ -383,19 +460,34 @@ const PVCDetails: React.FCC = ({ obj: pvc }) => { )} - {isVACSupported && - !!volumeAttributesClassName && - volumeAttributesClassName === currentVolumeAttributesClassName && ( - - {t('public~VolumeAttributesClass')} - + {isVACSupported && volumeAttributesClassName !== currentVolumeAttributesClassName && ( + + + {t('public~Requested VolumeAttributesClass')} + + + {volumeAttributesClassName ? ( - - - )} + ) : ( + DASH + )} + + + )} + {isVACSupported && !!currentVolumeAttributesClassName && ( + + {t('public~VolumeAttributesClass')} + + + + + )} {volumeName && canListPV && ( {t('public~PersistentVolumes')} diff --git a/frontend/public/locales/en/public.json b/frontend/public/locales/en/public.json index 6b5eb4eb513..668d0ee389f 100644 --- a/frontend/public/locales/en/public.json +++ b/frontend/public/locales/en/public.json @@ -1128,6 +1128,12 @@ "Used": "Used", "StorageClass": "StorageClass", "Total": "Total", + "VolumeAttributesClass modification failed": "VolumeAttributesClass modification failed", + "VolumeAttributesClass modification failed. Your volume settings could not be updated. Please try again.": "VolumeAttributesClass modification failed. Your volume settings could not be updated. Please try again.", + "VolumeAttributesClass application pending": "VolumeAttributesClass application pending", + "VolumeAttributesClass modification in progress": "VolumeAttributesClass modification in progress", + "VolumeAttributesClass \"{{target}}\" is pending application.": "VolumeAttributesClass \"{{target}}\" is pending application.", + "Your volume settings are being updated from \"{{current}}\" to \"{{target}}\". This may take a few moments.": "Your volume settings are being updated from \"{{current}}\" to \"{{target}}\". This may take a few moments.", "PersistentVolumeClaim details": "PersistentVolumeClaim details", "Available versus used capacity": "Available versus used capacity", "Total capacity": "Total capacity", @@ -1136,6 +1142,7 @@ "Access modes": "Access modes", "Volume mode": "Volume mode", "StorageClasses": "StorageClasses", + "Requested VolumeAttributesClass": "Requested VolumeAttributesClass", "VolumeAttributesClass": "VolumeAttributesClass", "PersistentVolumes": "PersistentVolumes", "Bound": "Bound",