diff --git a/frontend/src/__mocks__/mockNimAccount.ts b/frontend/src/__mocks__/mockNimAccount.ts
index 3567d5b21d..5d0b414a5a 100644
--- a/frontend/src/__mocks__/mockNimAccount.ts
+++ b/frontend/src/__mocks__/mockNimAccount.ts
@@ -19,7 +19,15 @@ export const mockNimAccount = ({
nimConfigName = 'mock-nvidia-nim-images-data',
runtimeTemplateName = 'mock-nvidia-nim-serving-template',
nimPullSecretName = 'mock-nvidia-nim-image-pull',
- conditions = [],
+ conditions = [
+ {
+ type: 'AccountStatus',
+ status: 'True',
+ lastTransitionTime: new Date().toISOString(),
+ reason: 'AccountSuccessful',
+ message: 'reconciled successfully',
+ },
+ ],
}: MockResourceConfigType): NIMAccountKind => ({
apiVersion: 'nim.opendatahub.io/v1',
kind: 'Account',
diff --git a/frontend/src/__mocks__/mockNimResource.ts b/frontend/src/__mocks__/mockNimResource.ts
index 9d2ea1d1f5..75579dcd9c 100644
--- a/frontend/src/__mocks__/mockNimResource.ts
+++ b/frontend/src/__mocks__/mockNimResource.ts
@@ -143,3 +143,31 @@ export const mockNimModelPVC = (): PersistentVolumeClaimKind => {
export const mockNimServingResource = (
resource: ConfigMapKind | SecretKind,
): NimServingResponse => ({ body: { body: resource } });
+
+export const mockOdhApplication = [
+ {
+ metadata: {
+ name: 'nvidia-nim',
+ annotations: {
+ 'internal.config.kubernetes.io/previousKinds': 'OdhApplication',
+ },
+ },
+ spec: {
+ displayName: 'NVIDIA NIM',
+ provider: 'NVIDIA',
+ description: 'NVIDIA Inference Microservices for AI model serving.',
+ route: 'https://nim-route.test.com',
+ routeNamespace: 'redhat-ods-applications',
+ img: 'https://example.com/nvidia-nim.png',
+ docsLink: 'https://docs.nvidia.com/nim',
+ getStartedLink: 'https://nvidia.com/get-started-nim',
+ getStartedMarkDown: '**NVIDIA NIM** provides fast and efficient model serving.',
+ category: 'Self-managed',
+ shownOnEnabledPage: true,
+ isEnabled: true,
+ internalRoute: 'https://internal-nim-route.test.com',
+ quickStart: 'nim-quickstart-guide',
+ beta: false,
+ },
+ },
+];
diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/modelServingNim.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/modelServingNim.cy.ts
index a8cf7aec24..fdb087cd05 100644
--- a/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/modelServingNim.cy.ts
+++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/modelServingNim.cy.ts
@@ -264,7 +264,7 @@ describe('NIM Model Serving', () => {
disableModelMesh: false,
disableNIMModelServing: false,
},
- true,
+ // true,
);
projectDetailsOverviewTab.visit('test-project');
cy.findByTestId('model-serving-platform-button').should('not.exist');
@@ -277,7 +277,7 @@ describe('NIM Model Serving', () => {
disableModelMesh: false,
disableNIMModelServing: false,
},
- true,
+ // true,
);
projectDetailsOverviewTab.visit('test-project');
projectDetailsOverviewTab.findModelServingPlatform('nvidia-nim').should('not.exist');
@@ -291,7 +291,7 @@ describe('NIM Model Serving', () => {
disableModelMesh: false,
disableNIMModelServing: false,
},
- true,
+ // true,
);
projectDetails.visitSection('test-project', 'model-server');
cy.get('button[data-testid=deploy-button]').should('not.exist');
@@ -304,7 +304,7 @@ describe('NIM Model Serving', () => {
disableModelMesh: false,
disableNIMModelServing: false,
},
- true,
+ // true,
);
projectDetails.visitSection('test-project', 'model-server');
projectDetails.findModelServingPlatform('nvidia-nim-model').should('not.exist');
diff --git a/frontend/src/__tests__/cypress/cypress/utils/nimUtils.ts b/frontend/src/__tests__/cypress/cypress/utils/nimUtils.ts
index 7bf22a7c6c..58463166a5 100644
--- a/frontend/src/__tests__/cypress/cypress/utils/nimUtils.ts
+++ b/frontend/src/__tests__/cypress/cypress/utils/nimUtils.ts
@@ -16,7 +16,6 @@ import {
PVCModel,
SecretModel,
ServingRuntimeModel,
- TemplateModel,
} from '~/__tests__/cypress/cypress/utils/models';
import {
mockNimImages,
@@ -25,9 +24,9 @@ import {
mockNimProject,
mockNimServingResource,
mockNimServingRuntime,
- mockNimServingRuntimeTemplate,
mockNvidiaNimAccessSecret,
mockNvidiaNimImagePullSecret,
+ mockOdhApplication,
} from '~/__mocks__/mockNimResource';
import { mockAcceleratorProfile } from '~/__mocks__/mockAcceleratorProfile';
import type { InferenceServiceKind } from '~/k8sTypes';
@@ -62,11 +61,15 @@ export const initInterceptsToEnableNim = ({ hasAllModels = false }: EnableNimCon
}),
);
+ cy.interceptOdh('GET /api/components', mockOdhApplication);
+
+ cy.interceptK8sList(NIMAccountModel, mockK8sResourceList([mockNimAccount({})]));
+
cy.interceptK8sList(ProjectModel, mockK8sResourceList([mockNimProject(hasAllModels)]));
- const templateMock = mockNimServingRuntimeTemplate();
- cy.interceptK8sList(TemplateModel, mockK8sResourceList([templateMock]));
- cy.interceptK8s(TemplateModel, templateMock);
+ // const templateMock = mockNimServingRuntimeTemplate();
+ // cy.interceptK8sList(TemplateModel, mockK8sResourceList([templateMock]));
+ // cy.interceptK8s(TemplateModel, templateMock);
cy.interceptK8sList(
AcceleratorProfileModel,
@@ -79,8 +82,6 @@ export const initInterceptsToEnableNim = ({ hasAllModels = false }: EnableNimCon
total: { 'nvidia.com/gpu': 1 },
allocated: { 'nvidia.com/gpu': 1 },
});
-
- cy.interceptK8sList(NIMAccountModel, mockK8sResourceList([mockNimAccount({})]));
};
// intercept all APIs required for deploying new NIM models in existing projects
@@ -149,16 +150,16 @@ export const initInterceptorsValidatingNimEnablement = (
): void => {
cy.interceptOdh('GET /api/config', mockDashboardConfig(dashboardConfig));
- if (!disableServingRuntime) {
- const templateMock = mockNimServingRuntimeTemplate();
- cy.interceptK8sList(TemplateModel, mockK8sResourceList([templateMock]));
- cy.interceptK8s(TemplateModel, templateMock);
- }
+ cy.interceptK8sList(NIMAccountModel, mockK8sResourceList([mockNimAccount({})]));
+
+ // if (!disableServingRuntime) {
+ // const templateMock = mockNimServingRuntimeTemplate();
+ // cy.interceptK8sList(TemplateModel, mockK8sResourceList([templateMock]));
+ // cy.interceptK8s(TemplateModel, templateMock);
+ // }
cy.interceptK8sList(
ProjectModel,
mockK8sResourceList([mockProjectK8sResource({ hasAnnotations: true })]),
);
-
- cy.interceptK8sList(NIMAccountModel, mockK8sResourceList([mockNimAccount({})]));
};
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index ef03093ecc..8221d80a66 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -19,21 +19,21 @@ import { ReduxContext } from './redux/context';
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const root = createRoot(document.getElementById('root')!);
root.render(
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ //
+
+
+
+
+
+
+
+
+
+
+
+
-
- ,
+
+ ,
+ // ,
);
diff --git a/frontend/src/pages/modelServing/ModelServingContext.tsx b/frontend/src/pages/modelServing/ModelServingContext.tsx
index 398ee439e5..c282a32669 100644
--- a/frontend/src/pages/modelServing/ModelServingContext.tsx
+++ b/frontend/src/pages/modelServing/ModelServingContext.tsx
@@ -9,11 +9,11 @@ import {
import { useNavigate } from 'react-router-dom';
import { ExclamationCircleIcon } from '@patternfly/react-icons';
import {
- ServingRuntimeKind,
InferenceServiceKind,
- TemplateKind,
ProjectKind,
SecretKind,
+ ServingRuntimeKind,
+ TemplateKind,
} from '~/k8sTypes';
import { DEFAULT_CONTEXT_DATA, DEFAULT_LIST_WATCH_RESULT } from '~/utilities/const';
import { ContextResourceData, CustomWatchK8sResult } from '~/types';
@@ -22,10 +22,11 @@ import { useDashboardNamespace } from '~/redux/selectors';
import { DataConnection } from '~/pages/projects/types';
import useDataConnections from '~/pages/projects/screens/detail/data-connections/useDataConnections';
import useSyncPreferredProject from '~/concepts/projects/useSyncPreferredProject';
-import { ProjectsContext, byName } from '~/concepts/projects/ProjectsContext';
-import { SupportedArea, conditionalArea } from '~/concepts/areas';
+import { byName, ProjectsContext } from '~/concepts/projects/ProjectsContext';
+import { conditionalArea, SupportedArea } from '~/concepts/areas';
import useServingPlatformStatuses from '~/pages/modelServing/useServingPlatformStatuses';
import { useTemplates } from '~/api';
+import { ServingPlatformStatuses } from '~/pages/modelServing/screens/types';
import useInferenceServices from './useInferenceServices';
import useServingRuntimes from './useServingRuntimes';
import useTemplateOrder from './customServingRuntimes/useTemplateOrder';
@@ -46,6 +47,7 @@ type ModelServingContextType = {
preferredProject: ProjectKind | null;
serverSecrets: ContextResourceData;
projects: ProjectKind[] | null;
+ servingPlatformStatuses: ServingPlatformStatuses;
};
type ModelServingContextProviderProps = {
@@ -67,6 +69,22 @@ export const ModelServingContext = React.createContext(
project: null,
preferredProject: null,
projects: null,
+ servingPlatformStatuses: {
+ kServe: {
+ enabled: false,
+ installed: false,
+ },
+ kServeNIM: {
+ enabled: false,
+ installed: false,
+ isLoaded: false,
+ },
+ modelMesh: {
+ enabled: false,
+ installed: false,
+ },
+ platformEnabledCount: 0,
+ },
});
const ModelServingContextProvider = conditionalArea(
@@ -92,6 +110,7 @@ const ModelServingContextProvider = conditionalArea(useDataConnections(namespace));
+ const servingPlatformStatuses = useServingPlatformStatuses();
const servingRuntimeRefresh = servingRuntimes.refresh;
const inferenceServiceRefresh = inferenceServices.refresh;
@@ -102,13 +121,8 @@ const ModelServingContextProvider = conditionalArea
{children}
diff --git a/frontend/src/pages/modelServing/screens/global/InferenceServiceTableRow.tsx b/frontend/src/pages/modelServing/screens/global/InferenceServiceTableRow.tsx
index ff34ef8080..dee0176440 100644
--- a/frontend/src/pages/modelServing/screens/global/InferenceServiceTableRow.tsx
+++ b/frontend/src/pages/modelServing/screens/global/InferenceServiceTableRow.tsx
@@ -11,6 +11,7 @@ import useIsAreaAvailable from '~/concepts/areas/useIsAreaAvailable';
import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils';
import { byName, ProjectsContext } from '~/concepts/projects/ProjectsContext';
import { isProjectNIMSupported } from '~/pages/modelServing/screens/projects/nimUtils';
+import useServingPlatformStatuses from '~/pages/modelServing/useServingPlatformStatuses';
import InferenceServiceEndpoint from './InferenceServiceEndpoint';
import InferenceServiceProject from './InferenceServiceProject';
import InferenceServiceStatus from './InferenceServiceStatus';
@@ -38,6 +39,8 @@ const InferenceServiceTableRow: React.FC = ({
const { projects } = React.useContext(ProjectsContext);
const project = projects.find(byName(inferenceService.metadata.namespace)) ?? null;
const isKServeNIMEnabled = project ? isProjectNIMSupported(project) : false;
+ const servingPlatformStatuses = useServingPlatformStatuses();
+ const isNIMAvailable = servingPlatformStatuses.kServeNIM.enabled;
const [modelMetricsEnabled] = useModelMetricsEnabled();
const kserveMetricsEnabled = useIsAreaAvailable(SupportedArea.K_SERVE_METRICS).status;
@@ -112,6 +115,7 @@ const InferenceServiceTableRow: React.FC = ({
onClick: () => {
onEditInferenceService(inferenceService);
},
+ isDisabled: !isNIMAvailable && isKServeNIMEnabled,
},
{ isSeparator: true },
{
diff --git a/frontend/src/pages/modelServing/screens/global/ServeModelButton.tsx b/frontend/src/pages/modelServing/screens/global/ServeModelButton.tsx
index c7a5d9b5d1..13eebbde5b 100644
--- a/frontend/src/pages/modelServing/screens/global/ServeModelButton.tsx
+++ b/frontend/src/pages/modelServing/screens/global/ServeModelButton.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { Button, Tooltip } from '@patternfly/react-core';
+import { Button, Skeleton, Tooltip } from '@patternfly/react-core';
import { useParams } from 'react-router-dom';
import ManageInferenceServiceModal from '~/pages/modelServing/screens/projects/InferenceServiceModal/ManageInferenceServiceModal';
import { ModelServingContext } from '~/pages/modelServing/ModelServingContext';
@@ -12,7 +12,6 @@ import { ServingRuntimePlatform } from '~/types';
import { getProjectModelServingPlatform } from '~/pages/modelServing/screens/projects/utils';
import ManageKServeModal from '~/pages/modelServing/screens/projects/kServeModal/ManageKServeModal';
import { byName, ProjectsContext } from '~/concepts/projects/ProjectsContext';
-import useServingPlatformStatuses from '~/pages/modelServing/useServingPlatformStatuses';
import { isProjectNIMSupported } from '~/pages/modelServing/screens/projects/nimUtils';
import ManageNIMServingModal from '~/pages/modelServing/screens/projects/NIMServiceModal/ManageNIMServingModal';
@@ -27,10 +26,12 @@ const ServeModelButton: React.FC = () => {
servingRuntimeTemplateOrder: { data: templateOrder },
servingRuntimeTemplateDisablement: { data: templateDisablement },
dataConnections: { data: dataConnections },
+ servingPlatformStatuses,
} = React.useContext(ModelServingContext);
const { projects } = React.useContext(ProjectsContext);
const { namespace } = useParams<{ namespace: string }>();
- const servingPlatformStatuses = useServingPlatformStatuses();
+ const isNIMAvailable = servingPlatformStatuses.kServeNIM.enabled;
+ const nimLoaded = servingPlatformStatuses.kServeNIM.isLoaded;
const project = projects.find(byName(namespace));
@@ -58,7 +59,7 @@ const ServeModelButton: React.FC = () => {
getProjectModelServingPlatform(project, servingPlatformStatuses).platform,
)
}
- isAriaDisabled={!project || !templatesEnabled}
+ isAriaDisabled={!project || (!isNIMAvailable && isKServeNIMEnabled)}
>
Deploy model
@@ -67,7 +68,19 @@ const ServeModelButton: React.FC = () => {
if (!project) {
return (
- {deployButton}
+ {deployButton}
+
+ );
+ }
+
+ if (isKServeNIMEnabled && !nimLoaded) {
+ return ;
+ }
+
+ if (!isNIMAvailable && isKServeNIMEnabled) {
+ return (
+
+ {deployButton}
);
}
diff --git a/frontend/src/pages/modelServing/screens/projects/EmptySingleModelServingCard.tsx b/frontend/src/pages/modelServing/screens/projects/EmptySingleModelServingCard.tsx
index b950d71714..76cc83dc86 100644
--- a/frontend/src/pages/modelServing/screens/projects/EmptySingleModelServingCard.tsx
+++ b/frontend/src/pages/modelServing/screens/projects/EmptySingleModelServingCard.tsx
@@ -20,6 +20,7 @@ const EmptySingleModelServingCard: React.FC =
setErrorSelectingPlatform,
}) => {
const { currentProject } = React.useContext(ProjectDetailsContext);
+
return (
{
// deployingFromRegistry = User came from the Model Registry page because this project didn't have a serving platform selected
const deployingFromRegistry = !!(modelRegistryName && registeredModelId && modelVersionId);
- const servingPlatformStatuses = useServingPlatformStatuses();
- const kServeEnabled = servingPlatformStatuses.kServe.enabled;
- const isNIMAvailable = servingPlatformStatuses.kServeNIM.enabled;
- const modelMeshEnabled = servingPlatformStatuses.modelMesh.enabled;
-
const {
servingRuntimes: {
data: servingRuntimes,
@@ -80,8 +74,13 @@ const ModelServingPlatform: React.FC = () => {
serverSecrets: { refresh: refreshTokens },
inferenceServices: { refresh: refreshInferenceServices },
currentProject,
+ servingPlatformStatuses,
} = React.useContext(ProjectDetailsContext);
+ const kServeEnabled = servingPlatformStatuses.kServe.enabled;
+ const modelMeshEnabled = servingPlatformStatuses.modelMesh.enabled;
+ const isNIMAvailable = servingPlatformStatuses.kServeNIM.enabled;
+
const isKServeNIMEnabled = isProjectNIMSupported(currentProject);
const templatesSorted = getSortedTemplates(templates, templateOrder);
@@ -140,6 +139,12 @@ const ModelServingPlatform: React.FC = () => {
testId={`${isProjectModelMesh ? 'add-server' : 'deploy'}-button`}
emptyTemplates={emptyTemplates}
variant="primary"
+ // isNimDisabled={!isNIMAvailable && isKServeNIMEnabled}
+ // tooltipContent={
+ // !isNIMAvailable && isKServeNIMEnabled
+ // ? 'NIM is not available. Contact your administrator.'
+ // : undefined
+ // }
onClick={() => {
setPlatformSelected(
isProjectModelMesh ? ServingRuntimePlatform.MULTI : ServingRuntimePlatform.SINGLE,
@@ -221,6 +226,12 @@ const ModelServingPlatform: React.FC = () => {
isProjectModelMesh={isProjectModelMesh}
testId={`${isProjectModelMesh ? 'add-server' : 'deploy'}-button`}
emptyTemplates={emptyTemplates}
+ // isNimDisabled={!isNIMAvailable && isKServeNIMEnabled}
+ // tooltipContent={
+ // !isNIMAvailable && isKServeNIMEnabled
+ // ? 'NIM is not available. Contact your administrator.'
+ // : undefined
+ // }
onClick={() => {
setPlatformSelected(
isProjectModelMesh
diff --git a/frontend/src/pages/modelServing/screens/projects/ModelServingPlatformButtonAction.tsx b/frontend/src/pages/modelServing/screens/projects/ModelServingPlatformButtonAction.tsx
index 4f7057ce24..8d35cb160e 100644
--- a/frontend/src/pages/modelServing/screens/projects/ModelServingPlatformButtonAction.tsx
+++ b/frontend/src/pages/modelServing/screens/projects/ModelServingPlatformButtonAction.tsx
@@ -1,6 +1,7 @@
import * as React from 'react';
-import { Button, Tooltip, Content, ButtonProps } from '@patternfly/react-core';
+import { Button, ButtonProps, Content, Tooltip } from '@patternfly/react-core';
import { ProjectDetailsContext } from '~/pages/projects/ProjectDetailsContext';
+import { isProjectNIMSupported } from '~/pages/modelServing/screens/projects/nimUtils';
type ModelServingPlatformButtonActionProps = ButtonProps & {
isProjectModelMesh: boolean;
@@ -17,13 +18,18 @@ const ModelServingPlatformButtonAction: React.FC {
const {
servingRuntimeTemplates: [, templatesLoaded],
+ servingPlatformStatuses,
+ currentProject,
} = React.useContext(ProjectDetailsContext);
+ const isNIMAvailable = servingPlatformStatuses.kServeNIM.enabled;
+ const isKServeNIMEnabled = isProjectNIMSupported(currentProject);
+ const isNimDisabled = !isNIMAvailable && isKServeNIMEnabled;
- const actionButton = () => (
+ const actionButton = (