From ed8611346d4e9106383e514eaf6189e9ee8f47fb Mon Sep 17 00:00:00 2001 From: Conal Ryan Date: Tue, 8 Oct 2024 11:12:07 -0400 Subject: [PATCH] feat: [UIE-8074] - DBaaS GA Summary tab --- .../DatabaseSummary/DatabaseSummary.test.tsx | 216 +++++++++++ .../DatabaseSummary/DatabaseSummary.tsx | 51 ++- .../DatabaseSummaryAccessControls.tsx | 18 - ...tabaseSummaryClusterConfiguration.style.ts | 61 +++ ...tabaseSummaryClusterConfiguration.test.tsx | 198 ++++++++++ .../DatabaseSummaryClusterConfiguration.tsx | 169 ++++---- .../DatabaseSummaryConnectionDetails.test.tsx | 126 ++++++ .../DatabaseSummaryConnectionDetails.tsx | 311 ++++++--------- ...abaseSummaryClusterConfigurationLegacy.tsx | 143 +++++++ ...DatabaseSummaryConnectionDetailsLegacy.tsx | 367 ++++++++++++++++++ 10 files changed, 1354 insertions(+), 306 deletions(-) create mode 100644 packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummary.test.tsx delete mode 100644 packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryAccessControls.tsx create mode 100644 packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.style.ts create mode 100644 packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.test.tsx create mode 100644 packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails.test.tsx create mode 100644 packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/legacy/DatabaseSummaryClusterConfigurationLegacy.tsx create mode 100644 packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/legacy/DatabaseSummaryConnectionDetailsLegacy.tsx diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummary.test.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummary.test.tsx new file mode 100644 index 00000000000..7da913d951a --- /dev/null +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummary.test.tsx @@ -0,0 +1,216 @@ +import { Database } from '@linode/api-v4'; +import { waitFor } from '@testing-library/react'; +import * as React from 'react'; +import { databaseFactory } from 'src/factories'; +import { renderWithTheme } from 'src/utilities/testHelpers'; +import { vi } from 'vitest'; +import * as utils from '../../utilities'; +import { DatabaseSummary } from './DatabaseSummary'; + +const CLUSTER_CONFIGURATION = 'Cluster Configuration'; +const THREE_NODE = 'Primary +2 replicas'; +const TWO_NODE = 'Primary +1 replicas'; +const VERSION = 'Version'; + +const CONNECTION_DETAILS = 'Connection Details'; +const PRIVATE_NETWORK_HOST = 'Private Network Host'; +const PRIVATE_NETWORK_HOST_LABEL = 'private network host'; +const READONLY_HOST_LABEL = 'read-only host'; +const GA_READONLY_HOST_LABEL = 'Read-only Host'; + +const ACCESS_CONTROLS = 'Access Controls'; + +const DEFAULT_PLATFORM = 'rdbms-default'; +const DEFAULT_PRIMARY = 'db-mysql-default-primary.net'; +const DEFAULT_STANDBY = 'db-mysql-default-standby.net'; + +const LEGACY_PLATFORM = 'rdbms-legacy'; +const LEGACY_PRIMARY = 'db-mysql-legacy-primary.net'; +const LEGACY_SECONDARY = 'db-mysql-legacy-secondary.net'; + +const spy = vi.spyOn(utils, 'useIsDatabasesEnabled'); +spy.mockReturnValue({ + isDatabasesEnabled: true, + isDatabasesV1Enabled: true, + isDatabasesV2Enabled: true, + isDatabasesV2Beta: false, + isV2ExistingBetaUser: false, + isV2NewBetaUser: false, + isDatabasesGAEnabled: true, + isV2GAUser: true, +}); + +describe('Database Summary', () => { + it('should render V2GA view default db', async () => { + const database = databaseFactory.build({ + platform: DEFAULT_PLATFORM, + cluster_size: 2, + hosts: { + primary: DEFAULT_PRIMARY, + standby: DEFAULT_STANDBY, + }, + }) as Database; + + const { queryAllByText } = renderWithTheme( + + ); + + await waitFor(() => { + expect(queryAllByText(CLUSTER_CONFIGURATION)).toHaveLength(1); + expect(queryAllByText(TWO_NODE)).toHaveLength(1); + expect(queryAllByText(VERSION)).toHaveLength(0); + + expect(queryAllByText(CONNECTION_DETAILS)).toHaveLength(1); + expect(queryAllByText(PRIVATE_NETWORK_HOST)).toHaveLength(0); + expect(queryAllByText(GA_READONLY_HOST_LABEL)).toHaveLength(1); + expect(queryAllByText(DEFAULT_STANDBY)).toHaveLength(1); + + expect(queryAllByText(ACCESS_CONTROLS)).toHaveLength(0); + }); + }); + + it('should render V2GA view legacy db', async () => { + const database = databaseFactory.build({ + platform: LEGACY_PLATFORM, + cluster_size: 3, + hosts: { + primary: LEGACY_PRIMARY, + secondary: LEGACY_SECONDARY, + }, + }) as Database; + + const { queryAllByText } = renderWithTheme( + + ); + + await waitFor(() => { + expect(queryAllByText(CLUSTER_CONFIGURATION)).toHaveLength(1); + expect(queryAllByText(THREE_NODE)).toHaveLength(1); + expect(queryAllByText(VERSION)).toHaveLength(0); + + expect(queryAllByText(CONNECTION_DETAILS)).toHaveLength(1); + expect(queryAllByText(PRIVATE_NETWORK_HOST)).toHaveLength(1); + expect(queryAllByText(GA_READONLY_HOST_LABEL)).toHaveLength(0); + expect(queryAllByText(LEGACY_SECONDARY)).toHaveLength(1); + + expect(queryAllByText(ACCESS_CONTROLS)).toHaveLength(0); + }); + }); + + it('should render Beta view default db', async () => { + spy.mockReturnValue({ + isDatabasesEnabled: true, + isDatabasesV1Enabled: true, + isDatabasesV2Enabled: true, + isDatabasesV2Beta: true, + isV2ExistingBetaUser: true, + isV2NewBetaUser: false, + isDatabasesGAEnabled: false, + isV2GAUser: false, + }); + const database = databaseFactory.build({ + platform: DEFAULT_PLATFORM, + cluster_size: 2, + hosts: { + primary: DEFAULT_PRIMARY, + standby: DEFAULT_STANDBY, + secondary: undefined, + }, + }) as Database; + + const { queryAllByText } = renderWithTheme( + + ); + + await waitFor(() => { + expect(queryAllByText(CLUSTER_CONFIGURATION)).toHaveLength(1); + expect(queryAllByText(TWO_NODE)).toHaveLength(1); + expect(queryAllByText(VERSION)).toHaveLength(1); + + expect(queryAllByText(CONNECTION_DETAILS)).toHaveLength(1); + expect(queryAllByText(PRIVATE_NETWORK_HOST_LABEL)).toHaveLength(0); + expect(queryAllByText(READONLY_HOST_LABEL)).toHaveLength(1); + expect(queryAllByText(/db-mysql-default-standby.net/)).toHaveLength(1); + + expect(queryAllByText(ACCESS_CONTROLS)).toHaveLength(1); + }); + }); + + it('should render Beta view legacy db', async () => { + spy.mockReturnValue({ + isDatabasesEnabled: true, + isDatabasesV1Enabled: true, + isDatabasesV2Enabled: true, + isDatabasesV2Beta: true, + isV2ExistingBetaUser: true, + isV2NewBetaUser: false, + isDatabasesGAEnabled: false, + isV2GAUser: false, + }); + const database = databaseFactory.build({ + platform: LEGACY_PLATFORM, + cluster_size: 3, + hosts: { + primary: LEGACY_PRIMARY, + secondary: LEGACY_SECONDARY, + standby: undefined, + }, + }) as Database; + + const { queryAllByText } = renderWithTheme( + + ); + + await waitFor(() => { + expect(queryAllByText(CLUSTER_CONFIGURATION)).toHaveLength(1); + expect(queryAllByText(THREE_NODE)).toHaveLength(1); + expect(queryAllByText(VERSION)).toHaveLength(1); + + expect(queryAllByText(CONNECTION_DETAILS)).toHaveLength(1); + expect(queryAllByText(PRIVATE_NETWORK_HOST_LABEL)).toHaveLength(1); + expect(queryAllByText(READONLY_HOST_LABEL)).toHaveLength(0); + expect(queryAllByText(/db-mysql-legacy-secondary.net/)).toHaveLength(1); + + expect(queryAllByText(ACCESS_CONTROLS)).toHaveLength(1); + }); + }); + + it('should render V1 view legacy db', async () => { + spy.mockReturnValue({ + isDatabasesEnabled: true, + isDatabasesV1Enabled: true, + isDatabasesV2Enabled: false, + isDatabasesV2Beta: false, + isV2ExistingBetaUser: false, + isV2NewBetaUser: false, + isDatabasesGAEnabled: false, + isV2GAUser: false, + }); + const database = databaseFactory.build({ + platform: LEGACY_PLATFORM, + cluster_size: 3, + hosts: { + primary: LEGACY_PRIMARY, + secondary: LEGACY_SECONDARY, + standby: undefined, + }, + }) as Database; + + const { queryAllByText } = renderWithTheme( + + ); + + await waitFor(() => { + expect(queryAllByText(CLUSTER_CONFIGURATION)).toHaveLength(1); + expect(queryAllByText(THREE_NODE)).toHaveLength(1); + expect(queryAllByText(VERSION)).toHaveLength(1); + + expect(queryAllByText(CONNECTION_DETAILS)).toHaveLength(1); + expect(queryAllByText(PRIVATE_NETWORK_HOST_LABEL)).toHaveLength(1); + expect(queryAllByText(READONLY_HOST_LABEL)).toHaveLength(0); + expect(queryAllByText(/db-mysql-legacy-secondary.net/)).toHaveLength(1); + + expect(queryAllByText(ACCESS_CONTROLS)).toHaveLength(1); + }); + }); +}); diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummary.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummary.tsx index 37e50ebbdc2..8e70b910de2 100644 --- a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummary.tsx +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummary.tsx @@ -1,16 +1,16 @@ +import type { Database } from '@linode/api-v4/lib/databases/types'; import Grid from '@mui/material/Unstable_Grid2'; import * as React from 'react'; - import { Divider } from 'src/components/Divider'; import { Link } from 'src/components/Link'; import { Paper } from 'src/components/Paper'; import { Typography } from 'src/components/Typography'; - -import AccessControls from '../AccessControls'; -import ClusterConfiguration from './DatabaseSummaryClusterConfiguration'; -import ConnectionDetails from './DatabaseSummaryConnectionDetails'; - -import type { Database } from '@linode/api-v4/lib/databases/types'; +import AccessControls from 'src/features/Databases/DatabaseDetail/AccessControls'; +import ClusterConfiguration from 'src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration'; +import ConnectionDetails from 'src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails'; +import ClusterConfigurationLegacy from 'src/features/Databases/DatabaseDetail/DatabaseSummary/legacy/DatabaseSummaryClusterConfigurationLegacy'; +import ConnectionDetailsLegacy from 'src/features/Databases/DatabaseDetail/DatabaseSummary/legacy/DatabaseSummaryConnectionDetailsLegacy'; +import { useIsDatabasesEnabled } from 'src/features/Databases/utilities'; interface Props { database: Database; @@ -19,6 +19,7 @@ interface Props { export const DatabaseSummary: React.FC = (props) => { const { database, disabled } = props; + const { isV2GAUser } = useIsDatabasesEnabled(); const description = ( <> @@ -40,19 +41,35 @@ export const DatabaseSummary: React.FC = (props) => { return ( - - + + {isV2GAUser ? ( + + ) : ( + // Deprecated @since DBaaS V2 GA. Will be removed remove post GA release ~ Dec 2024 + + )} - - + + {isV2GAUser ? ( + + ) : ( + // Deprecated @since DBaaS V2 GA. Will be removed remove post GA release ~ Dec 2024 + + )} - - + {!isV2GAUser && ( + // Deprecated @since DBaaS V2 GA. Will be removed remove post GA release ~ Dec 2024 + // AccessControls accessible through dropdown menu on landing page table and on settings tab + <> + + + + )} ); }; diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryAccessControls.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryAccessControls.tsx deleted file mode 100644 index c4eb0c625b6..00000000000 --- a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryAccessControls.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from 'react'; -// import useDatabases from 'src/hooks/useDatabases'; - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface Props { - // databaseID: number; -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const DatabaseSummaryAccessControls: React.FC = (props) => { - // const databases = useDatabases(); - // const { databaseID } = props; - // const thisDatabase = databases.databases.itemsById[databaseID]; - - return <> Access Controls ; -}; - -export default DatabaseSummaryAccessControls; diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.style.ts b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.style.ts new file mode 100644 index 00000000000..237b468be6c --- /dev/null +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.style.ts @@ -0,0 +1,61 @@ +import { styled } from '@mui/material/styles'; +import Grid2 from '@mui/material/Unstable_Grid2/Grid2'; +import { Box } from 'src/components/Box'; +import { Typography } from 'src/components/Typography'; + +export const StyledGridContainer = styled(Grid2, { + label: 'StyledGridContainer', +})(({ theme }) => ({ + '&>*:nth-of-type(even)': { + boxShadow: `inset 0px -1px 0px 0 ${ + theme.palette.mode === 'dark' + ? theme.color.white + : theme.palette.grey[200] + }`, + }, + '&>*:nth-of-type(odd)': { + boxShadow: `inset 0px -1px 0px 0 ${theme.color.white}`, + marginBottom: '1px', + }, + boxShadow: `inset 0 -1px 0 0 ${ + theme.palette.mode === 'dark' ? theme.color.white : theme.palette.grey[200] + }, inset 0 1px 0 0 ${ + theme.palette.mode === 'dark' ? theme.color.white : theme.palette.grey[200] + }, inset -1px 0 0 ${ + theme.palette.mode === 'dark' ? theme.color.white : theme.palette.grey[200] + }`, +})); + +export const StyledLabelTypography = styled(Typography, { + label: 'StyledLabelTypography', +})(({ theme }) => ({ + background: + theme.palette.mode === 'dark' + ? theme.bg.tableHeader + : theme.palette.grey[200], + color: theme.palette.mode === 'dark' ? theme.color.grey6 : 'inherit', + fontFamily: theme.font.bold, + height: '100%', + padding: '4px 15px', +})); + +export const StyledValueBox = styled(Box, { + label: 'StyledValueBox', +})(({ theme }) => ({ + '& > button': { + marginTop: '-5px', + }, + '& > div': { + alignSelf: 'flex-start', + verticalAlign: 'sub', + }, + '& button.MuiButtonBase-root': { + marginTop: '0', + }, + '& button.copy-tooltip': { + marginTop: '-3px', + }, + color: theme.palette.mode === 'dark' ? theme.color.grey8 : theme.color.black, + display: 'flex', + padding: '3px 5px', +})); diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.test.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.test.tsx new file mode 100644 index 00000000000..5a349883067 --- /dev/null +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.test.tsx @@ -0,0 +1,198 @@ +import { Database, DatabaseStatus } from '@linode/api-v4/lib/databases'; +import { waitFor } from '@testing-library/react'; +import React from 'react'; +import { databaseFactory, databaseTypeFactory } from 'src/factories/databases'; +import { regionFactory } from 'src/factories/regions'; +import { renderWithTheme } from 'src/utilities/testHelpers'; +import { DatabaseSummaryClusterConfiguration } from './DatabaseSummaryClusterConfiguration'; + +const STATUS_VALUE = 'Active'; +const PLAN_VALUE = 'New DBaaS - Dedicated 8 GB'; +const NODES_VALUE = 'Primary +1 replicas'; +const REGION_ID = 'us-east'; +const REGION_LABEL = 'Newark, NJ'; + +const DEFAULT_PLATFORM = 'rdbms-default'; +const TYPE = 'g6-dedicated-4'; + +const queryMocks = vi.hoisted(() => ({ + useDatabaseTypesQuery: vi.fn().mockReturnValue({}), + useRegionsQuery: vi.fn().mockReturnValue({}), +})); + +vi.mock('src/queries/regions/regions', () => ({ + useRegionsQuery: queryMocks.useRegionsQuery, +})); + +vi.mock('src/queries/databases/databases', () => ({ + useDatabaseTypesQuery: queryMocks.useDatabaseTypesQuery, +})); + +describe('DatabaseSummaryClusterConfiguration', () => { + it('should display correctly for default db', async () => { + queryMocks.useRegionsQuery.mockReturnValue({ + data: regionFactory.buildList(1, { + id: REGION_ID, + label: REGION_LABEL, + country: 'us', + status: 'ok', + }), + }); + + queryMocks.useDatabaseTypesQuery.mockReturnValue({ + data: databaseTypeFactory.buildList(1, { + class: 'dedicated', + disk: 163840, + id: TYPE, + label: PLAN_VALUE, + memory: 8192, + vcpus: 4, + }), + }); + + const database = databaseFactory.build({ + cluster_size: 2, + engine: 'postgresql', + platform: DEFAULT_PLATFORM, + region: REGION_ID, + status: STATUS_VALUE.toLowerCase() as DatabaseStatus, + total_disk_size_gb: 130, + type: TYPE, + used_disk_size_gb: 6, + version: '16.4', + }) as Database; + + const { queryAllByText } = renderWithTheme( + + ); + + expect(queryMocks.useDatabaseTypesQuery).toHaveBeenCalledWith({ + platform: DEFAULT_PLATFORM, + }); + + await waitFor(() => { + expect(queryAllByText('Status')).toHaveLength(1); + expect(queryAllByText(STATUS_VALUE)).toHaveLength(1); + + expect(queryAllByText('Plan')).toHaveLength(1); + expect(queryAllByText(PLAN_VALUE)).toHaveLength(1); + + expect(queryAllByText('Nodes')).toHaveLength(1); + expect(queryAllByText(NODES_VALUE)).toHaveLength(1); + + expect(queryAllByText('CPUs')).toHaveLength(1); + expect(queryAllByText(4)).toHaveLength(1); + + expect(queryAllByText('Engine')).toHaveLength(1); + expect(queryAllByText('PostgreSQL v16.4')).toHaveLength(1); + + expect(queryAllByText('Region')).toHaveLength(1); + expect(queryAllByText(REGION_LABEL)).toHaveLength(1); + + expect(queryAllByText('RAM')).toHaveLength(1); + expect(queryAllByText('8 GB')).toHaveLength(1); + + expect(queryAllByText('Total Disk Size')).toHaveLength(1); + expect(queryAllByText('130 GB')).toHaveLength(1); + }); + }); + + it('should display correctly for legacy db', async () => { + queryMocks.useRegionsQuery.mockReturnValue({ + data: regionFactory.buildList(1, { + id: 'us-southeast', + label: 'Atlanta, GA, USA', + country: 'us', + status: 'ok', + }), + }); + + queryMocks.useDatabaseTypesQuery.mockReturnValue({ + data: databaseTypeFactory.buildList(1, { + class: 'nanode', + disk: 25600, + id: 'g6-nanode-1', + label: 'DBaaS - Nanode 1GB', + memory: 1024, + vcpus: 1, + }), + }); + + const database = databaseFactory.build({ + cluster_size: 1, + engine: 'mysql', + platform: 'rdbms-legacy', + region: 'us-southeast', + replication_type: 'none', + status: 'provisioning', + total_disk_size_gb: 15, + type: 'g6-nanode-1', + used_disk_size_gb: 2, + version: '8.0.30', + }) as Database; + + const { queryAllByText } = renderWithTheme( + + ); + + expect(queryMocks.useDatabaseTypesQuery).toHaveBeenCalledWith({ + platform: 'rdbms-legacy', + }); + + await waitFor(() => { + expect(queryAllByText('Status')).toHaveLength(1); + expect(queryAllByText('Provisioning')).toHaveLength(1); + + expect(queryAllByText('Plan')).toHaveLength(1); + expect(queryAllByText('Nanode 1 GB')).toHaveLength(1); + + expect(queryAllByText('Nodes')).toHaveLength(1); + expect(queryAllByText('Primary')).toHaveLength(1); + + expect(queryAllByText('CPUs')).toHaveLength(1); + expect(queryAllByText(1)).toHaveLength(1); + + expect(queryAllByText('Engine')).toHaveLength(1); + expect(queryAllByText('MySQL v8.0.30')).toHaveLength(1); + + expect(queryAllByText('Region')).toHaveLength(1); + expect(queryAllByText('Atlanta, GA, USA')).toHaveLength(1); + + expect(queryAllByText('RAM')).toHaveLength(1); + expect(queryAllByText('1 GB')).toHaveLength(1); + + expect(queryAllByText('Total Disk Size')).toHaveLength(1); + expect(queryAllByText('15 GB')).toHaveLength(1); + }); + }); + + it('should return null when there is no matching type', async () => { + queryMocks.useDatabaseTypesQuery.mockReturnValue({ + data: databaseTypeFactory.buildList(1, { + class: 'standard', + disk: 81920, + id: 'g6-standard-2', + label: 'DBaaS - Standard 4GB', + memory: 4096, + vcpus: 2, + }), + }); + + const database = databaseFactory.build({ + platform: 'rdbms-legacy', + type: 'g6-nanode-1', + }) as Database; + + const { queryAllByText } = renderWithTheme( + + ); + + expect(queryMocks.useDatabaseTypesQuery).toHaveBeenCalledWith({ + platform: 'rdbms-legacy', + }); + + await waitFor(() => { + expect(queryAllByText('Cluster Configuration')).toHaveLength(0); + }); + }); +}); diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.tsx index 721633578d9..26de7cf0daa 100644 --- a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.tsx +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.tsx @@ -1,57 +1,39 @@ +import type { Region } from '@linode/api-v4'; +import type { + Database, + DatabaseType, +} from '@linode/api-v4/lib/databases/types'; +import type { Theme } from '@mui/material/styles'; +import Grid from '@mui/material/Unstable_Grid2/Grid2'; import * as React from 'react'; -import { makeStyles } from 'tss-react/mui'; - -import { Box } from 'src/components/Box'; import { TooltipIcon } from 'src/components/TooltipIcon'; import { Typography } from 'src/components/Typography'; +import { DatabaseStatusDisplay } from 'src/features/Databases/DatabaseDetail/DatabaseStatusDisplay'; +import { + StyledGridContainer, + StyledLabelTypography, + StyledValueBox, +} from 'src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration.style'; +import { databaseEngineMap } from 'src/features/Databases/DatabaseLanding/DatabaseRow'; import { useDatabaseTypesQuery } from 'src/queries/databases/databases'; import { useInProgressEvents } from 'src/queries/events/events'; import { useRegionsQuery } from 'src/queries/regions/regions'; import { formatStorageUnits } from 'src/utilities/formatStorageUnits'; import { convertMegabytesTo } from 'src/utilities/unitConversions'; - -import { databaseEngineMap } from '../../DatabaseLanding/DatabaseRow'; -import { DatabaseStatusDisplay } from '../DatabaseStatusDisplay'; - -import type { Region } from '@linode/api-v4'; -import type { - Database, - DatabaseInstance, - DatabaseType, -} from '@linode/api-v4/lib/databases/types'; -import type { Theme } from '@mui/material/styles'; +import { makeStyles } from 'tss-react/mui'; const useStyles = makeStyles()((theme: Theme) => ({ - configs: { - fontSize: '0.875rem', - lineHeight: '22px', - }, header: { marginBottom: theme.spacing(2), }, - label: { - fontFamily: theme.font.bold, - lineHeight: '22px', - width: theme.spacing(13), - }, - status: { - alignItems: 'center', - display: 'flex', - textTransform: 'capitalize', - }, })); interface Props { database: Database; } -export const getDatabaseVersionNumber = ( - version: DatabaseInstance['version'] -) => version.split('/')[1]; - export const DatabaseSummaryClusterConfiguration = (props: Props) => { const { classes } = useStyles(); - const { database } = props; const { data: types } = useDatabaseTypesQuery({ @@ -88,60 +70,75 @@ export const DatabaseSummaryClusterConfiguration = (props: Props) => { Cluster Configuration -
- - Status -
+ + + Status + + + -
-
- - Version - {databaseEngineMap[database.engine]} v{database.version} - - - Nodes - {configuration} - - - Region - {region?.label ?? database.region} - - - Plan - {formatStorageUnits(type.label)} - - - RAM - {type.memory / 1024} GB - - - CPUs - {type.vcpus} - - {database.total_disk_size_gb ? ( - <> - - Total Disk Size - {database.total_disk_size_gb} GB - - - - Used - {database.used_disk_size_gb} GB - - - ) : ( - - Storage - {convertMegabytesTo(type.disk, true)} - - )} -
+ + + + Plan + + + {formatStorageUnits(type.label)} + + + Nodes + + + {configuration} + + + CPUs + + + {type.vcpus} + + + Engine + + + + {databaseEngineMap[database.engine]} v{database.version} + + + + Region + + + {region?.label ?? database.region} + + + RAM + + + {type.memory / 1024} GB + + + + {database.total_disk_size_gb ? 'Total Disk Size' : 'Storage'} + + + + + {database.total_disk_size_gb ? ( + <> + {database.total_disk_size_gb} GB + + + ) : ( + convertMegabytesTo(type.disk, true) + )} + + + ); }; diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails.test.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails.test.tsx new file mode 100644 index 00000000000..74087dfc41f --- /dev/null +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails.test.tsx @@ -0,0 +1,126 @@ +import { Database } from '@linode/api-v4/lib/databases'; +import { waitFor } from '@testing-library/react'; +import React from 'react'; +import { databaseFactory } from 'src/factories/databases'; +import { renderWithTheme } from 'src/utilities/testHelpers'; +import DatabaseSummaryConnectionDetails from './DatabaseSummaryConnectionDetails'; + +const AKMADMIN = 'akmadmin'; +const POSTGRESQL = 'postgresql'; +const DEFAULT_PRIMARY = 'db-mysql-default-primary.net'; +const DEFAULT_STANDBY = 'db-mysql-default-standby.net'; + +const MYSQL = 'mysql'; +const LINROOT = 'linroot'; +const LEGACY_PRIMARY = 'db-mysql-legacy-primary.net'; +const LEGACY_SECONDARY = 'db-mysql-legacy-secondary.net'; + +const queryMocks = vi.hoisted(() => ({ + useDatabaseCredentialsQuery: vi.fn().mockReturnValue({}), +})); + +vi.mock('src/queries/databases/databases', () => ({ + useDatabaseCredentialsQuery: queryMocks.useDatabaseCredentialsQuery, +})); + +describe('DatabaseSummaryConnectionDetails', () => { + it('should display correctly for default db', async () => { + queryMocks.useDatabaseCredentialsQuery.mockReturnValue({ + data: { + username: AKMADMIN, + password: 'abc123', + }, + }); + + const database = databaseFactory.build({ + engine: POSTGRESQL, + hosts: { + primary: DEFAULT_PRIMARY, + standby: DEFAULT_STANDBY, + secondary: undefined, + }, + id: 99, + platform: 'rdbms-default', + port: 22496, + ssl_connection: true, + }) as Database; + + const { queryAllByText } = renderWithTheme( + + ); + + expect(queryMocks.useDatabaseCredentialsQuery).toHaveBeenCalledWith( + POSTGRESQL, + 99 + ); + + await waitFor(() => { + expect(queryAllByText('Username')).toHaveLength(1); + expect(queryAllByText(AKMADMIN)).toHaveLength(1); + + expect(queryAllByText('Password')).toHaveLength(1); + + expect(queryAllByText('Host')).toHaveLength(1); + expect(queryAllByText(DEFAULT_PRIMARY)).toHaveLength(1); + + expect(queryAllByText('Read-only Host')).toHaveLength(1); + expect(queryAllByText(DEFAULT_STANDBY)).toHaveLength(1); + + expect(queryAllByText('Port')).toHaveLength(1); + expect(queryAllByText('22496')).toHaveLength(1); + + expect(queryAllByText('SSL')).toHaveLength(1); + expect(queryAllByText('ENABLED')).toHaveLength(1); + }); + }); + + it('should display correctly for legacy db', async () => { + queryMocks.useDatabaseCredentialsQuery.mockReturnValue({ + data: { + username: LINROOT, + password: 'abc123', + }, + }); + + const database = databaseFactory.build({ + engine: MYSQL, + hosts: { + primary: LEGACY_PRIMARY, + secondary: LEGACY_SECONDARY, + standby: undefined, + }, + id: 22, + platform: 'rdbms-legacy', + port: 3306, + ssl_connection: true, + }) as Database; + + const { queryAllByText } = renderWithTheme( + + ); + + expect(queryMocks.useDatabaseCredentialsQuery).toHaveBeenCalledWith( + MYSQL, + 22 + ); + + await waitFor(() => { + expect(queryAllByText('Username')).toHaveLength(1); + expect(queryAllByText(LINROOT)).toHaveLength(1); + + expect(queryAllByText('Password')).toHaveLength(1); + + expect(queryAllByText('Host')).toHaveLength(1); + expect(queryAllByText(LEGACY_PRIMARY)).toHaveLength(1); + + expect(queryAllByText('Private Network Host')).toHaveLength(1); + expect(queryAllByText(LEGACY_SECONDARY)).toHaveLength(1); + + expect(queryAllByText('Port')).toHaveLength(1); + expect(queryAllByText('3306')).toHaveLength(1); + + expect(queryAllByText('SSL')).toHaveLength(1); + expect(queryAllByText('ENABLED')).toHaveLength(1); + }); + }); +}); diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails.tsx index ac0409e1b0a..cb3c8d22110 100644 --- a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails.tsx +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails.tsx @@ -1,13 +1,10 @@ import { getSSLFields } from '@linode/api-v4/lib/databases/databases'; -import { Database, SSLFields } from '@linode/api-v4/lib/databases/types'; -import { useTheme } from '@mui/material'; -import { Theme } from '@mui/material/styles'; +import type { Database, SSLFields } from '@linode/api-v4/lib/databases/types'; +import type { Theme } from '@mui/material/styles'; +import Grid from '@mui/material/Unstable_Grid2/Grid2'; import { useSnackbar } from 'notistack'; import * as React from 'react'; -import { makeStyles } from 'tss-react/mui'; - import DownloadIcon from 'src/assets/icons/lke-download.svg'; -import { Box } from 'src/components/Box'; import { Button } from 'src/components/Button/Button'; import { CircleProgress } from 'src/components/CircleProgress'; import { CopyTooltip } from 'src/components/CopyTooltip/CopyTooltip'; @@ -17,11 +14,18 @@ import { DB_ROOT_USERNAME } from 'src/constants'; import { useDatabaseCredentialsQuery } from 'src/queries/databases/databases'; import { downloadFile } from 'src/utilities/downloadFile'; import { getErrorStringOrDefault } from 'src/utilities/errorUtils'; +import { makeStyles } from 'tss-react/mui'; +import { + StyledGridContainer, + StyledLabelTypography, + StyledValueBox, +} from './DatabaseSummaryClusterConfiguration.style'; const useStyles = makeStyles()((theme: Theme) => ({ actionBtnsCtn: { display: 'flex', justifyContent: 'flex-end', + marginTop: '10px', padding: `${theme.spacing(1)} 0`, }, caCertBtn: { @@ -125,14 +129,11 @@ const sxTooltipIcon = { const privateHostCopy = 'A private network host and a private IP can only be used to access a Database Cluster from Linodes in the same data center and will not incur transfer costs.'; -const mongoHostHelperCopy = - 'This is a public hostname. Coming soon: connect to your MongoDB clusters using private IPs'; - export const DatabaseSummaryConnectionDetails = (props: Props) => { const { database } = props; const { classes } = useStyles(); - const theme = useTheme(); const { enqueueSnackbar } = useSnackbar(); + const isLegacy = database.platform === 'rdbms-legacy'; const [showCredentials, setShowPassword] = React.useState(false); const [isCACertDownloading, setIsCACertDownloading] = React.useState( @@ -160,9 +161,6 @@ export const DatabaseSummaryConnectionDetails = (props: Props) => { setShowPassword((showCredentials) => !showCredentials); }; - const isMongoReplicaSet = - database.engine === 'mongodb' && database.cluster_size > 1; - React.useEffect(() => { if (showCredentials && !credentials) { getDatabaseCredentials(); @@ -198,7 +196,29 @@ export const DatabaseSummaryConnectionDetails = (props: Props) => { const disableShowBtn = ['failed', 'provisioning'].includes(database.status); const disableDownloadCACertificateBtn = database.status === 'provisioning'; - const readOnlyHost = database?.hosts?.standby || database?.hosts?.secondary; + const readOnlyHostValue = + database?.hosts?.standby ?? database?.hosts?.secondary ?? ''; + + const readOnlyHost = () => { + const defaultValue = isLegacy ? '-' : 'not available'; + const value = readOnlyHostValue ?? defaultValue; + return ( + <> + {value} + + {isLegacy && ( + + )} + + ); + }; const credentialsBtn = (handleClick: () => void, btnText: string) => { return ( @@ -223,7 +243,7 @@ export const DatabaseSummaryConnectionDetails = (props: Props) => { Download CA Certificate - {disableDownloadCACertificateBtn ? ( + {disableDownloadCACertificateBtn && ( { text="Your Database Cluster is currently provisioning." /> - ) : null} + )} ); @@ -240,179 +260,100 @@ export const DatabaseSummaryConnectionDetails = (props: Props) => { Connection Details - - - username = {username} - - - - password = {password} - - {showCredentials && credentialsLoading ? ( -
- -
- ) : credentialsError ? ( - <> - - Error retrieving credentials. - - {credentialsBtn(() => getDatabaseCredentials(), 'Retry')} - - ) : ( - credentialsBtn( - handleShowPasswordClick, - showCredentials && credentials ? 'Hide' : 'Show' - ) - )} - {disableShowBtn ? ( - - ) : null} - {showCredentials && credentials ? ( - - ) : null} -
- - {!isMongoReplicaSet ? ( - - {database.hosts?.primary ? ( - <> - - host ={' '} - - {database.hosts?.primary} - {' '} - - - {database.engine === 'mongodb' ? ( - - ) : null} - - ) : ( - - host ={' '} - - Your hostname will appear here once it is available. - - - )} - - ) : ( - <> - - hosts ={' '} - {!database.peers || database.peers.length === 0 ? ( - - Your hostnames will appear here once they are available. - - ) : null} - - {database.peers && database.peers.length > 0 - ? database.peers.map((hostname, i) => ( - - - - {hostname} - - - - {/* Display the helper text on the first hostname */} - {i === 0 ? ( - - ) : null} - - )) - : null} - - )} - - {readOnlyHost ? ( - - - {database.platform === 'rdbms-default' ? ( - read-only host - ) : ( - private network host - )} - = {readOnlyHost} - - - - - ) : null} - - port = {database.port} - - {isMongoReplicaSet ? ( - database.replica_set ? ( - - - replica set ={' '} - - {database.replica_set} + + + Username + + + {username} + + + Password + + + + {password} + {showCredentials && credentialsLoading ? ( +
+ +
+ ) : credentialsError ? ( + <> + + Error retrieving credentials. -
+ {credentialsBtn(() => getDatabaseCredentials(), 'Retry')} + + ) : ( + credentialsBtn( + handleShowPasswordClick, + showCredentials && credentials ? 'Hide' : 'Show' + ) + )} + {disableShowBtn && ( + + )} + {showCredentials && credentials && ( -
- ) : ( - - replica set ={' '} - - Your replica set will appear here once it is available. - - - ) - ) : null} - - ssl = {database.ssl_connection ? 'ENABLED' : 'DISABLED'} - -
+ )} + + + + Host + + + + {database.hosts?.primary ? ( + <> + {database.hosts?.primary} + + + ) : ( + + + Your hostname will appear here once it is available. + + + )} + + + + + {isLegacy ? 'Private Network Host' : 'Read-only Host'} + + + + {readOnlyHost()} + + + Port + + + {database.port} + + + SSL + + + + {database.ssl_connection ? 'ENABLED' : 'DISABLED'} + + +
{database.ssl_connection ? caCertificateJSX : null}
diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/legacy/DatabaseSummaryClusterConfigurationLegacy.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/legacy/DatabaseSummaryClusterConfigurationLegacy.tsx new file mode 100644 index 00000000000..0d765a08de3 --- /dev/null +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/legacy/DatabaseSummaryClusterConfigurationLegacy.tsx @@ -0,0 +1,143 @@ +import type { Region } from '@linode/api-v4'; +import type { + Database, + DatabaseType, +} from '@linode/api-v4/lib/databases/types'; +import type { Theme } from '@mui/material/styles'; +import * as React from 'react'; +import { Box } from 'src/components/Box'; +import { TooltipIcon } from 'src/components/TooltipIcon'; +import { Typography } from 'src/components/Typography'; +import { DatabaseStatusDisplay } from 'src/features/Databases/DatabaseDetail/DatabaseStatusDisplay'; +import { databaseEngineMap } from 'src/features/Databases/DatabaseLanding/DatabaseRow'; +import { useDatabaseTypesQuery } from 'src/queries/databases/databases'; +import { useInProgressEvents } from 'src/queries/events/events'; +import { useRegionsQuery } from 'src/queries/regions/regions'; +import { formatStorageUnits } from 'src/utilities/formatStorageUnits'; +import { convertMegabytesTo } from 'src/utilities/unitConversions'; +import { makeStyles } from 'tss-react/mui'; + +const useStyles = makeStyles()((theme: Theme) => ({ + configs: { + fontSize: '0.875rem', + lineHeight: '22px', + }, + header: { + marginBottom: theme.spacing(2), + }, + label: { + fontFamily: theme.font.bold, + lineHeight: '22px', + width: theme.spacing(13), + }, + status: { + alignItems: 'center', + display: 'flex', + textTransform: 'capitalize', + }, +})); + +interface Props { + database: Database; +} + +/** + * Deprecated @since DBaaS V2 GA. Will be removed remove post GA release ~ Dec 2024 + */ +export const DatabaseSummaryClusterConfigurationLegacy = (props: Props) => { + const { classes } = useStyles(); + const { database } = props; + + const { data: types } = useDatabaseTypesQuery({ + platform: database.platform, + }); + + const type = types?.find((type: DatabaseType) => type.id === database?.type); + + const { data: regions } = useRegionsQuery(); + + const region = regions?.find((r: Region) => r.id === database.region); + + const { data: events } = useInProgressEvents(); + + if (!database || !type) { + return null; + } + + const configuration = + database.cluster_size === 1 + ? 'Primary' + : `Primary +${database.cluster_size - 1} replicas`; + + const sxTooltipIcon = { + marginLeft: '4px', + padding: '0px', + }; + + const STORAGE_COPY = + 'The total disk size is smaller than the selected plan capacity due to overhead from the OS.'; + + return ( + <> + + Cluster Configuration + +
+ + Status +
+ +
+
+ + Version + {databaseEngineMap[database.engine]} v{database.version} + + + Nodes + {configuration} + + + Region + {region?.label ?? database.region} + + + Plan + {formatStorageUnits(type.label)} + + + RAM + {type.memory / 1024} GB + + + CPUs + {type.vcpus} + + {database.total_disk_size_gb ? ( + <> + + Total Disk Size + {database.total_disk_size_gb} GB + + + + Used + {database.used_disk_size_gb} GB + + + ) : ( + + Storage + {convertMegabytesTo(type.disk, true)} + + )} +
+ + ); +}; + +export default DatabaseSummaryClusterConfigurationLegacy; diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/legacy/DatabaseSummaryConnectionDetailsLegacy.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/legacy/DatabaseSummaryConnectionDetailsLegacy.tsx new file mode 100644 index 00000000000..f1cb854a76f --- /dev/null +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/legacy/DatabaseSummaryConnectionDetailsLegacy.tsx @@ -0,0 +1,367 @@ +import { getSSLFields } from '@linode/api-v4/lib/databases/databases'; +import type { Database, SSLFields } from '@linode/api-v4/lib/databases/types'; +import { useTheme } from '@mui/material'; +import type { Theme } from '@mui/material/styles'; +import { useSnackbar } from 'notistack'; +import * as React from 'react'; +import DownloadIcon from 'src/assets/icons/lke-download.svg'; +import { Box } from 'src/components/Box'; +import { Button } from 'src/components/Button/Button'; +import { CircleProgress } from 'src/components/CircleProgress'; +import { CopyTooltip } from 'src/components/CopyTooltip/CopyTooltip'; +import { TooltipIcon } from 'src/components/TooltipIcon'; +import { Typography } from 'src/components/Typography'; +import { DB_ROOT_USERNAME } from 'src/constants'; +import { useDatabaseCredentialsQuery } from 'src/queries/databases/databases'; +import { downloadFile } from 'src/utilities/downloadFile'; +import { getErrorStringOrDefault } from 'src/utilities/errorUtils'; +import { makeStyles } from 'tss-react/mui'; + +const useStyles = makeStyles()((theme: Theme) => ({ + actionBtnsCtn: { + display: 'flex', + justifyContent: 'flex-end', + marginTop: '10px', + padding: `${theme.spacing(1)} 0`, + }, + caCertBtn: { + '& svg': { + marginRight: theme.spacing(), + }, + '&:hover': { + backgroundColor: 'transparent', + opacity: 0.7, + }, + '&[disabled]': { + '& g': { + stroke: '#cdd0d5', + }, + '&:hover': { + backgroundColor: 'inherit', + textDecoration: 'none', + }, + // Override disabled background color defined for dark mode + backgroundColor: 'transparent', + color: '#cdd0d5', + cursor: 'default', + }, + color: theme.palette.primary.main, + fontFamily: theme.font.bold, + fontSize: '0.875rem', + lineHeight: '1.125rem', + marginLeft: theme.spacing(), + minHeight: 'auto', + minWidth: 'auto', + padding: 0, + }, + connectionDetailsCtn: { + '& p': { + lineHeight: '1.5rem', + }, + '& span': { + fontFamily: theme.font.bold, + }, + background: theme.bg.bgAccessRow, + border: `1px solid ${theme.name === 'light' ? '#ccc' : '#222'}`, + padding: '8px 15px', + }, + copyToolTip: { + '& svg': { + color: theme.palette.primary.main, + height: `16px !important`, + width: `16px !important`, + }, + marginRight: 12, + }, + error: { + color: theme.color.red, + marginLeft: theme.spacing(2), + }, + header: { + marginBottom: theme.spacing(2), + }, + inlineCopyToolTip: { + '& svg': { + height: `16px`, + width: `16px`, + }, + '&:hover': { + backgroundColor: 'transparent', + }, + display: 'inline-flex', + marginLeft: 4, + }, + progressCtn: { + '& circle': { + stroke: theme.palette.primary.main, + }, + alignSelf: 'flex-end', + marginBottom: 2, + marginLeft: 22, + }, + provisioningText: { + fontFamily: theme.font.normal, + fontStyle: 'italic', + }, + showBtn: { + color: theme.palette.primary.main, + fontSize: '0.875rem', + marginLeft: theme.spacing(), + minHeight: 'auto', + minWidth: 'auto', + padding: 0, + }, +})); + +interface Props { + database: Database; +} + +const sxTooltipIcon = { + marginLeft: '4px', + padding: '0px', +}; + +const privateHostCopy = + 'A private network host and a private IP can only be used to access a Database Cluster from Linodes in the same data center and will not incur transfer costs.'; + +const mongoHostHelperCopy = + 'This is a public hostname. Coming soon: connect to your MongoDB clusters using private IPs'; + +/** + * Deprecated @since DBaaS V2 GA. Will be removed remove post GA release ~ Dec 2024 + */ +export const DatabaseSummaryConnectionDetailsLegacy = (props: Props) => { + const { database } = props; + const { classes } = useStyles(); + const theme = useTheme(); + const { enqueueSnackbar } = useSnackbar(); + + const [showCredentials, setShowPassword] = React.useState(false); + const [isCACertDownloading, setIsCACertDownloading] = React.useState( + false + ); + + const { + data: credentials, + error: credentialsError, + isLoading: credentialsLoading, + refetch: getDatabaseCredentials, + } = useDatabaseCredentialsQuery(database.engine, database.id); + + const username = + database.platform === 'rdbms-default' + ? 'akmadmin' + : database.engine === 'postgresql' + ? 'linpostgres' + : DB_ROOT_USERNAME; + + const password = + showCredentials && credentials ? credentials?.password : '••••••••••'; + + const handleShowPasswordClick = () => { + setShowPassword((showCredentials) => !showCredentials); + }; + + React.useEffect(() => { + if (showCredentials && !credentials) { + getDatabaseCredentials(); + } + }, [credentials, getDatabaseCredentials, showCredentials]); + + const handleDownloadCACertificate = () => { + setIsCACertDownloading(true); + getSSLFields(database.engine, database.id) + .then((response: SSLFields) => { + // Convert to utf-8 from base64 + try { + const decodedFile = window.atob(response.ca_certificate); + downloadFile(`${database.label}-ca-certificate.crt`, decodedFile); + setIsCACertDownloading(false); + } catch (e) { + enqueueSnackbar('Error parsing your CA Certificate file', { + variant: 'error', + }); + setIsCACertDownloading(false); + return; + } + }) + .catch((errorResponse: any) => { + const error = getErrorStringOrDefault( + errorResponse, + 'Unable to download your CA Certificate' + ); + setIsCACertDownloading(false); + enqueueSnackbar(error, { variant: 'error' }); + }); + }; + + const disableShowBtn = ['failed', 'provisioning'].includes(database.status); + const disableDownloadCACertificateBtn = database.status === 'provisioning'; + const readOnlyHost = database?.hosts?.standby || database?.hosts?.secondary; + + const credentialsBtn = (handleClick: () => void, btnText: string) => { + return ( + + ); + }; + + const caCertificateJSX = ( + <> + + {disableDownloadCACertificateBtn && ( + + + + )} + + ); + + return ( + <> + + Connection Details + + + + username = {username} + + + + password = {password} + + {showCredentials && credentialsLoading ? ( +
+ +
+ ) : credentialsError ? ( + <> + + Error retrieving credentials. + + {credentialsBtn(() => getDatabaseCredentials(), 'Retry')} + + ) : ( + credentialsBtn( + handleShowPasswordClick, + showCredentials && credentials ? 'Hide' : 'Show' + ) + )} + {disableShowBtn && ( + + )} + {showCredentials && credentials && ( + + )} +
+ + + hosts ={' '} + {(!database.peers || database.peers.length === 0) && ( + + Your hostnames will appear here once they are available. + + )} + + {database.peers && + database.peers.length > 0 && + database.peers.map((hostname, i) => ( + + + + {hostname} + + + + {/* Display the helper text on the first hostname */} + {i === 0 && ( + + )} + + ))} + + {readOnlyHost && ( + + + {database.platform === 'rdbms-default' ? ( + read-only host + ) : ( + private network host + )} + = {readOnlyHost} + + + {database.platform === 'rdbms-legacy' && ( + + )} + + )} + + port = {database.port} + + + ssl = {database.ssl_connection ? 'ENABLED' : 'DISABLED'} + +
+
+ {database.ssl_connection ? caCertificateJSX : null} +
+ + ); +}; + +export default DatabaseSummaryConnectionDetailsLegacy;