Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Add service URIs to Database summary tab ([#13261](https://github.com/linode/manager/pull/13261))
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { Database } from '@linode/api-v4/lib/databases/types';

interface ConnectionDetailsHostRowsProps {
database: Database;
isSummaryTab?: boolean;
}

type HostContentMode = 'default' | 'private' | 'public';
Expand All @@ -30,7 +31,7 @@ type HostContentMode = 'default' | 'private' | 'public';
export const ConnectionDetailsHostRows = (
props: ConnectionDetailsHostRowsProps
) => {
const { database } = props;
const { database, isSummaryTab } = props;
const { classes } = useStyles();

const sxTooltipIcon = {
Expand Down Expand Up @@ -136,21 +137,28 @@ export const ConnectionDetailsHostRows = (

return (
<>
<ConnectionDetailsRow label={hasVPC ? 'Private Host' : 'Host'}>
<ConnectionDetailsRow
isSummaryTab={isSummaryTab}
label={hasVPC ? 'Private Host' : 'Host'}
>
{getHostContent(hasVPC ? 'private' : 'default')}
</ConnectionDetailsRow>
{hasPublicVPC && (
<ConnectionDetailsRow label="Public Host">
<ConnectionDetailsRow isSummaryTab={isSummaryTab} label="Public Host">
{getHostContent('public')}
</ConnectionDetailsRow>
)}
<ConnectionDetailsRow
isSummaryTab={isSummaryTab}
label={hasVPC ? 'Private Read-only Host' : readonlyHostLabel}
>
{getReadOnlyHostContent(hasVPC ? 'private' : 'default')}
</ConnectionDetailsRow>
{hasPublicVPC && (
<ConnectionDetailsRow label="Public Read-only Host">
<ConnectionDetailsRow
isSummaryTab={isSummaryTab}
label="Public Read-only Host"
>
{getReadOnlyHostContent('public')}
</ConnectionDetailsRow>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,25 @@ import {

interface ConnectionDetailsRowProps {
children: React.ReactNode;
isSummaryTab?: boolean;
label: string;
}

export const ConnectionDetailsRow = (props: ConnectionDetailsRowProps) => {
const { children, label } = props;
const { children, label, isSummaryTab } = props;
return (
<>
<Grid
size={{
md: 4,
md: isSummaryTab ? 3 : 4,
xs: 3,
}}
>
<StyledLabelTypography>{label}</StyledLabelTypography>
</Grid>
<StyledValueGrid size={{ md: 8, xs: 9 }}>{children}</StyledValueGrid>
<StyledValueGrid size={{ md: isSummaryTab ? 9 : 8, xs: 9 }}>
{children}
</StyledValueGrid>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { getSSLFields } from '@linode/api-v4/lib/databases/databases';
import { TooltipIcon } from '@linode/ui';
import { downloadFile } from '@linode/utilities';
import { styled } from '@mui/material/styles';
import { Button } from 'akamai-cds-react-components';
import { useSnackbar } from 'notistack';
import * as React from 'react';

import DownloadIcon from 'src/assets/icons/lke-download.svg';
import { getErrorStringOrDefault } from 'src/utilities/errorUtils';

import { sxTooltipIcon } from './DatabaseSummaryConnectionDetails';

import type { Database, SSLFields } from '@linode/api-v4';

interface Props {
database: Database;
}

export const DatabaseCaCert = (props: Props) => {
const { database } = props;
const { enqueueSnackbar } = useSnackbar();
const [isCACertDownloading, setIsCACertDownloading] =
React.useState<boolean>(false);

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 {
enqueueSnackbar('Error parsing your CA Certificate file', {
variant: 'error',
});
setIsCACertDownloading(false);
return;
}
})
.catch((errorResponse: any) => {

Check warning on line 43 in packages/manager/src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseCaCert.tsx

View workflow job for this annotation

GitHub Actions / ESLint Review (manager)

[eslint] reported by reviewdog 🐢 Unexpected any. Specify a different type. Raw Output: {"ruleId":"@typescript-eslint/no-explicit-any","severity":1,"message":"Unexpected any. Specify a different type.","line":43,"column":30,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":43,"endColumn":33,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1504,1507],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1504,1507],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}
const error = getErrorStringOrDefault(
errorResponse,
'Unable to download your CA Certificate'
);
setIsCACertDownloading(false);
enqueueSnackbar(error, { variant: 'error' });
});
};

const disableDownloadCACertificateBtn = database.status === 'provisioning';

return (
<>
<StyledCaCertButton
data-testid="download-ca-certificate"
disabled={disableDownloadCACertificateBtn}
onClick={handleDownloadCACertificate}
processing={isCACertDownloading}
variant="link"
>
<DownloadIcon />
Download CA Certificate
</StyledCaCertButton>
{disableDownloadCACertificateBtn && (
<span style={{ alignContent: 'center' }}>
<TooltipIcon
status="info"
sxTooltipIcon={sxTooltipIcon}
text="Your Database Cluster is currently provisioning."
/>
</span>
)}
</>
);
};

export const StyledCaCertButton = styled(Button, {
label: 'StyledCaCertButton',
})(({ theme }) => ({
'&:hover': {
backgroundColor: 'transparent',
opacity: 0.7,
},
'&[disabled]': {
'& g': {
stroke: theme.tokens.color.Neutrals[30],
},
'&:hover': {
backgroundColor: 'inherit',
textDecoration: 'none',
},
// Override disabled background color defined for dark mode
backgroundColor: 'transparent',
color: theme.tokens.color.Neutrals[30],
cursor: 'default',
},
color: theme.palette.primary.main,
font: theme.font.bold,
fontSize: '0.875rem',
lineHeight: '1.125rem',
minHeight: 'auto',
minWidth: 'auto',
padding: 0,
}));
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
import { Paper } from '@linode/ui';
import { useDatabaseConnectionPoolsQuery } from '@linode/queries';
import { Paper, Typography } from '@linode/ui';
import Grid from '@mui/material/Grid';
import { styled } from '@mui/material/styles';
import * as React from 'react';

import ClusterConfiguration from 'src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryClusterConfiguration';
import ConnectionDetails from 'src/features/Databases/DatabaseDetail/DatabaseSummary/DatabaseSummaryConnectionDetails';
import { useFlags } from 'src/hooks/useFlags';

import { useDatabaseDetailContext } from '../DatabaseDetailContext';
import { ServiceURI } from '../ServiceURI';
import { DatabaseCaCert } from './DatabaseCaCert';

export const DatabaseSummary = () => {
const { database } = useDatabaseDetailContext();
const flags = useFlags();

const { data: connectionPools } = useDatabaseConnectionPoolsQuery(
database.id,
flags.databasePgBouncer,
{}
);

const showPgBouncerConnectionDetails =
flags.databasePgBouncer &&
database.engine === 'postgresql' &&
connectionPools &&
connectionPools.data.length > 0;
Comment thread
hana-akamai marked this conversation as resolved.

return (
<Paper>
Expand All @@ -29,7 +47,34 @@ export const DatabaseSummary = () => {
>
<ConnectionDetails database={database} />
</Grid>
{showPgBouncerConnectionDetails && (
<Grid
size={{
md: 12,
sm: 12,
}}
>
<Typography mb={2} variant="h3">
PgBouncer Connection Details
</Typography>
<ServiceURI database={database} />
</Grid>
)}
Copy link
Copy Markdown
Contributor

@smans-akamai smans-akamai Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may not be necessary, but I'm wondering if it would make sense to add some kind of error state for this field in the event that the connection-pools GET call fails. Currently it just hides the PgBouncer Connection Details field as a whole, but they won't actually see any error messaging until they navigate to the Networking tab.

</Grid>
{database.ssl_connection && (
<StyledButtonCtn>
<DatabaseCaCert database={database} />
</StyledButtonCtn>
)}
</Paper>
);
};

export const StyledButtonCtn = styled('div', {
label: 'StyledButtonCtn',
})(({ theme }) => ({
display: 'flex',
justifyContent: 'flex-end',
marginTop: '10px',
padding: `${theme.spacingFunction(8)} 0`,
}));
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,6 @@ import { makeStyles } from 'tss-react/mui';
import type { Theme } from '@mui/material/styles';

export 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: theme.tokens.color.Neutrals[30],
},
'&:hover': {
backgroundColor: 'inherit',
textDecoration: 'none',
},
// Override disabled background color defined for dark mode
backgroundColor: 'transparent',
color: theme.tokens.color.Neutrals[30],
cursor: 'default',
},
color: theme.palette.primary.main,
font: theme.font.bold,
fontSize: '0.875rem',
lineHeight: '1.125rem',
marginLeft: theme.spacing(),
minHeight: 'auto',
minWidth: 'auto',
padding: 0,
},
tooltipIcon: {
alignContent: 'center',
},
Comment thread
hana-akamai marked this conversation as resolved.
connectionDetailsCtn: {
'& p': {
lineHeight: '1.5rem',
Expand Down
Loading