diff --git a/backend/src/routes/api/modelRegistries/modelRegistryUtils.ts b/backend/src/routes/api/modelRegistries/modelRegistryUtils.ts index 00a2e5846e..7c5e63bcf1 100644 --- a/backend/src/routes/api/modelRegistries/modelRegistryUtils.ts +++ b/backend/src/routes/api/modelRegistries/modelRegistryUtils.ts @@ -246,7 +246,7 @@ const patchModelRegistry = async ( dryRun ? 'All' : undefined, undefined, undefined, - { headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_PATCH } }, + { headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_MERGE_PATCH } }, // patchNamespacedCustomObject doesn't support TS generics and returns body as `object`, so we assert its real type ) as Promise<{ body: ModelRegistryKind }>); return response.body; @@ -279,6 +279,9 @@ const updateDatabasePassword = async ( }, undefined, dryRun ? 'All' : undefined, + undefined, + undefined, + { headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_MERGE_PATCH } }, ); } else { await deleteDatabasePasswordSecret(fastify, modelRegistry, modelRegistryNamespace); diff --git a/frontend/src/__tests__/cypress/cypress/pages/modelRegistrySettings.ts b/frontend/src/__tests__/cypress/cypress/pages/modelRegistrySettings.ts index 7b9e779252..313eeab659 100644 --- a/frontend/src/__tests__/cypress/cypress/pages/modelRegistrySettings.ts +++ b/frontend/src/__tests__/cypress/cypress/pages/modelRegistrySettings.ts @@ -1,8 +1,8 @@ import { appChrome } from './appChrome'; +import { K8sNameDescriptionField } from './components/subComponents/K8sNameDescriptionField'; export enum FormFieldSelector { NAME = '#mr-name', - RESOURCENAME = '#resource-mr-name', HOST = '#mr-host', PORT = '#mr-port', USERNAME = '#mr-username', @@ -27,6 +27,8 @@ export enum DatabaseDetailsTestId { } class ModelRegistrySettings { + k8sNameDescription = new K8sNameDescriptionField('mr'); + visit(wait = true) { cy.visitWithLogin('/modelRegistrySettings'); if (wait) { diff --git a/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts b/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts index 28ee725b29..2b500aa58d 100644 --- a/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts +++ b/frontend/src/__tests__/cypress/cypress/support/commands/odh.ts @@ -366,6 +366,13 @@ declare global { type: 'GET /api/modelRegistries', response: OdhResponse>, ) => Cypress.Chainable) & + (( + type: 'PATCH /api/modelRegistries/:modelRegistryName', + options: { + path: { modelRegistryName: string }; + }, + response: OdhResponse<{ modelRegistry: ModelRegistryKind; databasePassword?: string }>, + ) => Cypress.Chainable) & (( type: 'GET /api/modelRegistries/:modelRegistryName', options: { diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistrySettings/modelRegistrySettings.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistrySettings/modelRegistrySettings.cy.ts index a9e907203f..d39aeec2d5 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistrySettings/modelRegistrySettings.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistrySettings/modelRegistrySettings.cy.ts @@ -4,7 +4,6 @@ import { StackCapability, StackComponent } from '~/concepts/areas/types'; import { FormFieldSelector, modelRegistrySettings, - DatabaseDetailsTestId, } from '~/__tests__/cypress/cypress/pages/modelRegistrySettings'; import { pageNotfound } from '~/__tests__/cypress/cypress/pages/pageNotFound'; import { @@ -26,8 +25,10 @@ const groupSubjects: RoleBindingSubject[] = [ const setupMocksForMRSettingAccess = ({ hasModelRegistries = true, + hasDatabasePassword = true, }: { hasModelRegistries?: boolean; + hasDatabasePassword?: boolean; }) => { asProductAdminUser(); cy.interceptOdh( @@ -82,7 +83,7 @@ const setupMocksForMRSettingAccess = ({ }, { modelRegistry: mockModelRegistry({ name: 'test-registry-1' }), - databasePassword: 'test-password', + databasePassword: hasDatabasePassword ? 'test-password' : undefined, }, ); cy.interceptOdh( @@ -95,6 +96,17 @@ const setupMocksForMRSettingAccess = ({ }, ); + cy.interceptOdh( + 'PATCH /api/modelRegistries/:modelRegistryName', + { + path: { modelRegistryName: 'test-registry-1' }, + }, + { + modelRegistry: mockModelRegistry({ name: 'test-registry-1' }), + databasePassword: 'test-password', + }, + ); + cy.interceptOdh( 'GET /api/modelRegistryRoleBindings', mockK8sResourceList([ @@ -161,6 +173,21 @@ describe('CreateModal', () => { modelRegistrySettings.findFormField(FormFieldSelector.PASSWORD).type('strongPassword'); modelRegistrySettings.findFormField(FormFieldSelector.DATABASE).type('myDatabase'); modelRegistrySettings.findFormField(FormFieldSelector.DATABASE).blur(); + modelRegistrySettings.findSubmitButton().should('be.enabled'); + + // test resource name validation + modelRegistrySettings.k8sNameDescription.findResourceEditLink().click(); + modelRegistrySettings.k8sNameDescription + .findResourceNameInput() + .should('have.attr', 'aria-invalid', 'false'); + // Invalid character k8s names fail + modelRegistrySettings.k8sNameDescription.findResourceNameInput().clear().type('InVaLiD vAlUe!'); + modelRegistrySettings.k8sNameDescription + .findResourceNameInput() + .should('have.attr', 'aria-invalid', 'true'); + modelRegistrySettings.findSubmitButton().should('be.disabled'); + modelRegistrySettings.k8sNameDescription.findResourceNameInput().clear().type('image'); + modelRegistrySettings.findSubmitButton().should('be.enabled'); modelRegistrySettings.shouldHaveNoErrors(); }); @@ -176,40 +203,80 @@ describe('ModelRegistriesTable', () => { }); }); -describe('ViewDatabaseConfigModal', () => { - it('Shows database details for a registry', () => { +describe('EditModelRegistry', () => { + it('Update model registry', () => { setupMocksForMRSettingAccess({}); modelRegistrySettings.visit(true); modelRegistrySettings .findModelRegistryRow('test-registry-1') - .findKebabAction('View database configuration') + .findKebabAction('Edit model registry') .click(); modelRegistrySettings - .findDatabaseDetail(DatabaseDetailsTestId.HOST) - .should('contain.text', 'model-registry-db'); + .findFormField(FormFieldSelector.NAME) + .should('have.value', 'test-registry-1'); + modelRegistrySettings.findFormField(FormFieldSelector.NAME).clear().type('test-2'); modelRegistrySettings - .findDatabaseDetail(DatabaseDetailsTestId.PORT) - .should('contain.text', '5432'); + .findFormField(FormFieldSelector.HOST) + .should('have.value', 'model-registry-db'); + modelRegistrySettings.findFormField(FormFieldSelector.PORT).should('have.value', '5432'); modelRegistrySettings - .findDatabaseDetail(DatabaseDetailsTestId.USERNAME) - .should('contain.text', 'mlmduser'); - modelRegistrySettings.findDatabasePasswordHiddenButton().click(); + .findFormField(FormFieldSelector.USERNAME) + .should('have.value', 'mlmduser'); modelRegistrySettings - .findDatabaseDetail(DatabaseDetailsTestId.PASSWORD) - .should('contain.text', 'test-password'); + .findFormField(FormFieldSelector.PASSWORD) + .should('have.value', 'test-password'); modelRegistrySettings - .findDatabaseDetail(DatabaseDetailsTestId.DATABASE) - .should('contain.text', 'model-registry'); + .findFormField(FormFieldSelector.DATABASE) + .should('have.value', 'model-registry'); + modelRegistrySettings.findSubmitButton().should('be.enabled'); + modelRegistrySettings.findSubmitButton().click(); }); - it('Shows error loading password when secret fails to decode', () => { - setupMocksForMRSettingAccess({}); + it('Shows skeleton, when password is loading', () => { + setupMocksForMRSettingAccess({ hasDatabasePassword: false }); + modelRegistrySettings.visit(true); + modelRegistrySettings + .findModelRegistryRow('test-registry-1') + .findKebabAction('Edit model registry') + .click(); + modelRegistrySettings + .findFormField(FormFieldSelector.NAME) + .should('have.value', 'test-registry-1'); + modelRegistrySettings + .findFormField(FormFieldSelector.HOST) + .should('have.value', 'model-registry-db'); + modelRegistrySettings.findFormField(FormFieldSelector.PORT).should('have.value', '5432'); + modelRegistrySettings + .findFormField(FormFieldSelector.USERNAME) + .should('have.value', 'mlmduser'); + modelRegistrySettings + .findFormField(FormFieldSelector.DATABASE) + .should('have.value', 'model-registry'); + modelRegistrySettings.findSubmitButton().should('be.disabled'); + }); + + it('Shows erros, when password fails to load', () => { + setupMocksForMRSettingAccess({ hasDatabasePassword: false }); modelRegistrySettings.visit(true); modelRegistrySettings .findModelRegistryRow('test-registry-2') - .findKebabAction('View database configuration') + .findKebabAction('Edit model registry') .click(); - cy.findByText('Error loading password').should('exist'); + modelRegistrySettings + .findFormField(FormFieldSelector.NAME) + .should('have.value', 'test-registry-2'); + modelRegistrySettings + .findFormField(FormFieldSelector.HOST) + .should('have.value', 'model-registry-db'); + modelRegistrySettings.findFormField(FormFieldSelector.PORT).should('have.value', '5432'); + modelRegistrySettings + .findFormField(FormFieldSelector.USERNAME) + .should('have.value', 'mlmduser'); + cy.findByText('Failed to load the password. The Secret file is missing.').should('exist'); + modelRegistrySettings + .findFormField(FormFieldSelector.DATABASE) + .should('have.value', 'model-registry'); + modelRegistrySettings.findSubmitButton().should('be.disabled'); }); }); diff --git a/frontend/src/pages/modelRegistrySettings/CreateModal.tsx b/frontend/src/pages/modelRegistrySettings/CreateModal.tsx index caf429ec99..c669b08a47 100644 --- a/frontend/src/pages/modelRegistrySettings/CreateModal.tsx +++ b/frontend/src/pages/modelRegistrySettings/CreateModal.tsx @@ -9,31 +9,34 @@ import { TextInput, } from '@patternfly/react-core'; import { Modal } from '@patternfly/react-core/deprecated'; -import PasswordInput from '~/components/PasswordInput'; import DashboardModalFooter from '~/concepts/dashboard/DashboardModalFooter'; import { ModelRegistryKind } from '~/k8sTypes'; import { ModelRegistryModel } from '~/api'; -import { createModelRegistryBackend } from '~/services/modelRegistrySettingsService'; +import { + createModelRegistryBackend, + updateModelRegistryBackend, +} from '~/services/modelRegistrySettingsService'; import { isValidK8sName, translateDisplayNameForK8s } from '~/concepts/k8s/utils'; -import NameDescriptionField from '~/concepts/k8s/NameDescriptionField'; -import { NameDescType } from '~/pages/projects/types'; import FormSection from '~/components/pf-overrides/FormSection'; import { AreaContext } from '~/concepts/areas/AreaContext'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; +import K8sNameDescriptionField, { + useK8sNameDescriptionFieldData, +} from '~/concepts/k8s/K8sNameDescriptionField/K8sNameDescriptionField'; import { CreateMRSecureDBSection, SecureDBInfo, SecureDBRType } from './CreateMRSecureDBSection'; +import ModelRegistryDatabasePassword from './ModelRegistryDatabasePassword'; type CreateModalProps = { onClose: () => void; refresh: () => Promise; + modelRegistry?: ModelRegistryKind; }; -const CreateModal: React.FC = ({ onClose, refresh }) => { +const CreateModal: React.FC = ({ onClose, refresh, modelRegistry: mr }) => { const [isSubmitting, setIsSubmitting] = React.useState(false); const [error, setError] = React.useState(); - const [nameDesc, setNameDesc] = React.useState({ - name: '', - k8sName: undefined, - description: '', + const { data: nameDesc, onDataChange: setNameDesc } = useK8sNameDescriptionFieldData({ + initialData: mr, }); const [host, setHost] = React.useState(''); const [port, setPort] = React.useState(''); @@ -69,14 +72,20 @@ const CreateModal: React.FC = ({ onClose, refresh }) => { const existingCertSecrets = ['builder-dockercfg-b7gdr', 'builder-token-hwsps', 'foo-secret']; const existingCertKeys = ['service-ca.crt', 'foo-ca.crt']; + React.useEffect(() => { + if (mr) { + const dbSpec = mr.spec.mysql || mr.spec.postgres; + setHost(dbSpec?.host || 'Unknown'); + setPort(dbSpec?.port?.toString() || 'Unknown'); + setUsername(dbSpec?.username || 'Unknown'); + setDatabase(dbSpec?.database || 'Unknown'); + } + }, [mr]); + const onBeforeClose = () => { setIsSubmitting(false); setError(undefined); - setNameDesc({ - name: '', - k8sName: undefined, - description: '', - }); + setHost(''); setPort(''); setUsername(''); @@ -94,44 +103,76 @@ const CreateModal: React.FC = ({ onClose, refresh }) => { const onSubmit = async () => { setIsSubmitting(true); setError(undefined); - const data: ModelRegistryKind = { - apiVersion: `${ModelRegistryModel.apiGroup}/${ModelRegistryModel.apiVersion}`, - kind: 'ModelRegistry', - metadata: { - name: nameDesc.k8sName || translateDisplayNameForK8s(nameDesc.name), - namespace: dscStatus?.components?.modelregistry?.registriesNamespace || '', - annotations: { - 'openshift.io/description': nameDesc.description, - 'openshift.io/display-name': nameDesc.name.trim(), - }, - }, - spec: { - grpc: {}, - rest: {}, - istio: { - gateway: { - grpc: { tls: {} }, - rest: { tls: {} }, + + if (mr) { + try { + await updateModelRegistryBackend(mr.metadata.name, { + modelRegistry: { + metadata: { + annotations: { + 'openshift.io/description': nameDesc.description, + 'openshift.io/display-name': nameDesc.name.trim(), + }, + }, + spec: { + mysql: { + host, + port: Number(port), + database, + username, + }, + }, + }, + databasePassword: password, + }); + await refresh(); + onBeforeClose(); + } catch (e) { + if (e instanceof Error) { + setError(e); + } + setIsSubmitting(false); + } + } else { + const data: ModelRegistryKind = { + apiVersion: `${ModelRegistryModel.apiGroup}/${ModelRegistryModel.apiVersion}`, + kind: 'ModelRegistry', + metadata: { + name: nameDesc.k8sName.value || translateDisplayNameForK8s(nameDesc.name), + namespace: dscStatus?.components?.modelregistry?.registriesNamespace || '', + annotations: { + 'openshift.io/description': nameDesc.description, + 'openshift.io/display-name': nameDesc.name.trim(), }, }, - mysql: { - host, - port: Number(port), - database, - username, - skipDBCreation: false, + spec: { + grpc: {}, + rest: {}, + istio: { + gateway: { + grpc: { tls: {} }, + rest: { tls: {} }, + }, + }, + mysql: { + host, + port: Number(port), + database, + username, + skipDBCreation: false, + }, }, - }, - }; - try { - await createModelRegistryBackend({ modelRegistry: data, databasePassword: password }); - await refresh(); - onBeforeClose(); - } catch (e) { - if (e instanceof Error) { - setError(e); + }; + try { + await createModelRegistryBackend({ modelRegistry: data, databasePassword: password }); + await refresh(); + onBeforeClose(); + } catch (e) { + if (e instanceof Error) { + setError(e); + } + setIsSubmitting(false); } - setIsSubmitting(false); } }; @@ -139,7 +180,7 @@ const CreateModal: React.FC = ({ onClose, refresh }) => { const canSubmit = () => !isSubmitting && - isValidK8sName(nameDesc.k8sName || translateDisplayNameForK8s(nameDesc.name)) && + isValidK8sName(nameDesc.k8sName.value || translateDisplayNameForK8s(nameDesc.name)) && hasContent(host) && hasContent(password) && hasContent(port) && @@ -150,7 +191,7 @@ const CreateModal: React.FC = ({ onClose, refresh }) => { return ( @@ -165,26 +206,18 @@ const CreateModal: React.FC = ({ onClose, refresh }) => { } >
- { - setNameDesc(value); - }} - /> + @@ -245,23 +278,14 @@ const CreateModal: React.FC = ({ onClose, refresh }) => { )} - setIsPasswordTouched(true)} - onChange={(_e, value) => setPassword(value)} - validated={isPasswordTouched && !hasContent(password) ? 'error' : 'default'} + - {isPasswordTouched && !hasContent(password) && ( - - - Password cannot be empty - - - )} = ({ roleBindings, refresh, onCreateModelRegistryClick, -}) => ( - - - - - - - - } - rowRenderer={(mr) => ( - { + const [editRegistry, setEditRegistry] = React.useState(); + const [deleteRegistry, setDeleteRegistry] = React.useState(); + return ( + <> +
+ + + + + + + } + rowRenderer={(mr) => ( + setEditRegistry(i)} + onDeleteRegistry={(i) => setDeleteRegistry(i)} + /> + )} + variant="compact" /> - )} - variant="compact" - /> -); + {editRegistry ? ( + setEditRegistry(undefined)} + refresh={refresh} + /> + ) : null} + {deleteRegistry ? ( + setDeleteRegistry(undefined)} + refresh={refresh} + /> + ) : null} + + ); +}; export default ModelRegistriesTable; diff --git a/frontend/src/pages/modelRegistrySettings/ModelRegistriesTableRow.tsx b/frontend/src/pages/modelRegistrySettings/ModelRegistriesTableRow.tsx index 567ff93909..7b785b8b3a 100644 --- a/frontend/src/pages/modelRegistrySettings/ModelRegistriesTableRow.tsx +++ b/frontend/src/pages/modelRegistrySettings/ModelRegistriesTableRow.tsx @@ -5,23 +5,21 @@ import { Button, Tooltip } from '@patternfly/react-core'; import { ModelRegistryKind, RoleBindingKind } from '~/k8sTypes'; import ResourceNameTooltip from '~/components/ResourceNameTooltip'; import { ContextResourceData } from '~/types'; -import ViewDatabaseConfigModal from './ViewDatabaseConfigModal'; -import DeleteModelRegistryModal from './DeleteModelRegistryModal'; import { ModelRegistryTableRowStatus } from './ModelRegistryTableRowStatus'; type ModelRegistriesTableRowProps = { modelRegistry: ModelRegistryKind; roleBindings: ContextResourceData; - refresh: () => Promise; + onEditRegistry: (obj: ModelRegistryKind) => void; + onDeleteRegistry: (obj: ModelRegistryKind) => void; }; const ModelRegistriesTableRow: React.FC = ({ modelRegistry: mr, roleBindings, - refresh, + onEditRegistry, + onDeleteRegistry, }) => { - const [isDatabaseConfigModalOpen, setIsDatabaseConfigModalOpen] = React.useState(false); - const [isDeleteModalOpen, setIsDeleteModalOpen] = React.useState(false); const navigate = useNavigate(); const filteredRoleBindings = roleBindings.data.filter( (rb) => @@ -30,67 +28,55 @@ const ModelRegistriesTableRow: React.FC = ({ ); return ( - <> - - - - + + + - + - - {isDatabaseConfigModalOpen ? ( - setIsDatabaseConfigModalOpen(false)} + }, + ]} /> - ) : null} - {isDeleteModalOpen ? ( - setIsDeleteModalOpen(false)} - refresh={refresh} - /> - ) : null} - + + ); }; diff --git a/frontend/src/pages/modelRegistrySettings/ModelRegistryDatabasePassword.tsx b/frontend/src/pages/modelRegistrySettings/ModelRegistryDatabasePassword.tsx index e5026aee3b..7e95a833d6 100644 --- a/frontend/src/pages/modelRegistrySettings/ModelRegistryDatabasePassword.tsx +++ b/frontend/src/pages/modelRegistrySettings/ModelRegistryDatabasePassword.tsx @@ -1,23 +1,83 @@ import React from 'react'; -import { Alert } from '@patternfly/react-core'; -import PasswordHiddenText from '~/components/PasswordHiddenText'; +import { Alert, HelperText, HelperTextItem, Skeleton } from '@patternfly/react-core'; +import PasswordInput from '~/components/PasswordInput'; +import { ModelRegistryKind } from '~/k8sTypes'; +import useFetchState, { FetchStateCallbackPromise, NotReadyError } from '~/utilities/useFetchState'; +import { getModelRegistryBackend } from '~/services/modelRegistrySettingsService'; type ModelRegistryDatabasePasswordProps = { - password?: string; - loadError?: Error; + password: string | undefined; + setPassword: (value: string) => void; + showPassword?: boolean; + isPasswordTouched?: boolean; + setIsPasswordTouched: (value: boolean) => void; + editRegistry?: ModelRegistryKind; }; const ModelRegistryDatabasePassword: React.FC = ({ - password, - loadError, + password = '', + setPassword, + showPassword, + isPasswordTouched, + setIsPasswordTouched, + editRegistry: mr, }) => { - if (loadError) { - return ; + const [existingDbPassword, passwordLoaded, passwordLoadError] = useFetchState( + React.useCallback>(async () => { + if (!mr) { + return Promise.reject(new NotReadyError('Model registry does not exist')); + } + + const { databasePassword } = await getModelRegistryBackend(mr.metadata.name); + return databasePassword; + }, [mr]), + undefined, + ); + + React.useEffect(() => { + if (existingDbPassword && mr) { + setPassword(existingDbPassword); + } + }, [existingDbPassword, setPassword, mr]); + + const hasContent = (value: string): boolean => !!value.trim().length; + + if (!passwordLoaded && !passwordLoadError && mr) { + return ; } - if (!password) { - return 'No password'; + + if (passwordLoadError) { + return ( + + ); } - return ; + + return ( + <> + setIsPasswordTouched(true)} + onChange={(_e, value) => setPassword(value)} + validated={isPasswordTouched && !hasContent(password) ? 'error' : 'default'} + /> + {isPasswordTouched && !hasContent(password) && ( + + + Password cannot be empty + + + )} + + ); }; export default ModelRegistryDatabasePassword; diff --git a/frontend/src/pages/modelRegistrySettings/ViewDatabaseConfigModal.tsx b/frontend/src/pages/modelRegistrySettings/ViewDatabaseConfigModal.tsx deleted file mode 100644 index e3ae1dbffc..0000000000 --- a/frontend/src/pages/modelRegistrySettings/ViewDatabaseConfigModal.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react'; -import { - Bullseye, - Button, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - Spinner, -} from '@patternfly/react-core'; -import { Modal } from '@patternfly/react-core/deprecated'; -import spacing from '@patternfly/react-styles/css/utilities/Spacing/spacing'; -import { ModelRegistryKind } from '~/k8sTypes'; -import useFetchState from '~/utilities/useFetchState'; -import { getModelRegistryBackend } from '~/services/modelRegistrySettingsService'; -import ModelRegistryDatabasePassword from './ModelRegistryDatabasePassword'; - -type ViewDatabaseConfigModalProps = { - modelRegistry: ModelRegistryKind; - onClose: () => void; -}; - -const ViewDatabaseConfigModal: React.FC = ({ - modelRegistry: mr, - onClose, -}) => { - const dbSpec = mr.spec.mysql || mr.spec.postgres; - const host = dbSpec?.host || 'Unknown'; - const port = dbSpec?.port || 'Unknown'; - const username = dbSpec?.username || 'Unknown'; - const database = dbSpec?.database || 'Unknown'; - - const [password, passwordLoaded, passwordLoadError] = useFetchState( - React.useCallback(async () => { - const { databasePassword } = await getModelRegistryBackend(mr.metadata.name); - return databasePassword; - }, [mr]), - undefined, - ); - - return ( - - Close - , - ]} - > - {!passwordLoaded && !passwordLoadError ? ( - - - - ) : ( - - - Host - {host} - - - Port - {port} - - - Username - - {username} - - - - Password - - - - - - Database - - {database} - - - - )} - - ); -}; - -export default ViewDatabaseConfigModal; diff --git a/frontend/src/services/modelRegistrySettingsService.ts b/frontend/src/services/modelRegistrySettingsService.ts index 4d5218099a..ce0ba44a14 100644 --- a/frontend/src/services/modelRegistrySettingsService.ts +++ b/frontend/src/services/modelRegistrySettingsService.ts @@ -42,7 +42,7 @@ export const getModelRegistryBackend = ( export const updateModelRegistryBackend = ( modelRegistryName: string, - patch: RecursivePartial, + patch: RecursivePartial, ): Promise => axios .patch(`${registriesUrl}/${modelRegistryName}`, patch)
- - - {mr.metadata.annotations?.['openshift.io/display-name'] || mr.metadata.name} - - - {mr.metadata.annotations?.['openshift.io/description'] && ( -

{mr.metadata.annotations['openshift.io/description']}

- )} -
- - - {filteredRoleBindings.length === 0 ? ( - - - - ) : ( -
+ + + {mr.metadata.annotations?.['openshift.io/display-name'] || mr.metadata.name} + + + {mr.metadata.annotations?.['openshift.io/description'] && ( +

{mr.metadata.annotations['openshift.io/description']}

+ )} +
+ + + {filteredRoleBindings.length === 0 ? ( + + - )} - - setIsDatabaseConfigModalOpen(true), + + ) : ( + + )} + + { + onEditRegistry(mr); }, - { isSeparator: true }, - { - title: 'Delete model registry', - onClick: () => setIsDeleteModalOpen(true), + }, + { + title: 'Delete model registry', + onClick: () => { + onDeleteRegistry(mr); }, - ]} - /> -