From e56b6fa59c2273c8af675adc7d00b06ba25ced4c Mon Sep 17 00:00:00 2001 From: Roman Sergeenko Date: Mon, 11 Nov 2024 15:13:04 +0100 Subject: [PATCH] #RI-6268 - change add database forms --- .../ImportDatabasesDialog.tsx | 80 ---- .../import-databases-dialog/index.ts | 3 - .../styles.module.scss | 19 - redisinsight/ui/src/components/index.ts | 2 - .../OAuthAutodiscovery.tsx | 10 +- .../oauth-autodiscovery/styles.module.scss | 24 +- redisinsight/ui/src/pages/home/HomePage.tsx | 158 ++----- .../AddDatabaseFlowTabs.spec.tsx | 20 + .../AddDatabaseFlowTabs.tsx | 46 ++ .../add-database-flow-tabs/index.ts | 3 + .../add-database-flow-tabs/styles.module.scss | 10 + .../CloudConnectionFormWrapper.tsx | 27 +- .../CloudConnectionForm.spec.tsx | 11 +- .../CloudConnectionForm.tsx | 115 +++-- .../cloud-connection/styles.module.scss | 34 +- .../ClusterConnectionFormWrapper.tsx | 13 +- .../ClusterConnectionForm.tsx | 238 +++++----- .../cluster-connection/styles.module.scss | 4 - .../DatabasesListWrapper.tsx | 49 ++- .../DatabasePanelDialog.spec.tsx | 49 +++ .../DatabasePanelDialog.tsx} | 161 ++++--- .../ModalTitleProvider.tsx | 17 + .../components/database-panel-dialog/index.ts | 3 + .../database-panel-dialog/styles.module.scss | 114 +++++ .../database-panel/DatabasePanel.spec.tsx | 20 - .../home/components/database-panel/index.ts | 3 - .../InstanceConnections.spec.tsx | 21 - .../InstanceConnections.tsx | 150 ------- .../instance-connections/index.ts | 3 - .../database-panel/styles.module.scss | 68 --- .../home/components/form/DatabaseForm.tsx | 79 ++-- .../home/components/form/DbCompressor.tsx | 68 ++- .../pages/home/components/form/DbIndex.tsx | 78 ++-- .../pages/home/components/form/Messages.tsx | 44 +- .../pages/home/components/form/SSHDetails.tsx | 3 +- .../pages/home/components/form/TlsDetails.tsx | 117 +++-- .../form/sentinel/DbInfoSentinel.tsx | 12 +- .../form/sentinel/SentinelHostPort.tsx | 5 +- .../import-database/ImportDatabase.spec.tsx} | 27 +- .../import-database/ImportDatabase.tsx | 208 +++++++++ .../components/ResultsLog/ResultsLog.spec.tsx | 0 .../components/ResultsLog/ResultsLog.tsx | 0 .../components/ResultsLog/index.ts | 0 .../components/ResultsLog/styles.module.scss | 0 .../TableResult/TableResult.spec.tsx | 0 .../components/TableResult/TableResult.tsx | 0 .../components/TableResult/index.ts | 0 .../components/TableResult/styles.module.scss | 0 .../home/components/import-database/index.ts | 3 + .../import-database/styles.module.scss | 34 ++ .../ManualConnectionWrapper.tsx | 35 +- .../ManualConnectionForm.tsx | 413 +++++++----------- .../ManualConnectionFrom.spec.tsx | 4 +- .../components/CloneConnection.tsx | 44 ++ .../components/FooterActions.tsx | 118 +++++ .../SentinelConnectionWrapper.tsx | 3 - .../SentinelConnectionForm.tsx | 64 +-- .../pages/home/components/styles.module.scss | 46 +- .../ui/src/pages/home/constants/database.ts | 7 + .../ui/src/pages/home/styles.module.scss | 41 -- redisinsight/ui/src/pages/home/styles.scss | 5 +- .../edit-connection/EditConnection.tsx | 12 +- .../ui/src/slices/instances/instances.ts | 4 +- .../ui/src/slices/interfaces/instances.ts | 4 +- .../slices/tests/instances/instances.spec.ts | 2 +- .../ui/src/styles/components/_itemList.scss | 2 +- .../ui/src/styles/components/_tabs.scss | 17 + 67 files changed, 1491 insertions(+), 1483 deletions(-) delete mode 100644 redisinsight/ui/src/components/import-databases-dialog/ImportDatabasesDialog.tsx delete mode 100644 redisinsight/ui/src/components/import-databases-dialog/index.ts delete mode 100644 redisinsight/ui/src/components/import-databases-dialog/styles.module.scss create mode 100644 redisinsight/ui/src/pages/home/components/add-database-flow-tabs/AddDatabaseFlowTabs.spec.tsx create mode 100644 redisinsight/ui/src/pages/home/components/add-database-flow-tabs/AddDatabaseFlowTabs.tsx create mode 100644 redisinsight/ui/src/pages/home/components/add-database-flow-tabs/index.ts create mode 100644 redisinsight/ui/src/pages/home/components/add-database-flow-tabs/styles.module.scss create mode 100644 redisinsight/ui/src/pages/home/components/database-panel-dialog/DatabasePanelDialog.spec.tsx rename redisinsight/ui/src/pages/home/components/{database-panel/DatabasePanel.tsx => database-panel-dialog/DatabasePanelDialog.tsx} (65%) create mode 100644 redisinsight/ui/src/pages/home/components/database-panel-dialog/ModalTitleProvider.tsx create mode 100644 redisinsight/ui/src/pages/home/components/database-panel-dialog/index.ts create mode 100644 redisinsight/ui/src/pages/home/components/database-panel-dialog/styles.module.scss delete mode 100644 redisinsight/ui/src/pages/home/components/database-panel/DatabasePanel.spec.tsx delete mode 100644 redisinsight/ui/src/pages/home/components/database-panel/index.ts delete mode 100644 redisinsight/ui/src/pages/home/components/database-panel/instance-connections/InstanceConnections.spec.tsx delete mode 100644 redisinsight/ui/src/pages/home/components/database-panel/instance-connections/InstanceConnections.tsx delete mode 100644 redisinsight/ui/src/pages/home/components/database-panel/instance-connections/index.ts delete mode 100644 redisinsight/ui/src/pages/home/components/database-panel/styles.module.scss rename redisinsight/ui/src/{components/import-databases-dialog/ImportDatabasesDialog.spec.tsx => pages/home/components/import-database/ImportDatabase.spec.tsx} (79%) create mode 100644 redisinsight/ui/src/pages/home/components/import-database/ImportDatabase.tsx rename redisinsight/ui/src/{components/import-databases-dialog => pages/home/components/import-database}/components/ResultsLog/ResultsLog.spec.tsx (100%) rename redisinsight/ui/src/{components/import-databases-dialog => pages/home/components/import-database}/components/ResultsLog/ResultsLog.tsx (100%) rename redisinsight/ui/src/{components/import-databases-dialog => pages/home/components/import-database}/components/ResultsLog/index.ts (100%) rename redisinsight/ui/src/{components/import-databases-dialog => pages/home/components/import-database}/components/ResultsLog/styles.module.scss (100%) rename redisinsight/ui/src/{components/import-databases-dialog => pages/home/components/import-database}/components/TableResult/TableResult.spec.tsx (100%) rename redisinsight/ui/src/{components/import-databases-dialog => pages/home/components/import-database}/components/TableResult/TableResult.tsx (100%) rename redisinsight/ui/src/{components/import-databases-dialog => pages/home/components/import-database}/components/TableResult/index.ts (100%) rename redisinsight/ui/src/{components/import-databases-dialog => pages/home/components/import-database}/components/TableResult/styles.module.scss (100%) create mode 100644 redisinsight/ui/src/pages/home/components/import-database/index.ts create mode 100644 redisinsight/ui/src/pages/home/components/import-database/styles.module.scss create mode 100644 redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/components/CloneConnection.tsx create mode 100644 redisinsight/ui/src/pages/home/components/manual-connection/manual-connection-form/components/FooterActions.tsx diff --git a/redisinsight/ui/src/components/import-databases-dialog/ImportDatabasesDialog.tsx b/redisinsight/ui/src/components/import-databases-dialog/ImportDatabasesDialog.tsx deleted file mode 100644 index 9425fb4682..0000000000 --- a/redisinsight/ui/src/components/import-databases-dialog/ImportDatabasesDialog.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import cx from 'classnames' -import React, { useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' - -import ImportFileModal from 'uiSrc/components/import-file-modal/ImportFileModal' -import { - fetchInstancesAction, - importInstancesSelector, - resetImportInstances, - uploadInstancesFile -} from 'uiSrc/slices/instances/instances' -import { ImportDatabasesData } from 'uiSrc/slices/interfaces' -import { TelemetryEvent, sendEventTelemetry } from 'uiSrc/telemetry' -import { Nullable } from 'uiSrc/utils' - -import ResultsLog from './components/ResultsLog' -import styles from './styles.module.scss' - -export interface Props { - onClose: (isCancelled: boolean) => void -} - -const MAX_MB_FILE = 10 -const MAX_FILE_SIZE = MAX_MB_FILE * 1024 * 1024 - -const ImportDatabasesDialog = ({ onClose }: Props) => { - const { loading, data, error } = useSelector(importInstancesSelector) - const [files, setFiles] = useState>(null) - const [isInvalid, setIsInvalid] = useState(false) - const [isSubmitDisabled, setIsSubmitDisabled] = useState(true) - - const dispatch = useDispatch() - - const onFileChange = (files: FileList | null) => { - setFiles(files) - setIsInvalid(!!files?.length && files?.[0].size > MAX_FILE_SIZE) - setIsSubmitDisabled(!files?.length || files[0].size > MAX_FILE_SIZE) - } - - const handleOnClose = () => { - if (data?.success?.length || data?.partial?.length) { - dispatch(fetchInstancesAction()) - } - onClose(!data) - dispatch(resetImportInstances()) - } - - const onSubmit = () => { - if (files) { - const formData = new FormData() - formData.append('file', files[0]) - - dispatch(uploadInstancesFile(formData)) - - sendEventTelemetry({ - event: TelemetryEvent.CONFIG_DATABASES_REDIS_IMPORT_SUBMITTED - }) - } - } - - return ( - - onClose={handleOnClose} - onFileChange={onFileChange} - onSubmit={onSubmit} - modalClassName={cx(styles.modal, { [styles.result]: !!data })} - title="Import Database Connections" - submitResults={} - loading={loading} - data={data} - error={error} - errorMessage="Failed to add database connections" - invalidMessage={`File should not exceed ${MAX_MB_FILE} MB`} - isInvalid={isInvalid} - isSubmitDisabled={isSubmitDisabled} - /> - ) -} - -export default ImportDatabasesDialog diff --git a/redisinsight/ui/src/components/import-databases-dialog/index.ts b/redisinsight/ui/src/components/import-databases-dialog/index.ts deleted file mode 100644 index 2a083df701..0000000000 --- a/redisinsight/ui/src/components/import-databases-dialog/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import ImportDatabasesDialog from './ImportDatabasesDialog' - -export default ImportDatabasesDialog diff --git a/redisinsight/ui/src/components/import-databases-dialog/styles.module.scss b/redisinsight/ui/src/components/import-databases-dialog/styles.module.scss deleted file mode 100644 index 1202ffd046..0000000000 --- a/redisinsight/ui/src/components/import-databases-dialog/styles.module.scss +++ /dev/null @@ -1,19 +0,0 @@ -.modal { - &.result { - width: 500px !important; - - @media screen and (min-width: 1024px) { - width: 700px !important; - min-width: 700px !important; - } - } - - .result { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - - margin-top: 20px; - } -} diff --git a/redisinsight/ui/src/components/index.ts b/redisinsight/ui/src/components/index.ts index bd10bc65b4..b047d6fec6 100644 --- a/redisinsight/ui/src/components/index.ts +++ b/redisinsight/ui/src/components/index.ts @@ -19,7 +19,6 @@ import GlobalSubscriptions from './global-subscriptions' import MonitorWrapper from './monitor' import PagePlaceholder from './page-placeholder' import BulkActionsConfig from './bulk-actions-config' -import ImportDatabasesDialog from './import-databases-dialog' import OnboardingTour from './onboarding-tour' import CodeBlock from './code-block' import ShowChildByCondition from './show-child-by-condition' @@ -67,7 +66,6 @@ export { ShortcutsFlyout, PagePlaceholder, BulkActionsConfig, - ImportDatabasesDialog, OnboardingTour, CodeBlock, ShowChildByCondition, diff --git a/redisinsight/ui/src/components/oauth/oauth-sso/oauth-autodiscovery/OAuthAutodiscovery.tsx b/redisinsight/ui/src/components/oauth/oauth-sso/oauth-autodiscovery/OAuthAutodiscovery.tsx index 81f1ce3b33..1f87b7395d 100644 --- a/redisinsight/ui/src/components/oauth/oauth-sso/oauth-autodiscovery/OAuthAutodiscovery.tsx +++ b/redisinsight/ui/src/components/oauth/oauth-sso/oauth-autodiscovery/OAuthAutodiscovery.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react' -import { EuiButton, EuiText } from '@elastic/eui' +import { EuiButton, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui' import { useDispatch, useSelector } from 'react-redux' import { useHistory } from 'react-router-dom' import { find } from 'lodash' @@ -108,11 +108,15 @@ const OAuthAutodiscovery = (props: Props) => { {(form: React.ReactNode) => ( <> - Auto-discover subscriptions and add your databases. -
+ Discover subscriptions and add your databases. A new Redis Cloud account will be created for you if you don’t have one.
+ + Get started with +

Redis Cloud account

+ {form} +
diff --git a/redisinsight/ui/src/components/oauth/oauth-sso/oauth-autodiscovery/styles.module.scss b/redisinsight/ui/src/components/oauth/oauth-sso/oauth-autodiscovery/styles.module.scss index 7cf31083f8..f4b21d81ff 100644 --- a/redisinsight/ui/src/components/oauth/oauth-sso/oauth-autodiscovery/styles.module.scss +++ b/redisinsight/ui/src/components/oauth/oauth-sso/oauth-autodiscovery/styles.module.scss @@ -3,10 +3,6 @@ flex-direction: column; align-items: center; - background-color: var(--euiColorLightestShade); - padding: 16px; - border-radius: 4px; - .buttonsContainer { .button { margin: 0 10px; @@ -35,26 +31,30 @@ } } - .title, + .title { + font-size: 28px; + font-weight: 700 !important; + } + .text { - text-align: center; - font-size: 14px !important; font-style: normal; font-weight: 400 !important; line-height: 150% !important; color: var(--htmlColor) !important; - } - - .text { - font-size: 12px !important; + font-size: 13px !important; padding-bottom: 16px; + align-self: flex-start; } .containerAgreement { margin-top: 16px; text-align: left; - } + :global(.euiCheckbox .euiCheckbox__input ~ .euiCheckbox__label) { + line-height: 18px !important; + font-size: 12px !important; + } + } } .withAdvantagesWrapper { diff --git a/redisinsight/ui/src/pages/home/HomePage.tsx b/redisinsight/ui/src/pages/home/HomePage.tsx index ab0e4c04c1..acf26b5d3a 100644 --- a/redisinsight/ui/src/pages/home/HomePage.tsx +++ b/redisinsight/ui/src/pages/home/HomePage.tsx @@ -2,14 +2,9 @@ import { EuiPage, EuiPageBody, EuiPanel, - EuiResizableContainer, - EuiResizeObserver } from '@elastic/eui' -import React, { useCallback, useEffect, useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' -import cx from 'classnames' -import { throttle } from 'lodash' -import DatabasePanel from 'uiSrc/pages/home/components/database-panel' import { clusterSelector, resetDataRedisCluster, resetInstancesRedisCluster, } from 'uiSrc/slices/instances/cluster' import { Nullable, setTitle } from 'uiSrc/utils' import { HomePageTemplate } from 'uiSrc/templates' @@ -41,18 +36,18 @@ import { AddDbType, CREATE_CLOUD_DB_ID } from 'uiSrc/pages/home/constants' import DatabasesList from './components/database-list-component' import DatabaseListHeader from './components/database-list-header' import EmptyMessage from './components/empty-message/EmptyMessage' +import DatabasePanelDialog from './components/database-panel-dialog' import './styles.scss' import styles from './styles.module.scss' -enum RightPanelName { +enum OpenDialogName { AddDatabase = 'add', EditDatabase = 'edit' } const HomePage = () => { - const [width, setWidth] = useState(0) - const [openRightPanel, setOpenRightPanel] = useState>(null) + const [openDialog, setOpenDialog] = useState>(null) const initialDbTypeRef = useRef(AddDbType.manual) const dispatch = useDispatch() @@ -103,20 +98,20 @@ const HomePage = () => { useEffect(() => { if (isChangedInstance) { - setOpenRightPanel(null) + setOpenDialog(null) dispatch(setEditedInstance(null)) } }, [isChangedInstance]) useEffect(() => { if (clusterCredentials || cloudCredentials || sentinelInstance) { - setOpenRightPanel(RightPanelName.AddDatabase) + setOpenDialog(OpenDialogName.AddDatabase) } }, [clusterCredentials, cloudCredentials, sentinelInstance]) useEffect(() => { if (action === UrlHandlingActions.Connect) { - setOpenRightPanel(RightPanelName.AddDatabase) + setOpenDialog(OpenDialogName.AddDatabase) } }, [action, dbConnection]) @@ -150,7 +145,7 @@ const HomePage = () => { const closeEditDialog = () => { dispatch(setEditedInstance(null)) - setOpenRightPanel(null) + setOpenDialog(null) sendEventTelemetry({ event: TelemetryEvent.CONFIG_DATABASES_DATABASE_EDIT_CANCELLED_CLICKED, @@ -164,7 +159,7 @@ const HomePage = () => { dispatch(resetDataRedisCluster()) dispatch(resetDataSentinel()) - setOpenRightPanel(null) + setOpenDialog(null) dispatch(setEditedInstance(null)) if (action === UrlHandlingActions.Connect) { @@ -178,23 +173,23 @@ const HomePage = () => { const handleAddInstance = (addDbType = AddDbType.manual) => { initialDbTypeRef.current = addDbType - setOpenRightPanel(RightPanelName.AddDatabase) + setOpenDialog(OpenDialogName.AddDatabase) dispatch(setEditedInstance(null)) } const handleEditInstance = (editedInstance: Instance) => { if (editedInstance) { dispatch(fetchEditedInstanceAction(editedInstance)) - setOpenRightPanel(RightPanelName.EditDatabase) + setOpenDialog(OpenDialogName.EditDatabase) } } const handleDeleteInstances = (instances: Instance[]) => { if ( instances.find((instance) => instance.id === editedInstance?.id) - && openRightPanel === RightPanelName.EditDatabase + && openDialog === OpenDialogName.EditDatabase ) { dispatch(setEditedInstance(null)) - setOpenRightPanel(null) + setOpenDialog(null) } instances.forEach((instance) => { @@ -202,34 +197,6 @@ const HomePage = () => { }) } - const onResize = ({ width: innerWidth }: { width: number }) => { - setWidth(innerWidth) - } - const onResizeTrottled = useCallback(throttle(onResize, 100), []) - - const InstanceList = () => - (!isInstanceExists && !loading && !loadingChanging ? ( - - - - ) : ( - - {(resizeRef) => ( -
- -
- )} -
- )) - return (
@@ -239,74 +206,39 @@ const HomePage = () => { key="instance-controls" onAddInstance={handleAddInstance} /> +
- - {(EuiResizablePanel, EuiResizableButton) => ( - <> - - - - - - - - {!!openRightPanel && ( - - )} -
- - - )} - + {(!isInstanceExists && !loading && !loadingChanging ? ( + + + + ) : ( + + ))}
diff --git a/redisinsight/ui/src/pages/home/components/add-database-flow-tabs/AddDatabaseFlowTabs.spec.tsx b/redisinsight/ui/src/pages/home/components/add-database-flow-tabs/AddDatabaseFlowTabs.spec.tsx new file mode 100644 index 0000000000..e050ae6488 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/add-database-flow-tabs/AddDatabaseFlowTabs.spec.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import { render, fireEvent, screen } from 'uiSrc/utils/test-utils' + +import { AddDbType } from 'uiSrc/pages/home/constants' +import AddDatabaseFlowTabs from './AddDatabaseFlowTabs' + +describe('AddDatabaseFlowTabs', () => { + it('should render', () => { + expect(render()).toBeTruthy() + }) + + it('should change tab', () => { + const onChange = jest.fn() + render() + + fireEvent.click(screen.getByTestId('add-database_tab_cloud')) + + expect(onChange).toBeCalledWith(AddDbType.cloud) + }) +}) diff --git a/redisinsight/ui/src/pages/home/components/add-database-flow-tabs/AddDatabaseFlowTabs.tsx b/redisinsight/ui/src/pages/home/components/add-database-flow-tabs/AddDatabaseFlowTabs.tsx new file mode 100644 index 0000000000..325f4301be --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/add-database-flow-tabs/AddDatabaseFlowTabs.tsx @@ -0,0 +1,46 @@ +import React, { useCallback } from 'react' +import cx from 'classnames' +import { EuiTab, EuiTabs } from '@elastic/eui' + +import { AddDbType } from 'uiSrc/pages/home/constants' +import styles from './styles.module.scss' + +export interface Props { + connectionType: AddDbType + onChange: (type: AddDbType) => void +} + +const TABS = [ + { id: 'cloud', title: 'Redis Cloud', connectionType: AddDbType.cloud }, + { id: 'software', title: 'Redis Software', connectionType: AddDbType.auto }, + { id: 'manual', title: 'Add manually', connectionType: AddDbType.manual }, + { id: 'import', title: 'Import from file', connectionType: AddDbType.import }, +] + +const AddDatabaseFlowTabs = (props: Props) => { + const { connectionType, onChange } = props + + const renderTabs = useCallback(() => TABS.map(({ id, title, connectionType: type }) => ( + onChange(type)} + data-testid={`add-database_tab_${id}`} + > + {title} + + )), [connectionType]) + + return ( +
+ + {renderTabs()} + +
+ ) +} + +export default AddDatabaseFlowTabs diff --git a/redisinsight/ui/src/pages/home/components/add-database-flow-tabs/index.ts b/redisinsight/ui/src/pages/home/components/add-database-flow-tabs/index.ts new file mode 100644 index 0000000000..75bcd0c88f --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/add-database-flow-tabs/index.ts @@ -0,0 +1,3 @@ +import AddDatabaseFlowTabs from './AddDatabaseFlowTabs' + +export default AddDatabaseFlowTabs diff --git a/redisinsight/ui/src/pages/home/components/add-database-flow-tabs/styles.module.scss b/redisinsight/ui/src/pages/home/components/add-database-flow-tabs/styles.module.scss new file mode 100644 index 0000000000..9cf7de5c49 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/add-database-flow-tabs/styles.module.scss @@ -0,0 +1,10 @@ +.tabsWrapper { + display: flex; + align-items: center; + justify-content: center; + padding: 16px 0; + flex-grow: 0; + + border-top: 1px solid var(--separatorColor); + border-bottom: 1px solid var(--separatorColor); +} diff --git a/redisinsight/ui/src/pages/home/components/cloud-connection/CloudConnectionFormWrapper.tsx b/redisinsight/ui/src/pages/home/components/cloud-connection/CloudConnectionFormWrapper.tsx index 37b25e2f0f..983c770116 100644 --- a/redisinsight/ui/src/pages/home/components/cloud-connection/CloudConnectionFormWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/cloud-connection/CloudConnectionFormWrapper.tsx @@ -1,17 +1,15 @@ -import React, { useEffect, useRef } from 'react' +import React, { useEffect } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useHistory } from 'react-router-dom' import { Pages } from 'uiSrc/constants' import { cloudSelector, fetchSubscriptionsRedisCloud, setSSOFlow } from 'uiSrc/slices/instances/cloud' -import { useResizableFormField } from 'uiSrc/services' import { resetErrors } from 'uiSrc/slices/app/notifications' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import CloudConnectionForm from './cloud-connection-form' export interface Props { - width: number onClose?: () => void } @@ -20,15 +18,12 @@ export interface ICloudConnectionSubmit { secretKey: string } -const CloudConnectionFormWrapper = ({ onClose, width }: Props) => { +const CloudConnectionFormWrapper = ({ onClose }: Props) => { const dispatch = useDispatch() - const formRef = useRef(null) const history = useHistory() const { loading, credentials } = useSelector(cloudSelector) - const [flexGroupClassName, flexItemClassName] = useResizableFormField(formRef, width) - useEffect( () => () => { dispatch(resetErrors()) @@ -49,17 +44,13 @@ const CloudConnectionFormWrapper = ({ onClose, width }: Props) => { } return ( -
- -
+ ) } diff --git a/redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.spec.tsx b/redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.spec.tsx index 5362dfe8d1..90ce30afad 100644 --- a/redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.spec.tsx +++ b/redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.spec.tsx @@ -24,14 +24,11 @@ describe('CloudConnectionForm', () => { it('should not render cloud sso form by default', () => { render() - expect(screen.queryByTestId('use-cloud-account-accordion')).not.toBeInTheDocument() - expect(screen.queryByTestId('use-cloud-keys-accordion')).not.toBeInTheDocument() - expect(screen.getByTestId('access-key')).toBeInTheDocument() expect(screen.getByTestId('secret-key')).toBeInTheDocument() }) - it('should render cloud sso form and collapsible nav groups with feature flag', () => { + it('should render cloud sso form with feature flag', () => { (appFeatureFlagsFeaturesSelector as jest.Mock).mockReturnValue({ cloudSso: { flag: true @@ -40,10 +37,6 @@ describe('CloudConnectionForm', () => { render() - expect(screen.getByTestId('use-cloud-account-accordion')).toBeInTheDocument() - expect(screen.getByTestId('use-cloud-keys-accordion')).toBeInTheDocument() - - expect(screen.getByTestId('access-key')).toBeInTheDocument() - expect(screen.getByTestId('secret-key')).toBeInTheDocument() + expect(screen.getByTestId('oauth-container-social-buttons')).toBeInTheDocument() }) }) diff --git a/redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.tsx b/redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.tsx index 6549ae1cf0..f279049a11 100644 --- a/redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.tsx +++ b/redisinsight/ui/src/pages/home/components/cloud-connection/cloud-connection-form/CloudConnectionForm.tsx @@ -4,25 +4,29 @@ import { FormikErrors, useFormik } from 'formik' import { isEmpty } from 'lodash' import { EuiButton, - EuiCollapsibleNavGroup, EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiForm, EuiFormRow, - EuiLink, + EuiRadioGroup, + EuiSpacer, EuiText, EuiToolTip, EuiWindowEvent, keys, } from '@elastic/eui' +import { useSelector } from 'react-redux' import { validateField } from 'uiSrc/utils/validations' import validationErrors from 'uiSrc/constants/validationErrors' import { FeatureFlagComponent } from 'uiSrc/components' import { FeatureFlags } from 'uiSrc/constants' -import { OAuthAutodiscovery } from 'uiSrc/components/oauth/oauth-sso' +import { CloudConnectionOptions } from 'uiSrc/pages/home/constants' +import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features' import { OAuthSocialSource } from 'uiSrc/slices/interfaces' +import { OAuthAutodiscovery } from 'uiSrc/components/oauth/oauth-sso' +import { MessageCloudApiKeys } from 'uiSrc/pages/home/components/form/Messages' import { ICloudConnectionSubmit } from '../CloudConnectionFormWrapper' import styles from '../styles.module.scss' @@ -30,21 +34,19 @@ import styles from '../styles.module.scss' export interface Props { accessKey: string secretKey: string - flexGroupClassName: string - flexItemClassName: string onClose?: () => void onSubmit: ({ accessKey, secretKey }: ICloudConnectionSubmit) => void loading: boolean } interface ISubmitButton { - onClick: () => void; - submitIsDisabled: boolean; + onClick: () => void + submitIsDisabled: boolean } interface Values { - accessKey: string; - secretKey: string; + accessKey: string + secretKey: string } const fieldDisplayNames: Values = { @@ -52,40 +54,29 @@ const fieldDisplayNames: Values = { secretKey: 'Enter API User Key', } -const Message = () => ( - <> - - {`Enter API keys to discover and add databases. - API keys can be enabled by following the steps - mentioned in the `} - - documentation. - - - -) +const options = [ + { id: CloudConnectionOptions.Account, label: 'Redis Cloud account' }, + { id: CloudConnectionOptions.ApiKeys, label: 'Redis Cloud API keys' }, +] const CloudConnectionForm = (props: Props) => { const { accessKey, secretKey, - flexGroupClassName, - flexItemClassName, onClose, onSubmit, loading, } = props + const { [FeatureFlags.cloudSso]: cloudSsoFeature } = useSelector(appFeatureFlagsFeaturesSelector) + const [domReady, setDomReady] = useState(false) const [errors, setErrors] = useState>( accessKey || secretKey ? {} : fieldDisplayNames ) + const [type, setType] = useState( + cloudSsoFeature?.flag ? CloudConnectionOptions.Account : CloudConnectionOptions.ApiKeys + ) useEffect(() => { setDomReady(true) @@ -124,10 +115,11 @@ const CloudConnectionForm = (props: Props) => { const CancelButton = ({ onClick }: { onClick: () => void }) => ( Cancel @@ -150,6 +142,7 @@ const CloudConnectionForm = (props: Props) => { > { return null } - const CloudApiForm = ( -
- -
+ const CloudApiForm = () => ( +
+ + - - + + { - - + + { ) return ( - <> -
- - Connect with: - - - - - - - {CloudApiForm} - - -
- +
+ + + Connect with: + + setType(id as CloudConnectionOptions)} + data-testid="cloud-options" + /> + + + + + {type === CloudConnectionOptions.Account && ()} + {type === CloudConnectionOptions.ApiKeys && ()} +
) } diff --git a/redisinsight/ui/src/pages/home/components/cloud-connection/styles.module.scss b/redisinsight/ui/src/pages/home/components/cloud-connection/styles.module.scss index 2bb64eae6c..492755fc01 100644 --- a/redisinsight/ui/src/pages/home/components/cloud-connection/styles.module.scss +++ b/redisinsight/ui/src/pages/home/components/cloud-connection/styles.module.scss @@ -1,9 +1,4 @@ .cloudApi { - margin-top: 5px; - padding: 16px; - background-color: var(--euiColorLightestShade); - border-radius: 4px; - :global(.euiTextColor), :global(.euiLink) { color: currentColor !important; @@ -52,33 +47,16 @@ } .message { - padding: 0 26px; - text-align: center; font-family: "Graphik", sans-serif; color: var(--euiTextSubduedColor) !important; } -.accordion { - background: var(--euiColorLightestShade); - border-radius: 4px; - padding: 12px 18px !important; - margin-top: 14px !important; - - :global { - .euiAccordion__triggerWrapper { - background: var(--euiColorLightestShade); - padding: 0 !important; - } - - .euiAccordion__triggerWrapper h3 { - font-size: 14px !important; - line-height: 14px !important; - font-weight: 400 !important; - height: auto !important; - } +.cloudOptions { + display: flex; + align-items: center; - .euiCollapsibleNavGroup__children { - padding: 12px 0 0 !important; - } + :global(.euiRadioGroup__item) { + margin-top: 0 !important; + margin-right: 12px; } } diff --git a/redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.tsx b/redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.tsx index 38379a24b7..4044c11705 100644 --- a/redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/cluster-connection/ClusterConnectionFormWrapper.tsx @@ -7,7 +7,6 @@ import { fetchInstancesRedisCluster, } from 'uiSrc/slices/instances/cluster' import { Pages } from 'uiSrc/constants' -import { useResizableFormField } from 'uiSrc/services' import { resetErrors } from 'uiSrc/slices/app/notifications' import { ICredentialsRedisCluster, InstanceType } from 'uiSrc/slices/interfaces' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' @@ -16,11 +15,10 @@ import { autoFillFormDetails } from 'uiSrc/pages/home/utils' import ClusterConnectionForm from './cluster-connection-form/ClusterConnectionForm' export interface Props { - width: number; - onClose?: () => void; + onClose?: () => void } -const ClusterConnectionFormWrapper = ({ onClose, width }: Props) => { +const ClusterConnectionFormWrapper = ({ onClose }: Props) => { const [initialValues, setInitialValues] = useState({ host: '', port: '', @@ -35,11 +33,6 @@ const ClusterConnectionFormWrapper = ({ onClose, width }: Props) => { const { loading, credentials } = useSelector(clusterSelector) - const [flexGroupClassName, flexItemClassName] = useResizableFormField( - formRef, - width - ) - useEffect( () => () => { dispatch(resetErrors()) @@ -83,8 +76,6 @@ const ClusterConnectionFormWrapper = ({ onClose, width }: Props) => { password={credentials?.password ?? ''} initialValues={initialValues} onHostNamePaste={handlePostHostName} - flexGroupClassName={flexGroupClassName} - flexItemClassName={flexItemClassName} onClose={onClose} onSubmit={formSubmit} loading={loading} diff --git a/redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm.tsx b/redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm.tsx index 74d06ba718..b679ec3336 100644 --- a/redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm.tsx +++ b/redisinsight/ui/src/pages/home/components/cluster-connection/cluster-connection-form/ClusterConnectionForm.tsx @@ -12,8 +12,6 @@ import { EuiForm, EuiFormRow, EuiIcon, - EuiLink, - EuiText, EuiToolTip, EuiWindowEvent, keys, @@ -24,25 +22,22 @@ import { validateField, validatePortNumber, } from 'uiSrc/utils/validations' -import { APPLICATION_NAME } from 'uiSrc/constants' import { handlePasteHostName } from 'uiSrc/utils' import validationErrors from 'uiSrc/constants/validationErrors' import { ICredentialsRedisCluster } from 'uiSrc/slices/interfaces' -import styles from '../styles.module.scss' +import { MessageEnterpriceSoftware } from 'uiSrc/pages/home/components/form/Messages' export interface Props { - host: string; - port: string; - username: string; - password: string; - onHostNamePaste: (text: string) => boolean; - flexGroupClassName: string; - flexItemClassName: string; - onClose?: () => void; - initialValues: Values; - onSubmit: (values: ICredentialsRedisCluster) => void; - loading: boolean; + host: string + port: string + username: string + password: string + onHostNamePaste: (text: string) => boolean + onClose?: () => void + initialValues: Values + onSubmit: (values: ICredentialsRedisCluster) => void + loading: boolean } interface ISubmitButton { @@ -65,26 +60,6 @@ const fieldDisplayNames: Values = { password: 'Admin Password', } -const Message = () => ( - - Your Redis Enterprise databases can be automatically added. Enter the - connection details of your Redis Enterprise Cluster to automatically - discover your databases and add them to - {' '} - {APPLICATION_NAME} - .   - - Learn more here. - - -) - const ClusterConnectionForm = (props: Props) => { const { host, @@ -93,8 +68,6 @@ const ClusterConnectionForm = (props: Props) => { password, initialValues: initialValuesProp, onHostNamePaste, - flexGroupClassName, - flexItemClassName, onClose, onSubmit, loading, @@ -191,10 +164,11 @@ const ClusterConnectionForm = (props: Props) => { const CancelButton = ({ onClick }: { onClick: () => void }) => ( Cancel @@ -219,6 +193,7 @@ const ClusterConnectionForm = (props: Props) => { > { } return ( - <> -
- -
- - - - - - - ) => { - formik.setFieldValue( - e.target.name, - validateField(e.target.value.trim()) - ) - }} - onPaste={(event: React.ClipboardEvent) => - handlePasteHostName(onHostNamePaste, event)} - append={} - /> - - +
+ +
- - - ) => { - formik.setFieldValue( - e.target.name, - validatePortNumber(e.target.value.trim()) - ) - }} - type="text" - min={0} - max={MAX_PORT_NUMBER} - /> - - - + + + + + + ) => { + formik.setFieldValue( + e.target.name, + validateField(e.target.value.trim()) + ) + }} + onPaste={(event: React.ClipboardEvent) => + handlePasteHostName(onHostNamePaste, event)} + append={} + /> + + - - - - - - + + + ) => { + formik.setFieldValue( + e.target.name, + validatePortNumber(e.target.value.trim()) + ) + }} + type="text" + min={0} + max={MAX_PORT_NUMBER} + /> + + + - - - - - - - + + + + + + -
-
- + + + + + +
+
+
+
) } diff --git a/redisinsight/ui/src/pages/home/components/cluster-connection/styles.module.scss b/redisinsight/ui/src/pages/home/components/cluster-connection/styles.module.scss index f41cbcc0de..00727cf936 100644 --- a/redisinsight/ui/src/pages/home/components/cluster-connection/styles.module.scss +++ b/redisinsight/ui/src/pages/home/components/cluster-connection/styles.module.scss @@ -1,10 +1,6 @@ .message { - margin-top: 5px; - padding: 16px; - background-color: var(--euiColorLightestShade); font-family: 'Graphik', sans-serif; color: var(--euiTextSubduedColor) !important; - border: 1px solid var(--euiColorLightShade); :global(.euiTextColor), :global(.euiLink) { color: currentColor !important; diff --git a/redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.tsx b/redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.tsx index 7e94117249..f36f69fe5a 100644 --- a/redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.tsx +++ b/redisinsight/ui/src/pages/home/components/database-list-component/DatabasesListWrapper.tsx @@ -2,7 +2,7 @@ import { Criteria, EuiButtonIcon, EuiIcon, - EuiLink, + EuiLink, EuiResizeObserver, EuiTableFieldDataColumnType, EuiText, EuiTextColor, @@ -60,7 +60,6 @@ export interface Props { instances: Instance[] predefinedInstances?: Instance[] loading: boolean - width: number editedInstance: Nullable onEditInstance: (instance: Instance) => void onDeleteInstances: (instances: Instance[]) => void @@ -73,7 +72,6 @@ const isCreateCloudDb = (id?: string) => id === CREATE_CLOUD_DB_ID const DatabasesListWrapper = (props: Props) => { const { instances, - width, predefinedInstances = [], onEditInstance, editedInstance, @@ -88,6 +86,7 @@ const DatabasesListWrapper = (props: Props) => { const { contextInstanceId } = useSelector(appContextSelector) const { [FeatureFlags.cloudSso]: cloudSsoFeature } = useSelector(appFeatureFlagsFeaturesSelector) + const [width, setWidth] = useState(0) const [, forceRerender] = useState({}) const sortingRef = useRef( localStorageService.get(BrowserStorageItem.instancesSorting) ?? { @@ -121,10 +120,6 @@ const DatabasesListWrapper = (props: Props) => { } }, [instances, search]) - useEffect(() => { - closePopover() - }, [width]) - const handleCopy = (text = '', databaseId?: string) => { navigator.clipboard?.writeText(text) sendEventTelemetry({ @@ -235,6 +230,10 @@ const DatabasesListWrapper = (props: Props) => { ) } + const onResize = ({ width: innerWidth }: { width: number }) => { + setWidth(innerWidth) + } + const handleClickGoToCloud = () => { sendEventTelemetry({ event: TelemetryEvent.CLOUD_LINK_CLICKED, @@ -487,22 +486,26 @@ const DatabasesListWrapper = (props: Props) => { ] return ( -
- - width={width} - columns={columns} - columnsToHide={COLS_TO_HIDE} - onDelete={handleDeleteInstances} - onExport={handleExportInstances} - onWheel={closePopover} - loading={loading} - data={listOfInstances} - rowProps={getRowProps} - getSelectableItems={(item) => item.id !== 'create-free-cloud-db'} - onTableChange={onTableChange} - sort={sortingRef.current} - /> -
+ + {(resizeRef) => ( +
+ + width={width} + columns={columns} + columnsToHide={COLS_TO_HIDE} + onDelete={handleDeleteInstances} + onExport={handleExportInstances} + onWheel={closePopover} + loading={loading} + data={listOfInstances} + rowProps={getRowProps} + getSelectableItems={(item) => item.id !== 'create-free-cloud-db'} + onTableChange={onTableChange} + sort={sortingRef.current} + /> +
+ )} +
) } diff --git a/redisinsight/ui/src/pages/home/components/database-panel-dialog/DatabasePanelDialog.spec.tsx b/redisinsight/ui/src/pages/home/components/database-panel-dialog/DatabasePanelDialog.spec.tsx new file mode 100644 index 0000000000..fe54e0adad --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/database-panel-dialog/DatabasePanelDialog.spec.tsx @@ -0,0 +1,49 @@ +import React from 'react' +import { instance, mock } from 'ts-mockito' +import { render, screen, fireEvent, act } from 'uiSrc/utils/test-utils' + +import DatabasePanelDialog, { Props } from './DatabasePanelDialog' + +const mockedProps = mock() + +describe('DatabasePanelDialog', () => { + it('should render', () => { + expect(render()).toBeTruthy() + }) + + it('should change tab to cloud and render proper form', () => { + render() + + fireEvent.click(screen.getByTestId('add-database_tab_cloud')) + + expect(screen.getByTestId('add-db_cloud-api')).toBeInTheDocument() + }) + + it('should change tab to software and render proper form', () => { + render() + + fireEvent.click(screen.getByTestId('add-database_tab_software')) + + expect(screen.getByTestId('add-db_cluster')).toBeInTheDocument() + }) + + it('should change tab to software sentinel and render proper form', async () => { + render() + + fireEvent.click(screen.getByTestId('add-database_tab_software')) + + await act(async () => { + fireEvent.click(document.querySelector('[data-test-subj="radio-btn-sentinel"] label') as Element) + }) + + expect(screen.getByTestId('add-db_sentinel')).toBeInTheDocument() + }) + + it('should change tab to import render proper form', async () => { + render() + + fireEvent.click(screen.getByTestId('add-database_tab_import')) + + expect(screen.getByTestId('add-db_import')).toBeInTheDocument() + }) +}) diff --git a/redisinsight/ui/src/pages/home/components/database-panel/DatabasePanel.tsx b/redisinsight/ui/src/pages/home/components/database-panel-dialog/DatabasePanelDialog.tsx similarity index 65% rename from redisinsight/ui/src/pages/home/components/database-panel/DatabasePanel.tsx rename to redisinsight/ui/src/pages/home/components/database-panel-dialog/DatabasePanelDialog.tsx index dc450d3f9b..7d665a5a45 100644 --- a/redisinsight/ui/src/pages/home/components/database-panel/DatabasePanel.tsx +++ b/redisinsight/ui/src/pages/home/components/database-panel-dialog/DatabasePanelDialog.tsx @@ -1,62 +1,63 @@ +import React, { useEffect, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' import { - EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiForm, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, EuiRadioGroup, - EuiRadioGroupOption, + EuiRadioGroupOption, EuiSpacer, EuiText, - EuiTitle, - EuiToolTip, + EuiTitle } from '@elastic/eui' -import React, { useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' import cx from 'classnames' import { Nullable } from 'uiSrc/utils' -import { cloudSelector, resetDataRedisCloud } from 'uiSrc/slices/instances/cloud' -import { clusterSelector, resetDataRedisCluster } from 'uiSrc/slices/instances/cluster' -import { Instance, InstanceType } from 'uiSrc/slices/interfaces' -import { sentinelSelector, resetDataSentinel } from 'uiSrc/slices/instances/sentinel' - import { UrlHandlingActions } from 'uiSrc/slices/interfaces/urlHandling' -import { appRedirectionSelector, setUrlHandlingInitialState } from 'uiSrc/slices/app/url-handling' +import { Instance, InstanceType } from 'uiSrc/slices/interfaces' import { AddDbType } from 'uiSrc/pages/home/constants' -import ClusterConnectionFormWrapper from 'uiSrc/pages/home/components/cluster-connection' -import CloudConnectionFormWrapper from 'uiSrc/pages/home/components/cloud-connection' -import SentinelConnectionWrapper from 'uiSrc/pages/home/components/sentinel-connection' +import { clusterSelector, resetDataRedisCluster } from 'uiSrc/slices/instances/cluster' +import { cloudSelector, resetDataRedisCloud } from 'uiSrc/slices/instances/cloud' +import { resetDataSentinel, sentinelSelector } from 'uiSrc/slices/instances/sentinel' +import { appRedirectionSelector, setUrlHandlingInitialState } from 'uiSrc/slices/app/url-handling' + import ManualConnectionWrapper from 'uiSrc/pages/home/components/manual-connection' -import InstanceConnections from 'uiSrc/pages/home/components/database-panel/instance-connections' +import SentinelConnectionWrapper from 'uiSrc/pages/home/components/sentinel-connection' +import ClusterConnectionFormWrapper from 'uiSrc/pages/home/components/cluster-connection' +import AddDatabaseFlowTabs from 'uiSrc/pages/home/components/add-database-flow-tabs' +import CloudConnectionFormWrapper from 'uiSrc/pages/home/components/cloud-connection' +import ImportDatabase from 'uiSrc/pages/home/components/import-database' +import { HeaderProvider } from './ModalTitleProvider' import styles from './styles.module.scss' export interface Props { - width: number - isResizablePanel?: boolean + isOpen: boolean editMode: boolean urlHandlingAction?: Nullable initialValues?: Nullable> editedInstance: Nullable - onClose?: () => void + onClose: () => void onDbEdited?: () => void - onAliasEdited?: (value: string) => void - isFullWidth?: boolean initConnectionType?: AddDbType } -const DatabasePanel = React.memo((props: Props) => { +const DatabasePanelDialog = (props: Props) => { const { + isOpen, editMode, - isResizablePanel, onClose, - isFullWidth: isFullWidthProp = false, initConnectionType = AddDbType.manual } = props const [typeSelected, setTypeSelected] = useState( - InstanceType.RedisCloudPro + InstanceType.RedisEnterpriseCluster ) const [connectionType, setConnectionType] = useState(initConnectionType) - const [isFullWidth, setIsFullWidth] = useState(isFullWidthProp) + const [headerContent, setHeaderContent] = useState>(null) const { credentials: clusterCredentials } = useSelector(clusterSelector) const { credentials: cloudCredentials } = useSelector(cloudSelector) @@ -96,7 +97,7 @@ const DatabasePanel = React.memo((props: Props) => { }, [editMode]) useEffect(() => - // ComponentWillUnmount + // ComponentWillUnmount () => { if (connectionType === AddDbType.manual) return @@ -122,16 +123,7 @@ const DatabasePanel = React.memo((props: Props) => { }, [typeSelected]) - useEffect(() => { - setIsFullWidth(isFullWidthProp) - }, [isFullWidthProp]) - const typesFormStage: EuiRadioGroupOption[] = [ - { - id: InstanceType.RedisCloudPro, - label: InstanceType.RedisCloudPro, - 'data-test-subj': 'radio-btn-cloud-pro', - }, { id: InstanceType.RedisEnterpriseCluster, label: InstanceType.RedisEnterpriseCluster, @@ -144,8 +136,6 @@ const DatabasePanel = React.memo((props: Props) => { }, ] - const radioBtnLegend = isResizablePanel ? '' : Connect to: - const onChange = (optionId: InstanceType) => { setTypeSelected(optionId) } @@ -157,29 +147,25 @@ const DatabasePanel = React.memo((props: Props) => { const InstanceTypes = () => ( - + - Connect to: + Connect with: onChange(id as InstanceType)} name="radio group" - legend={{ - children: radioBtnLegend, - }} data-testid="db-types" /> + ) @@ -188,55 +174,54 @@ const DatabasePanel = React.memo((props: Props) => { {connectionType === AddDbType.manual && ( )} + {connectionType === AddDbType.cloud && ( + + )} + {connectionType === AddDbType.import && ( + + )} {connectionType === AddDbType.auto && ( <> - {typeSelected === InstanceType.Sentinel && ( - - )} - {typeSelected === InstanceType.RedisEnterpriseCluster && ( - - )} - {typeSelected === InstanceType.RedisCloudPro && ( - - )} + {typeSelected === InstanceType.Sentinel && ()} + {typeSelected === InstanceType.RedisEnterpriseCluster && ()} )} ) + if (!isOpen) return null + return ( -
-
- {!isFullWidth && onClose && ( - - - - )} - {!editMode && ( - <> - -

Discover and Add Redis Databases

-
- + + + {headerContent ?? (

Discover and Add Redis Databases

)} +
+
+ +
+ {!editMode && ( + + )} +
{connectionType === AddDbType.auto && } - - )} - {Form()} -
-
-
+ + {Form()} + +
+
+ + +
+ + ) -}) +} -export default DatabasePanel +export default DatabasePanelDialog diff --git a/redisinsight/ui/src/pages/home/components/database-panel-dialog/ModalTitleProvider.tsx b/redisinsight/ui/src/pages/home/components/database-panel-dialog/ModalTitleProvider.tsx new file mode 100644 index 0000000000..793e60ad92 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/database-panel-dialog/ModalTitleProvider.tsx @@ -0,0 +1,17 @@ +import React, { createContext, useContext } from 'react' +import { Nullable } from 'uiSrc/utils' + +interface HeaderContextType { + headerContent: Nullable + setHeaderContent: (content: Nullable) => void +} + +// Create a context +const HeaderContext = createContext({ + headerContent: null, + setHeaderContent: () => {} +}) + +// Custom hook to access the header context +export const useModalHeader = () => useContext(HeaderContext) +export const HeaderProvider = HeaderContext.Provider diff --git a/redisinsight/ui/src/pages/home/components/database-panel-dialog/index.ts b/redisinsight/ui/src/pages/home/components/database-panel-dialog/index.ts new file mode 100644 index 0000000000..2ad58bb04f --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/database-panel-dialog/index.ts @@ -0,0 +1,3 @@ +import DatabasePanelDialog from './DatabasePanelDialog' + +export default DatabasePanelDialog diff --git a/redisinsight/ui/src/pages/home/components/database-panel-dialog/styles.module.scss b/redisinsight/ui/src/pages/home/components/database-panel-dialog/styles.module.scss new file mode 100644 index 0000000000..4f27d04fcd --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/database-panel-dialog/styles.module.scss @@ -0,0 +1,114 @@ +.modal { + width: 900px !important; + height: 700px !important; + + max-width: calc(100vw - 120px) !important; + max-height: calc(100vh - 120px) !important; + + &:global(.euiModal) { + background-color: var(--euiColorEmptyShade) !important; + } + + :global { + .euiModalHeader { + padding: 18px 24px; + + .euiModalHeader__title .euiTitle { + font-size: 18px; + } + } + + .euiModalBody__overflow { + padding: 8px 30px; + overflow-y: hidden !important; + mask-image: none !important; + } + + .euiModal__closeIcon { + top: 16px !important; + right: 16px !important; + background: none; + } + + .euiModalFooter { + display: block; + margin-top: 12px; + } + + .footerAddDatabase { + display: flex; + align-items: center; + justify-content: flex-end; + } + } + + .bodyWrapper { + height: 100%; + overflow: hidden; + + display: flex; + flex-direction: column; + } + + .formWrapper { + @include eui.scrollBar; + flex-grow: 1; + overflow-y: auto; + padding: 16px 24px; + + .softwareTypes { + display: flex; + align-items: center; + + :global(.euiRadioGroup__item) { + margin-top: 0; + margin-right: 12px; + } + } + } +} + +/* form override */ +.modal .formWrapper { + + :global { + .form__divider { + padding: 18px 0; + } + + .euiFieldText, + .euiFieldNumber, + .euiFieldPassword, + .euiFieldSearch, + .euiSelect, + .euiSuperSelectControl, + .euiComboBox .euiComboBox__inputWrap, + .euiTextArea { + background-color: var(--browserTableRowEven) !important; + padding: 12px; + border-color: var(--separatorColor) !important; + } + + .euiFormControlLayout--group { + border-color: var(--separatorColor) !important; + } + + .euiFormRow, .euiFormControlLayout { + max-width: none; + + .euiSuperSelectControl:not(.euiSuperSelectControl--compressed), + .euiSelect:not(.euiSelect--compressed), + .euiFormControlLayout:not(.euiFormControlLayout--compressed), + .euiFieldText:not(.euiFieldText--compressed), + .euiFieldNumber:not(.euiFieldNumber--compressed), + .euiFieldPassword { + height: 43px !important; + } + } + + .euiCheckbox__input~.euiCheckbox__label { + line-height: 24px !important; + font-size: 14px !important; + } + } +} diff --git a/redisinsight/ui/src/pages/home/components/database-panel/DatabasePanel.spec.tsx b/redisinsight/ui/src/pages/home/components/database-panel/DatabasePanel.spec.tsx deleted file mode 100644 index 75a451f387..0000000000 --- a/redisinsight/ui/src/pages/home/components/database-panel/DatabasePanel.spec.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import { instance, mock } from 'ts-mockito' -import { render, screen, fireEvent } from 'uiSrc/utils/test-utils' -import DatabasePanel, { Props } from './DatabasePanel' - -const mockedProps = mock() - -describe('DatabasePanel', () => { - it('should render', () => { - expect( - render() - ).toBeTruthy() - }) - - it('should render instance types after click on auto discover', () => { - render() - fireEvent.click(screen.getByTestId('add-auto')) - expect(screen.getByTestId('db-types')).toBeInTheDocument() - }) -}) diff --git a/redisinsight/ui/src/pages/home/components/database-panel/index.ts b/redisinsight/ui/src/pages/home/components/database-panel/index.ts deleted file mode 100644 index 18eecda936..0000000000 --- a/redisinsight/ui/src/pages/home/components/database-panel/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import DatabasePanel from './DatabasePanel' - -export default DatabasePanel diff --git a/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/InstanceConnections.spec.tsx b/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/InstanceConnections.spec.tsx deleted file mode 100644 index 442d34bc49..0000000000 --- a/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/InstanceConnections.spec.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react' -import { instance, mock } from 'ts-mockito' -import { render, screen, fireEvent } from 'uiSrc/utils/test-utils' -import InstanceConnections, { Props } from './InstanceConnections' - -const mockedProps = mock() - -describe('InstanceConnections', () => { - it('should render', () => { - expect( - render() - ).toBeTruthy() - }) - - it('should call changeConnectionType after change connection type', () => { - const changeConnectionType = jest.fn() - render() - fireEvent.click(screen.getByTestId('add-auto')) - expect(changeConnectionType).toBeCalled() - }) -}) diff --git a/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/InstanceConnections.tsx b/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/InstanceConnections.tsx deleted file mode 100644 index a5703dcf5d..0000000000 --- a/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/InstanceConnections.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import React, { useContext } from 'react' -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui' -import cx from 'classnames' - -import { Theme } from 'uiSrc/constants' -import { ThemeContext } from 'uiSrc/contexts/themeContext' - -import ActiveManualSvg from 'uiSrc/assets/img/active_manual.svg' -import NotActiveManualSvg from 'uiSrc/assets/img/not_active_manual.svg' -import ActiveAutoSvg from 'uiSrc/assets/img/active_auto.svg' -import NotActiveAutoSvg from 'uiSrc/assets/img/not_active_auto.svg' -import LightActiveManualSvg from 'uiSrc/assets/img/light_theme/active_manual.svg' -import LightNotActiveManualSvg from 'uiSrc/assets/img/light_theme/n_active_manual.svg' -import LightActiveAutoSvg from 'uiSrc/assets/img/light_theme/active_auto.svg' -import LightNotActiveAutoSvg from 'uiSrc/assets/img/light_theme/n_active_auto.svg' -import { AddDbType } from 'uiSrc/pages/home/constants' - -import styles from '../styles.module.scss' - -export interface Props { - connectionType: AddDbType, - changeConnectionType: (connectionType: AddDbType) => void, - isFullWidth: boolean -} - -const InstanceConnections = React.memo((props: Props) => { - const { connectionType, changeConnectionType, isFullWidth } = props - const { theme } = useContext(ThemeContext) - - const AddDatabaseManually = () => ( -
- Add Database Manually -
- ) - - const AutoDiscoverDatabase = () => ( -
- Autodiscover Databases -
- ) - - const getProperManualImage = () => { - if (theme === Theme.Dark) { - return connectionType === AddDbType.manual ? ActiveManualSvg : NotActiveManualSvg - } - return connectionType === AddDbType.manual ? LightActiveManualSvg : LightNotActiveManualSvg - } - - const getProperAutoImage = () => { - if (theme === Theme.Dark) { - return connectionType === AddDbType.auto ? ActiveAutoSvg : NotActiveAutoSvg - } - return connectionType === AddDbType.auto ? LightActiveAutoSvg : LightNotActiveAutoSvg - } - - return ( -
- - changeConnectionType(AddDbType.manual)} - grow={1} - data-testid="add-manual" - > - - - - - - - {!isFullWidth && ( - - - - )} - - - - {isFullWidth && ()} - - Use Host and Port to connect to your Redis Database - - - - - changeConnectionType(AddDbType.auto)} - grow={1} - data-testid="add-auto" - > - - - - - - - {!isFullWidth && ( - - - - )} - - - - {isFullWidth && ()} - - Use discovery tools to automatically discover and add your Redis Databases - - - - - -
- ) -}) - -export default InstanceConnections diff --git a/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/index.ts b/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/index.ts deleted file mode 100644 index 162026205d..0000000000 --- a/redisinsight/ui/src/pages/home/components/database-panel/instance-connections/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import InstanceConnections from './InstanceConnections' - -export default InstanceConnections diff --git a/redisinsight/ui/src/pages/home/components/database-panel/styles.module.scss b/redisinsight/ui/src/pages/home/components/database-panel/styles.module.scss deleted file mode 100644 index cef767dbe5..0000000000 --- a/redisinsight/ui/src/pages/home/components/database-panel/styles.module.scss +++ /dev/null @@ -1,68 +0,0 @@ -.closeKeyTooltip { - position: absolute; - top: 8px; - right: 10px; - z-index: 1; - - svg { - width: 20px; - height: 20px; - } -} - -.connectionTypesContainer { - max-width: 752px; - margin-bottom: 25px; -} - -.connectionType { - background-color: var(--euiColorLightestShade); - padding: 20px 16px; - cursor: pointer; - - > div { - color: var(--euiTextSubduedColorHover) !important; - } -} - - -.selectedConnectionType { - outline: 1px solid var(--euiColorPrimary); -} - -.connectionTypeTitle { - font-size: 16px; - line-height: 20px; - font-weight: 500; - letter-spacing: 0; -} - -.connectionTypeTitleFullWidth { - margin-bottom: 6px; -} - -.connectionIcon { - width: 55px !important; - height: 60px !important; -} - -.descriptionNotFullWidth { - margin-top: -6px; -} - -.radioBtnText { - display: block; - margin-right: 10px; - flex-basis: 100% !important; - - div { - font-size: 13px !important; - } -} - -.radioBtnTextFullWidth { - flex-basis: auto !important; - justify-content: center; - padding-bottom: 6px; - margin-right: 20px !important; -} diff --git a/redisinsight/ui/src/pages/home/components/form/DatabaseForm.tsx b/redisinsight/ui/src/pages/home/components/form/DatabaseForm.tsx index f87fba83ec..6d4a0a811e 100644 --- a/redisinsight/ui/src/pages/home/components/form/DatabaseForm.tsx +++ b/redisinsight/ui/src/pages/home/components/form/DatabaseForm.tsx @@ -33,22 +33,20 @@ interface IShowFields { } export interface Props { - flexGroupClassName?: string - flexItemClassName?: string formik: FormikProps onHostNamePaste: (content: string) => boolean showFields: IShowFields autoFocus?: boolean + readyOnlyFields?: string[] } const DatabaseForm = (props: Props) => { const { - flexGroupClassName = '', - flexItemClassName = '', formik, onHostNamePaste, autoFocus = false, showFields, + readyOnlyFields = [] } = props const { server } = useSelector(appInfoSelector) @@ -89,11 +87,34 @@ const DatabaseForm = (props: Props) => { ) + const isShowPort = server?.buildType !== BuildType.RedisStack && showFields.port + const isFieldDisabled = (name: string) => readyOnlyFields.includes(name) + return ( <> - + {showFields.alias && ( + + + + + + + + )} + {showFields.host && ( - + { onPaste={(event: React.ClipboardEvent) => handlePasteHostName(onHostNamePaste, event)} onFocus={selectOnFocus} append={} + disabled={isFieldDisabled('host')} /> )} - {server?.buildType !== BuildType.RedisStack && showFields.port && ( - + {isShowPort && ( + { type="text" min={0} max={MAX_PORT_NUMBER} + disabled={isFieldDisabled('port')} /> )} - {showFields.alias && ( - - - - - - - - )} - - - + + { placeholder="Enter Username" value={formik.values.username ?? ''} onChange={formik.handleChange} + disabled={isFieldDisabled('username')} /> - + { }} dualToggleProps={{ color: 'text' }} autoComplete="new-password" + disabled={isFieldDisabled('password')} /> + - {showFields.timeout && ( - + {showFields.timeout && ( + + { type="text" min={1} max={MAX_TIMEOUT_NUMBER} + disabled={isFieldDisabled('timeout')} /> - )} - + + + )} ) } diff --git a/redisinsight/ui/src/pages/home/components/form/DbCompressor.tsx b/redisinsight/ui/src/pages/home/components/form/DbCompressor.tsx index 92511938bc..75264b429a 100644 --- a/redisinsight/ui/src/pages/home/components/form/DbCompressor.tsx +++ b/redisinsight/ui/src/pages/home/components/form/DbCompressor.tsx @@ -3,27 +3,23 @@ import { EuiCheckbox, EuiFlexGroup, EuiFlexItem, - EuiFormRow, + EuiFormRow, EuiSpacer, EuiSuperSelect, EuiSuperSelectOption, htmlIdGenerator, } from '@elastic/eui' -import cx from 'classnames' import { FormikProps } from 'formik' import { KeyValueCompressor } from 'uiSrc/constants' import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' import { NONE } from 'uiSrc/pages/home/constants' -import styles from '../styles.module.scss' export interface Props { - flexGroupClassName?: string - flexItemClassName?: string formik: FormikProps } const DbCompressor = (props: Props) => { - const { flexGroupClassName = '', flexItemClassName = '', formik } = props + const { formik } = props const optionsCompressor: EuiSuperSelectOption[] = [ { @@ -70,16 +66,8 @@ const DbCompressor = (props: Props) => { return ( <> - - + + { {formik.values.showCompressor && ( - - - - + + + + + { - formik.setFieldValue( - 'compressor', - value || NONE - ) - }} - data-testid="select-compressor" - /> - - - + options={optionsCompressor} + onChange={(value) => { + formik.setFieldValue( + 'compressor', + value || NONE + ) + }} + data-testid="select-compressor" + /> + + + + + )} ) diff --git a/redisinsight/ui/src/pages/home/components/form/DbIndex.tsx b/redisinsight/ui/src/pages/home/components/form/DbIndex.tsx index c209bb3be7..37724581a2 100644 --- a/redisinsight/ui/src/pages/home/components/form/DbIndex.tsx +++ b/redisinsight/ui/src/pages/home/components/form/DbIndex.tsx @@ -1,6 +1,13 @@ import React, { ChangeEvent } from 'react' -import { EuiCheckbox, EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, htmlIdGenerator } from '@elastic/eui' -import cx from 'classnames' +import { + EuiCheckbox, + EuiFieldNumber, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, + htmlIdGenerator +} from '@elastic/eui' import { FormikProps } from 'formik' import { validateNumber } from 'uiSrc/utils' @@ -9,13 +16,11 @@ import { DbConnectionInfo } from 'uiSrc/pages/home/interfaces' import styles from '../styles.module.scss' export interface Props { - flexGroupClassName?: string - flexItemClassName?: string formik: FormikProps } const DbIndex = (props: Props) => { - const { flexGroupClassName = '', flexItemClassName = '', formik } = props + const { formik } = props const handleChangeDbIndexCheckbox = (e: ChangeEvent): void => { const isChecked = e.target.checked @@ -29,15 +34,11 @@ const DbIndex = (props: Props) => { return ( <> { {formik.values.showDb && ( - - - - ) => { - formik.setFieldValue( - e.target.name, - validateNumber(e.target.value.trim()) - ) - }} - type="text" - min={0} - /> - - - + <> + + + + + ) => { + formik.setFieldValue( + e.target.name, + validateNumber(e.target.value.trim()) + ) + }} + type="text" + min={0} + /> + + + + + )} ) diff --git a/redisinsight/ui/src/pages/home/components/form/Messages.tsx b/redisinsight/ui/src/pages/home/components/form/Messages.tsx index cce51f1b2a..af6e10a230 100644 --- a/redisinsight/ui/src/pages/home/components/form/Messages.tsx +++ b/redisinsight/ui/src/pages/home/components/form/Messages.tsx @@ -4,10 +4,24 @@ import { APPLICATION_NAME } from 'uiSrc/constants' import styles from '../styles.module.scss' +const MessageCloudApiKeys = () => ( + + {'Enter Redis Cloud API keys to discover and add databases. API keys can be enabled by following the steps mentioned in the '} + + documentation. + + +) + const MessageStandalone = () => ( - You can manually add your Redis databases. Enter Host and Port of your - Redis database to add it to + You can manually add your Redis databases. Enter host and port of your Redis database to add it to {' '} {APPLICATION_NAME} .   @@ -25,9 +39,8 @@ const MessageStandalone = () => ( const MessageSentinel = () => ( - You can automatically discover and add primary groups from your Redis - Sentinel. Enter Host and Port of your Redis Sentinel to automatically - discover your primary groups and add them to + You can automatically discover and add primary groups from your Redis Sentinel. + Enter host and port of your Redis Sentinel to automatically discover your primary groups and add them to {' '} {APPLICATION_NAME} .   @@ -43,7 +56,28 @@ const MessageSentinel = () => ( ) +const MessageEnterpriceSoftware = () => ( + + Your Redis Software databases can be automatically added. Enter the connection details of your + Redis Software Cluster to automatically discover your databases and add them to + {' '} + {APPLICATION_NAME} + .   + + Learn more here. + + +) + export { MessageStandalone, MessageSentinel, + MessageCloudApiKeys, + MessageEnterpriceSoftware, } diff --git a/redisinsight/ui/src/pages/home/components/form/SSHDetails.tsx b/redisinsight/ui/src/pages/home/components/form/SSHDetails.tsx index 9a7ef33918..4fe2cc11c0 100644 --- a/redisinsight/ui/src/pages/home/components/form/SSHDetails.tsx +++ b/redisinsight/ui/src/pages/home/components/form/SSHDetails.tsx @@ -8,7 +8,7 @@ import { EuiFlexItem, EuiFormRow, EuiRadioGroup, - EuiRadioGroupOption, + EuiRadioGroupOption, EuiSpacer, EuiTextArea, htmlIdGenerator } from '@elastic/eui' @@ -142,6 +142,7 @@ const SSHDetails = (props: Props) => { + diff --git a/redisinsight/ui/src/pages/home/components/form/TlsDetails.tsx b/redisinsight/ui/src/pages/home/components/form/TlsDetails.tsx index d08cdf06c2..f4b9c8e868 100644 --- a/redisinsight/ui/src/pages/home/components/form/TlsDetails.tsx +++ b/redisinsight/ui/src/pages/home/components/form/TlsDetails.tsx @@ -5,6 +5,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, + EuiSpacer, EuiSuperSelect, EuiSuperSelectOption, EuiTextArea, @@ -31,8 +32,6 @@ import styles from '../styles.module.scss' const suffix = '_tls_details' export interface Props { - flexGroupClassName?: string - flexItemClassName?: string formik: FormikProps caCertificates?: { id: string; name: string }[] certificates?: { id: number; name: string }[] @@ -40,7 +39,7 @@ export interface Props { const TlsDetails = (props: Props) => { const dispatch = useDispatch() - const { flexGroupClassName = '', flexItemClassName = '', formik, caCertificates, certificates } = props + const { formik, caCertificates, certificates } = props const [activeCertId, setActiveCertId] = useState>(null) const handleDeleteCaCert = (id: string) => { @@ -152,18 +151,8 @@ const TlsDetails = (props: Props) => { return ( <> - - + + { data-testid="tls" /> + - {formik.values.tls && ( - <> - + {formik.values.tls && ( + <> + + + { data-testid="sni" /> - {formik.values.sni && ( - - - ) => - formik.setFieldValue( - e.target.name, - validateField(e.target.value.trim()) - )} - data-testid="sni-servername" - /> - - - )} - + + {formik.values.sni && ( + <> + + + + + ) => + formik.setFieldValue( + e.target.name, + validateField(e.target.value.trim()) + )} + data-testid="sni-servername" + /> + + + + + )} + + + { data-testid="verify-tls-cert" /> - - )} - + + + )} {formik.values.tls && (
- - + + + { {formik.values.tls && formik.values.selectedCaCertName === ADD_NEW_CA_CERT && ( - + { {formik.values.tls && formik.values.selectedCaCertName === ADD_NEW_CA_CERT && ( - - + + { )} {formik.values.tls && ( - + { )} {formik.values.tls && formik.values.tlsClientAuthRequired && (
- - + + { {formik.values.tls && formik.values.tlsClientAuthRequired && formik.values.selectedTlsClientCertId === 'ADD_NEW' && ( - + { && formik.values.tlsClientAuthRequired && formik.values.selectedTlsClientCertId === 'ADD_NEW' && ( <> - - + + { - - + + sentinelMaster?: SentinelMaster } const DbInfoSentinel = (props: Props) => { - const { connectionType, nameFromProvider, sentinelMaster } = props + const { connectionType, nameFromProvider, sentinelMaster, host, port } = props return ( { )} /> )} + + {host && port && ( + + )} ) } diff --git a/redisinsight/ui/src/pages/home/components/form/sentinel/SentinelHostPort.tsx b/redisinsight/ui/src/pages/home/components/form/sentinel/SentinelHostPort.tsx index 269b724aa8..164dd7b01b 100644 --- a/redisinsight/ui/src/pages/home/components/form/sentinel/SentinelHostPort.tsx +++ b/redisinsight/ui/src/pages/home/components/form/sentinel/SentinelHostPort.tsx @@ -1,6 +1,7 @@ import React from 'react' -import { EuiButtonIcon, EuiText, EuiTextColor, EuiToolTip } from '@elastic/eui' +import { EuiButtonIcon, EuiListGroupItem, EuiText, EuiTextColor, EuiToolTip } from '@elastic/eui' +import cx from 'classnames' import styles from '../../styles.module.scss' export interface Props { @@ -17,7 +18,7 @@ const SentinelHostPort = (props: Props) => { return ( - Host:Port: + Sentinel Host & Port:
{`${host}:${port}`} ({ ...jest.requireActual('uiSrc/slices/instances/instances'), @@ -27,16 +27,21 @@ beforeEach(() => { store.clearActions() }) -describe('ImportDatabasesDialog', () => { +describe('ImportDatabase', () => { it('should render', () => { - expect(render()).toBeTruthy() + expect(render()).toBeTruthy() }) it('should call proper actions and send telemetry', async () => { const sendEventTelemetryMock = jest.fn(); (sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock) - render() + render( +
+ +
+
+ ) const jsonString = JSON.stringify({}) const blob = new Blob([jsonString]) @@ -53,8 +58,8 @@ describe('ImportDatabasesDialog', () => { ) }) - expect(screen.getByTestId('submit-btn')).not.toBeDisabled() - fireEvent.click(screen.getByTestId('submit-btn')) + expect(screen.getByTestId('btn-submit')).not.toBeDisabled() + fireEvent.click(screen.getByTestId('btn-submit')) const expectedActions = [importInstancesFromFile()] expect(store.getActions()).toEqual(expectedActions) @@ -73,7 +78,7 @@ describe('ImportDatabasesDialog', () => { error: 'Error message' })) - render() + render() expect(screen.getByTestId('result-failed')).toBeInTheDocument() expect(screen.getByTestId('result-failed')).toHaveTextContent('Error message') }) diff --git a/redisinsight/ui/src/pages/home/components/import-database/ImportDatabase.tsx b/redisinsight/ui/src/pages/home/components/import-database/ImportDatabase.tsx new file mode 100644 index 0000000000..d1cf888580 --- /dev/null +++ b/redisinsight/ui/src/pages/home/components/import-database/ImportDatabase.tsx @@ -0,0 +1,208 @@ +import React, { useEffect, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { + EuiButton, + EuiFilePicker, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLoadingSpinner, + EuiSpacer, + EuiText, + EuiTextColor, EuiToolTip +} from '@elastic/eui' +import ReactDOM from 'react-dom' +import { + fetchInstancesAction, + importInstancesSelector, + resetImportInstances, + uploadInstancesFile +} from 'uiSrc/slices/instances/instances' +import { Nullable } from 'uiSrc/utils' +import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' +import { UploadWarning } from 'uiSrc/components' +import ResultsLog from './components/ResultsLog' + +import styles from './styles.module.scss' + +export interface Props { + onClose: () => void +} + +const MAX_MB_FILE = 10 +const MAX_FILE_SIZE = MAX_MB_FILE * 1024 * 1024 + +const ImportDatabase = (props: Props) => { + const { onClose } = props + const { loading, data, error } = useSelector(importInstancesSelector) + const [files, setFiles] = useState>(null) + const [isInvalid, setIsInvalid] = useState(false) + const [isSubmitDisabled, setIsSubmitDisabled] = useState(true) + const [domReady, setDomReady] = useState(false) + + const dispatch = useDispatch() + + useEffect(() => { + setDomReady(true) + }, []) + + const onFileChange = (files: FileList | null) => { + setFiles(files) + setIsInvalid(!!files?.length && files?.[0].size > MAX_FILE_SIZE) + setIsSubmitDisabled(!files?.length || files[0].size > MAX_FILE_SIZE) + } + + const handleOnClose = () => { + if (data?.success?.length || data?.partial?.length) { + dispatch(fetchInstancesAction()) + } + onClose() + dispatch(resetImportInstances()) + + if (!data) { + sendEventTelemetry({ + event: TelemetryEvent.CONFIG_DATABASES_REDIS_IMPORT_CANCELLED, + }) + } + } + + const onSubmit = () => { + if (files) { + const formData = new FormData() + formData.append('file', files[0]) + + dispatch(uploadInstancesFile(formData)) + + sendEventTelemetry({ + event: TelemetryEvent.CONFIG_DATABASES_REDIS_IMPORT_SUBMITTED + }) + } + } + + const Footer = () => { + const footerEl = document.getElementById('footerDatabaseForm') + if (!domReady || !footerEl) return null + + if (data) { + return ReactDOM.createPortal( +
+ + Ok + +
, + footerEl + ) + } + + return ReactDOM.createPortal( +
+ + Cancel + + + + Submit + + +
, + footerEl + ) + } + + const isShowForm = !loading && !data && !error + + return ( + <> +
+ + + {isShowForm && ( + <> + + Use a JSON file to import your database connections. + Ensure that you only use files from trusted sources to + prevent the risk of automatically executing malicious code. + + + + {isInvalid && ( + + {`File should not exceed ${MAX_MB_FILE} MB`} + + )} + + )} + {loading && ( +
+ + + Uploading... + +
+ )} + {error && ( +
+ + + Failed to add database connections + + {error} +
+ )} +
+ {isShowForm && ( + + + + )} +
+ {data && ( + + + + + + )} +
+