diff --git a/backend/src/routes/api/integrations/nim/index.ts b/backend/src/routes/api/integrations/nim/index.ts index 4958fb0b5c..244659af20 100644 --- a/backend/src/routes/api/integrations/nim/index.ts +++ b/backend/src/routes/api/integrations/nim/index.ts @@ -7,6 +7,7 @@ import { createNIMAccount, manageNIMSecret, getNIMAccount, + errorMsgList, apiKeyValidationStatus, apiKeyValidationTimestamp, isAppEnabled, @@ -25,13 +26,14 @@ module.exports = async (fastify: KubeFastifyInstance) => { const isEnabled = isAppEnabled(response); const keyValidationStatus: string = apiKeyValidationStatus(response); const keyValidationTimestamp: string = apiKeyValidationTimestamp(response); + const errorMsg: string = errorMsgList(response)[0]; reply.send({ isInstalled: true, isEnabled: isEnabled, variablesValidationStatus: keyValidationStatus, variablesValidationTimestamp: keyValidationTimestamp, canInstall: !isEnabled, - error: '', + error: errorMsg, }); } else { // Not installed diff --git a/backend/src/routes/api/integrations/nim/nimUtils.ts b/backend/src/routes/api/integrations/nim/nimUtils.ts index 00638c484f..9090858f8e 100644 --- a/backend/src/routes/api/integrations/nim/nimUtils.ts +++ b/backend/src/routes/api/integrations/nim/nimUtils.ts @@ -3,6 +3,13 @@ import { KubeFastifyInstance, NIMAccountKind, SecretKind } from '../../../../typ const NIM_SECRET_NAME = 'nvidia-nim-access'; const NIM_ACCOUNT_NAME = 'odh-nim-account'; +export const errorMsgList = (app: NIMAccountKind): string[] => { + const conditions = app?.status?.conditions || []; + return conditions + .filter((condition) => condition.status === 'False') + .map((condition) => condition.message); +}; + export const apiKeyValidationTimestamp = (app: NIMAccountKind): string => { const conditions = app?.status?.conditions || []; const apiKeyCondition = conditions.find((condition) => condition.type === 'APIKeyValidation'); diff --git a/frontend/src/components/OdhAppCard.tsx b/frontend/src/components/OdhAppCard.tsx index 734de6facb..eca65f8e65 100644 --- a/frontend/src/components/OdhAppCard.tsx +++ b/frontend/src/components/OdhAppCard.tsx @@ -13,9 +13,10 @@ import { MenuToggle, DropdownList, Label, + capitalize, } from '@patternfly/react-core'; import { css } from '@patternfly/react-styles'; -import { EllipsisVIcon, ExternalLinkAltIcon } from '@patternfly/react-icons'; +import { EllipsisVIcon, ExclamationCircleIcon, ExternalLinkAltIcon } from '@patternfly/react-icons'; import { OdhApplication } from '~/types'; import { getLaunchStatus, launchQuickStart } from '~/utilities/quickStartUtils'; import EnableModal from '~/pages/exploreApplication/EnableModal'; @@ -141,19 +142,23 @@ const OdhAppCard: React.FC = ({ odhApp }) => { const popoverBodyContent = (hide: () => void) => (
- Subscription is no longer valid. To validate click  - - {!isInternalRouteIntegrationsApp(odhApp.spec.internalRoute) ? ( + {isInternalRouteIntegrationsApp(odhApp.spec.internalRoute) ? ( + <> + {odhApp.spec.error ? `${capitalize(odhApp.spec.error)}.` : ''} Contact your administrator. + + ) : ( <> + Subscription is no longer valid. To validate click  + To remove card click  . - ) : null} + )}
); const disabledPopover = ( Application disabled} + headerContent={ +
+ Enable {odhApp.spec.displayName} failed +
+ } bodyContent={popoverBodyContent} - position="bottom" + position="right" + headerIcon={} + alertSeverityVariant="danger" > -
diff --git a/frontend/src/components/OdhCard.scss b/frontend/src/components/OdhCard.scss index 304ccad233..2db39f8ed3 100644 --- a/frontend/src/components/OdhCard.scss +++ b/frontend/src/components/OdhCard.scss @@ -44,7 +44,8 @@ } &__disabled-popover-title { - font-size: var(--pf-t--global--font--size--heading--h6); + font-size: var(--pf-t--global--font--size--heading--h7); + color: var(--pf-t--global--text--color--status--danger--default); } &__explore-badges { diff --git a/frontend/src/types.ts b/frontend/src/types.ts index ea9c113c3a..45d4aba2e3 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -176,6 +176,7 @@ export type OdhApplication = { }; featureFlag?: string; internalRoute?: string; + error?: string; }; }; diff --git a/frontend/src/utilities/useWatchIntegrationComponents.tsx b/frontend/src/utilities/useWatchIntegrationComponents.tsx index 37142ec6b1..c894834bab 100644 --- a/frontend/src/utilities/useWatchIntegrationComponents.tsx +++ b/frontend/src/utilities/useWatchIntegrationComponents.tsx @@ -41,10 +41,19 @@ export const useWatchIntegrationComponents = ( ); if (response.error) { - // TODO: Show the error somehow - setNewComponents( - componentList.filter((app) => app.metadata.name !== component.metadata.name), + const updatedComponents = componentList.map((app) => + app.metadata.name === component.metadata.name + ? { + ...app, + spec: { + ...app.spec, + isEnabled: false, + error: response.error, + }, + } + : app, ); + setNewComponents(updatedComponents); } else { const updatedComponents = componentList .filter( @@ -69,13 +78,17 @@ export const useWatchIntegrationComponents = ( React.useEffect(() => { let watchHandle: ReturnType; + let isMounted = true; + if (integrationComponents && components) { if (integrationComponents.length === 0) { setIsIntegrationComponentsChecked(true); setNewComponents(components); } else { const watchComponents = () => { + if (!isMounted) return; updateComponentEnablementStatus(integrationComponents, components).then(() => { + if (!isMounted) return; setIsIntegrationComponentsChecked(true); watchHandle = setTimeout(watchComponents, POLL_INTERVAL); }); @@ -83,7 +96,9 @@ export const useWatchIntegrationComponents = ( watchComponents(); } } + return () => { + isMounted = false; clearTimeout(watchHandle); }; }, [components, integrationComponents]);