mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
console: support multi tenant connection pooling on cloud
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5950 Co-authored-by: Matthew Goodwin <49927862+m4ttheweric@users.noreply.github.com> GitOrigin-RevId: 47db9dd70235242002533ce64ef2f987a816d4fe
This commit is contained in:
parent
2f86f71d4d
commit
265311a4cc
@ -70,6 +70,7 @@ Environment variables accepted in `server` mode:
|
||||
- `SERVER_VERSION`: Hasura GraphQL Engine server version
|
||||
- `CONSOLE_MODE`: In server mode, it should be `server`
|
||||
- `IS_ADMIN_SECRET_SET`: Is GraphQl engine configured with an admin secret (`true`/`false`)
|
||||
- `HASURA_CONSOLE_TYPE`: The environment where the console is running, this could be oss, pro or cloud
|
||||
|
||||
Here's an example `.env` file for `server` mode:
|
||||
|
||||
@ -84,6 +85,7 @@ URL_PREFIX=/
|
||||
DATA_API_URL=http://localhost:8080
|
||||
SERVER_VERSION=v1.0.0
|
||||
CONSOLE_MODE=server
|
||||
HASURA_CONSOLE_TYPE=oss
|
||||
IS_ADMIN_SECRET_SET=true
|
||||
```
|
||||
|
||||
|
@ -80,7 +80,10 @@ export { ReactQueryProvider, reactQueryClient } from '../src/lib/reactQuery';
|
||||
|
||||
export { FeatureFlags } from '../src/features/FeatureFlags';
|
||||
|
||||
export { isMonitoringTabSupportedEnvironment } from '../src/utils/proConsole';
|
||||
export {
|
||||
isMonitoringTabSupportedEnvironment,
|
||||
isEnvironmentSupportMultiTenantConnectionPooling,
|
||||
} from '../src/utils/proConsole';
|
||||
|
||||
export {
|
||||
SampleDBBanner,
|
||||
|
@ -29,10 +29,13 @@ module.exports = {
|
||||
__DEVTOOLS__: true,
|
||||
socket: true,
|
||||
webpackIsomorphicTools: true,
|
||||
__env: {
|
||||
consoleType: 'oss',
|
||||
},
|
||||
window: {
|
||||
__env: {
|
||||
nodeEnv: 'development',
|
||||
serverVersion: 'v1.0.0',
|
||||
serverVersion: 'v1.0.0', // FIXME : moving this to the above __env block seem to be breaking some existing tests
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -6,8 +6,8 @@ import { isEmpty } from './components/Common/utils/jsUtils';
|
||||
import { stripTrailingSlash } from './components/Common/utils/urlUtils';
|
||||
|
||||
import { SERVER_CONSOLE_MODE } from './constants';
|
||||
import { parseConsoleType, ConsoleType } from './utils/envUtils';
|
||||
|
||||
type ConsoleType = 'oss' | 'cloud' | 'pro' | 'pro-lite';
|
||||
export type LuxFeature =
|
||||
| 'DatadogIntegration'
|
||||
| 'ProUser'
|
||||
@ -221,7 +221,9 @@ const globals = {
|
||||
luxDataHost: window.__env?.luxDataHost,
|
||||
userRole: window.__env?.userRole || undefined,
|
||||
userId: window.__env?.userId || undefined,
|
||||
consoleType: window.__env?.consoleType || '',
|
||||
consoleType: window.__env?.consoleType // FIXME : this check can be removed when the all CLI environments are set with the console type, some CLI environments could have empty consoleType
|
||||
? parseConsoleType(window.__env?.consoleType)
|
||||
: ('' as ConsoleType),
|
||||
eeMode: window.__env?.eeMode === 'true',
|
||||
};
|
||||
if (globals.consoleMode === SERVER_CONSOLE_MODE) {
|
||||
|
@ -10,7 +10,7 @@ import styles from './DataSources.module.scss';
|
||||
import JSONEditor from '../TablePermissions/JSONEditor';
|
||||
import { SupportedFeaturesType } from '../../../../dataSources/types';
|
||||
import { Path } from '../../../Common/utils/tsUtils';
|
||||
import ConnectionSettingsForm from './ConnectionSettingsForm';
|
||||
import ConnectionSettingsForm from './ConnectionSettings/ConnectionSettingsForm';
|
||||
import { GraphQLFieldCustomizationContainer } from './GraphQLFieldCustomization/GraphQLFieldCustomizationContainer';
|
||||
import { SampleDBTrial } from './SampleDatabase';
|
||||
|
||||
|
@ -0,0 +1,49 @@
|
||||
import React, { Dispatch } from 'react';
|
||||
import { ConnectDBActions, ConnectDBState } from '../state';
|
||||
import { buildFormSettings } from './buildFormSettings';
|
||||
import {
|
||||
FormContainer,
|
||||
ConnectionLifetime,
|
||||
CumulativeMaxConnections,
|
||||
IdleTimeout,
|
||||
IsolationLevel,
|
||||
MaxConnections,
|
||||
PoolTimeout,
|
||||
PreparedStatements,
|
||||
Retries,
|
||||
SSLCertificates,
|
||||
} from './parts';
|
||||
|
||||
export interface ConnectionSettingsFormProps {
|
||||
connectionDBState: ConnectDBState;
|
||||
connectionDBStateDispatch: Dispatch<ConnectDBActions>;
|
||||
}
|
||||
|
||||
const ConnectionSettingsForm: React.FC<ConnectionSettingsFormProps> = props => {
|
||||
const { connectionDBState } = props;
|
||||
|
||||
const formSettings = React.useMemo(
|
||||
() => buildFormSettings(connectionDBState.dbType),
|
||||
[connectionDBState.dbType]
|
||||
);
|
||||
|
||||
if (!formSettings.connectionSettings) return null;
|
||||
|
||||
return (
|
||||
<FormContainer>
|
||||
<MaxConnections {...props} />
|
||||
{formSettings.cumulativeMaxConnections && (
|
||||
<CumulativeMaxConnections {...props} />
|
||||
)}
|
||||
<IdleTimeout {...props} />
|
||||
{formSettings.retries && <Retries {...props} />}
|
||||
{formSettings.pool_timeout && <PoolTimeout {...props} />}
|
||||
{formSettings.connection_lifetime && <ConnectionLifetime {...props} />}
|
||||
{formSettings.isolation_level && <IsolationLevel {...props} />}
|
||||
{formSettings.prepared_statements && <PreparedStatements {...props} />}
|
||||
{formSettings.ssl_certificates && <SSLCertificates {...props} />}
|
||||
</FormContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectionSettingsForm;
|
@ -0,0 +1,28 @@
|
||||
import { getSupportedDrivers } from '@/dataSources';
|
||||
import { DbConnectionSettings } from '@/dataSources/types';
|
||||
import { ConnectDBState } from '../state';
|
||||
|
||||
export const buildFormSettings = (
|
||||
dbType: ConnectDBState['dbType']
|
||||
): DbConnectionSettings => {
|
||||
const settings: DbConnectionSettings = {
|
||||
connectionSettings: false,
|
||||
cumulativeMaxConnections: false,
|
||||
retries: false,
|
||||
pool_timeout: false,
|
||||
connection_lifetime: false,
|
||||
isolation_level: false,
|
||||
prepared_statements: false,
|
||||
ssl_certificates: false,
|
||||
};
|
||||
|
||||
let setting: keyof DbConnectionSettings;
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax, guard-for-in
|
||||
for (setting in settings) {
|
||||
settings[setting] = getSupportedDrivers(
|
||||
`connectDbForm.${setting}`
|
||||
).includes(dbType);
|
||||
}
|
||||
return settings;
|
||||
};
|
@ -0,0 +1,31 @@
|
||||
import { LabeledInput } from '@/components/Common/LabeledInput';
|
||||
import { ConnectionSettingsFormProps } from '@/components/Services/Data/DataSources/ConnectionSettings/ConnectionSettingsForm';
|
||||
import React from 'react';
|
||||
import styles from '../../DataSources.module.scss';
|
||||
|
||||
export const ConnectionLifetime: React.VFC<ConnectionSettingsFormProps> = ({
|
||||
connectionDBState,
|
||||
connectionDBStateDispatch,
|
||||
}) => (
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<LabeledInput
|
||||
label="Connection Lifetime"
|
||||
tooltipText="Time (in seconds) from connection creation after which the connection should be destroyed and a new one created. A value of 0 indicates we should never destroy an active connection. If 0 is passed, memory from large query results may not be reclaimed."
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="600"
|
||||
value={
|
||||
connectionDBState.connectionSettings?.connection_lifetime ?? undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_CONNECTION_LIFETIME',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="connection-lifetime"
|
||||
/>
|
||||
</div>
|
||||
);
|
@ -0,0 +1,30 @@
|
||||
import { LabeledInput } from '@/components/Common/LabeledInput';
|
||||
import { ConnectionSettingsFormProps } from '@/components/Services/Data/DataSources/ConnectionSettings/ConnectionSettingsForm';
|
||||
import React from 'react';
|
||||
import styles from '../../DataSources.module.scss';
|
||||
|
||||
export const CumulativeMaxConnections: React.VFC<ConnectionSettingsFormProps> =
|
||||
({ connectionDBState, connectionDBStateDispatch }) => (
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<LabeledInput
|
||||
label="Cumulative Max Connections"
|
||||
tooltipText="Maximum number of database connections"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="50"
|
||||
value={
|
||||
connectionDBState.connectionSettings?.cumulative_max_connections ||
|
||||
undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_CUMULATIVE_MAX_CONNECTIONS',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="max-connections"
|
||||
/>
|
||||
</div>
|
||||
);
|
@ -0,0 +1,15 @@
|
||||
import { Collapse } from '@/new-components/deprecated';
|
||||
import React from 'react';
|
||||
import styles from '../../DataSources.module.scss';
|
||||
|
||||
export const FormContainer: React.FC = ({ children }) => (
|
||||
<div className="w-full mb-md">
|
||||
<div className="cursor-pointer w-full flex-initial align-middle">
|
||||
<Collapse title="Connection Settings" defaultOpen>
|
||||
<Collapse.Content>
|
||||
<div className={styles.connection_settings_form}>{children}</div>
|
||||
</Collapse.Content>
|
||||
</Collapse>
|
||||
</div>
|
||||
</div>
|
||||
);
|
@ -0,0 +1,29 @@
|
||||
import { LabeledInput } from '@/components/Common/LabeledInput';
|
||||
import { ConnectionSettingsFormProps } from '@/components/Services/Data/DataSources/ConnectionSettings/ConnectionSettingsForm';
|
||||
import React from 'react';
|
||||
import styles from '../../DataSources.module.scss';
|
||||
|
||||
export const IdleTimeout: React.VFC<ConnectionSettingsFormProps> = ({
|
||||
connectionDBState,
|
||||
connectionDBStateDispatch,
|
||||
}) => (
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<LabeledInput
|
||||
label="Idle Timeout"
|
||||
tooltipText="The idle timeout (in seconds) per connection"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="180"
|
||||
value={connectionDBState.connectionSettings?.idle_timeout ?? undefined}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_IDLE_TIMEOUT',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="idle-timeout"
|
||||
/>
|
||||
</div>
|
||||
);
|
@ -0,0 +1,48 @@
|
||||
import { ConnectionSettingsFormProps } from '@/components/Services/Data/DataSources/ConnectionSettings/ConnectionSettingsForm';
|
||||
import { IsolationLevelOptions } from '@/metadata/types';
|
||||
import { IconTooltip } from '@/new-components/Tooltip';
|
||||
import React from 'react';
|
||||
import styles from '../../DataSources.module.scss';
|
||||
|
||||
const ISOLATION_LEVEL_OPTIONS: readonly string[] = Object.freeze([
|
||||
'read-committed',
|
||||
'repeatable-read',
|
||||
'serializable',
|
||||
]);
|
||||
|
||||
const isIsolationLevelOption = (
|
||||
value: string
|
||||
): value is IsolationLevelOptions => {
|
||||
return ['read-committed', 'repeatable-read', 'serializable'].includes(value);
|
||||
};
|
||||
|
||||
export const IsolationLevel: React.VFC<ConnectionSettingsFormProps> = ({
|
||||
connectionDBState,
|
||||
connectionDBStateDispatch,
|
||||
}) => (
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<label className="flex items-center gap-1">
|
||||
<b>Isolation Level</b>
|
||||
<IconTooltip message="The transaction isolation level in which the queries made to the source will be run" />
|
||||
</label>
|
||||
<select
|
||||
className={`form-control ${styles.connnection_settings_form_input} cursor-pointer`}
|
||||
onChange={e => {
|
||||
// any way to do this?
|
||||
if (isIsolationLevelOption(e.target.value)) {
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_ISOLATION_LEVEL',
|
||||
data: e.target.value,
|
||||
});
|
||||
}
|
||||
}}
|
||||
value={connectionDBState.isolationLevel}
|
||||
>
|
||||
{ISOLATION_LEVEL_OPTIONS.map(o => (
|
||||
<option key={o} value={o}>
|
||||
{o}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
@ -0,0 +1,29 @@
|
||||
import { LabeledInput } from '@/components/Common/LabeledInput';
|
||||
import { ConnectionSettingsFormProps } from '@/components/Services/Data/DataSources/ConnectionSettings/ConnectionSettingsForm';
|
||||
import React from 'react';
|
||||
import styles from '../../DataSources.module.scss';
|
||||
|
||||
export const MaxConnections: React.VFC<ConnectionSettingsFormProps> = ({
|
||||
connectionDBState,
|
||||
connectionDBStateDispatch,
|
||||
}) => (
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<LabeledInput
|
||||
label="Max Connections"
|
||||
tooltipText="Maximum number of database connections per instance"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="50"
|
||||
value={connectionDBState.connectionSettings?.max_connections ?? undefined}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_MAX_CONNECTIONS',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="max-connections"
|
||||
/>
|
||||
</div>
|
||||
);
|
@ -0,0 +1,29 @@
|
||||
import { LabeledInput } from '@/components/Common/LabeledInput';
|
||||
import { ConnectionSettingsFormProps } from '@/components/Services/Data/DataSources/ConnectionSettings/ConnectionSettingsForm';
|
||||
import React from 'react';
|
||||
import styles from '../../DataSources.module.scss';
|
||||
|
||||
export const PoolTimeout: React.VFC<ConnectionSettingsFormProps> = ({
|
||||
connectionDBState,
|
||||
connectionDBStateDispatch,
|
||||
}) => (
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<LabeledInput
|
||||
label="Pool Timeout"
|
||||
tooltipText="Maximum time (in seconds) to wait while acquiring a Postgres connection from the pool"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="360"
|
||||
value={connectionDBState.connectionSettings?.pool_timeout ?? undefined}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_POOL_TIMEOUT',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="pool-timeout"
|
||||
/>
|
||||
</div>
|
||||
);
|
@ -0,0 +1,28 @@
|
||||
import { ConnectionSettingsFormProps } from '@/components/Services/Data/DataSources/ConnectionSettings/ConnectionSettingsForm';
|
||||
import { IconTooltip } from '@/new-components/Tooltip';
|
||||
import React from 'react';
|
||||
import styles from '../../DataSources.module.scss';
|
||||
|
||||
export const PreparedStatements: React.VFC<ConnectionSettingsFormProps> = ({
|
||||
connectionDBState,
|
||||
connectionDBStateDispatch,
|
||||
}) => (
|
||||
<div className={`${styles.add_mar_bottom_mid} ${styles.checkbox_margin_top}`}>
|
||||
<label className="inline-flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={connectionDBState.preparedStatements}
|
||||
className="legacy-input-fix"
|
||||
onChange={e => {
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_PREPARED_STATEMENTS',
|
||||
data: e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>{' '}
|
||||
|
||||
<b>Use Prepared Statements</b>
|
||||
<IconTooltip message="Prepared statements are disabled by default" />
|
||||
</label>
|
||||
</div>
|
||||
);
|
@ -0,0 +1,29 @@
|
||||
import { LabeledInput } from '@/components/Common/LabeledInput';
|
||||
import { ConnectionSettingsFormProps } from '@/components/Services/Data/DataSources/ConnectionSettings/ConnectionSettingsForm';
|
||||
import React from 'react';
|
||||
import styles from '../../DataSources.module.scss';
|
||||
|
||||
export const Retries: React.VFC<ConnectionSettingsFormProps> = ({
|
||||
connectionDBState,
|
||||
connectionDBStateDispatch,
|
||||
}) => (
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<LabeledInput
|
||||
label="Retries"
|
||||
tooltipText="Number of retries to perform"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="1"
|
||||
value={connectionDBState.connectionSettings?.retries ?? undefined}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_RETRIES',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="retries"
|
||||
/>
|
||||
</div>
|
||||
);
|
@ -0,0 +1,123 @@
|
||||
import { LabeledInput } from '@/components/Common/LabeledInput';
|
||||
import { ConnectionSettingsFormProps } from '@/components/Services/Data/DataSources/ConnectionSettings/ConnectionSettingsForm';
|
||||
import { SSLModeOptions } from '@/metadata/types';
|
||||
import { IconTooltip } from '@/new-components/Tooltip';
|
||||
import React from 'react';
|
||||
import { FaCaretDown, FaCaretRight } from 'react-icons/fa';
|
||||
import styles from '../../DataSources.module.scss';
|
||||
|
||||
export const SSLCertificates: React.VFC<ConnectionSettingsFormProps> = ({
|
||||
connectionDBState,
|
||||
connectionDBStateDispatch,
|
||||
}) => {
|
||||
const [showCertSettings, setShowCertSettings] = React.useState(false);
|
||||
|
||||
const handleCertificateSettingsClick = () =>
|
||||
setShowCertSettings(!showCertSettings);
|
||||
|
||||
return (
|
||||
<div className={styles.add_mar_top}>
|
||||
<div
|
||||
onClick={handleCertificateSettingsClick}
|
||||
className={styles.connection_settings_header}
|
||||
>
|
||||
(showCertSettings ? <FaCaretDown /> : <FaCaretRight />
|
||||
{` SSL Certificates Settings`}
|
||||
</div>
|
||||
<div className={styles.text_muted}>
|
||||
Certificates will be loaded from{' '}
|
||||
<a href="https://hasura.io/docs/latest/graphql/cloud/projects/create.html#existing-database">
|
||||
environment variables
|
||||
</a>
|
||||
</div>
|
||||
{showCertSettings && (
|
||||
<div className="mt-xs">
|
||||
<div className="mb-xs">
|
||||
<label className="flex items-center gap-1">
|
||||
<b>SSL Mode</b>
|
||||
<IconTooltip message="SSL certificate verification mode" />
|
||||
</label>
|
||||
<select
|
||||
className="form-control"
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_SSL_MODE',
|
||||
data: (e.target.value as SSLModeOptions) || undefined,
|
||||
})
|
||||
}
|
||||
value={connectionDBState.sslConfiguration?.sslmode}
|
||||
>
|
||||
<option value="">--</option>
|
||||
<option value="disable">disable</option>
|
||||
<option value="verify-ca">verify-ca</option>
|
||||
<option value="verify-full">verify-full</option>
|
||||
</select>
|
||||
</div>
|
||||
<LabeledInput
|
||||
label="SSL Root Certificate"
|
||||
type="text"
|
||||
placeholder="SSL_ROOT_CERT"
|
||||
tooltipText="Environment variable that stores trusted certificate authorities"
|
||||
value={
|
||||
connectionDBState.sslConfiguration?.sslrootcert?.from_env ??
|
||||
undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_SSL_ROOT_CERT',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<LabeledInput
|
||||
label="SSL Certificate"
|
||||
type="text"
|
||||
placeholder="SSL_CERT"
|
||||
tooltipText="Environment variable that stores the client certificate (Optional)"
|
||||
value={
|
||||
connectionDBState.sslConfiguration?.sslcert?.from_env ?? undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_SSL_CERT',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<LabeledInput
|
||||
label="SSL Key"
|
||||
type="text"
|
||||
placeholder="SSL_KEY"
|
||||
tooltipText="Environment variable that stores the client private key (Optional)"
|
||||
value={
|
||||
connectionDBState.sslConfiguration?.sslkey?.from_env ?? undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_SSL_KEY',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<LabeledInput
|
||||
label="SSL Password"
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="SSL_PASSWORD"
|
||||
tooltipText="Environment variable that stores the password if the client private key is encrypted (Optional)"
|
||||
value={
|
||||
connectionDBState.sslConfiguration?.sslpassword?.from_env ??
|
||||
undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_SSL_PASSWORD',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,10 @@
|
||||
export { FormContainer } from './FormContainer';
|
||||
export { CumulativeMaxConnections } from './CumulativeMaxConnections';
|
||||
export { MaxConnections } from './MaxConnections';
|
||||
export { IdleTimeout } from './IdleTimeout';
|
||||
export { Retries } from './Retries';
|
||||
export { IsolationLevel } from './IsolationLevel';
|
||||
export { PoolTimeout } from './PoolTimeout';
|
||||
export { ConnectionLifetime } from './ConnectionLifetime';
|
||||
export { PreparedStatements } from './PreparedStatements';
|
||||
export { SSLCertificates } from './SSLCertificates';
|
@ -1,340 +0,0 @@
|
||||
import React, { Dispatch, useState } from 'react';
|
||||
import { Collapse } from '@/new-components/deprecated';
|
||||
import { IconTooltip } from '@/new-components/Tooltip';
|
||||
|
||||
import { FaCaretDown, FaCaretRight } from 'react-icons/fa';
|
||||
|
||||
import { ConnectDBActions, ConnectDBState } from './state';
|
||||
import { LabeledInput } from '../../../Common/LabeledInput';
|
||||
import { getSupportedDrivers } from '../../../../dataSources';
|
||||
|
||||
import styles from './DataSources.module.scss';
|
||||
import {
|
||||
SSLModeOptions,
|
||||
IsolationLevelOptions,
|
||||
} from '../../../../metadata/types';
|
||||
|
||||
export interface ConnectionSettingsFormProps {
|
||||
// Connect DB State Props
|
||||
connectionDBState: ConnectDBState;
|
||||
connectionDBStateDispatch: Dispatch<ConnectDBActions>;
|
||||
}
|
||||
|
||||
const ConnectionSettingsForm: React.FC<ConnectionSettingsFormProps> = ({
|
||||
connectionDBState,
|
||||
connectionDBStateDispatch,
|
||||
}) => {
|
||||
const [certificateSettingsState, setCertificateSettingsState] =
|
||||
useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
{getSupportedDrivers('connectDbForm.connectionSettings').includes(
|
||||
connectionDBState.dbType
|
||||
) ? (
|
||||
<div className="w-full mb-md">
|
||||
<div className="cursor-pointer w-full flex-initial align-middle">
|
||||
<Collapse title="Connection Settings" defaultOpen={false}>
|
||||
<Collapse.Content>
|
||||
<div className={styles.connection_settings_form}>
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<LabeledInput
|
||||
label="Max Connections"
|
||||
tooltipText="Maximum number of connections to be kept in the pool"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="50"
|
||||
value={
|
||||
connectionDBState.connectionSettings?.max_connections ??
|
||||
undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_MAX_CONNECTIONS',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="max-connections"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<LabeledInput
|
||||
label="Idle Timeout"
|
||||
tooltipText="The idle timeout (in seconds) per connection"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="180"
|
||||
value={
|
||||
connectionDBState.connectionSettings?.idle_timeout ??
|
||||
undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_IDLE_TIMEOUT',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="idle-timeout"
|
||||
/>
|
||||
</div>
|
||||
{getSupportedDrivers('connectDbForm.retries').includes(
|
||||
connectionDBState.dbType
|
||||
) ? (
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<LabeledInput
|
||||
label="Retries"
|
||||
tooltipText="Number of retries to perform"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="1"
|
||||
value={
|
||||
connectionDBState.connectionSettings?.retries ??
|
||||
undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_RETRIES',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="retries"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{getSupportedDrivers('connectDbForm.pool_timeout').includes(
|
||||
connectionDBState.dbType
|
||||
) ? (
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<LabeledInput
|
||||
label="Pool Timeout"
|
||||
tooltipText="Maximum time (in seconds) to wait while acquiring a Postgres connection from the pool"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="360"
|
||||
value={
|
||||
connectionDBState.connectionSettings?.pool_timeout ??
|
||||
undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_POOL_TIMEOUT',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="pool-timeout"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{getSupportedDrivers(
|
||||
'connectDbForm.connection_lifetime'
|
||||
).includes(connectionDBState.dbType) ? (
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<LabeledInput
|
||||
label="Connection Lifetime"
|
||||
tooltipText="Time (in seconds) from connection creation after which the connection should be destroyed and a new one created. A value of 0 indicates we should never destroy an active connection. If 0 is passed, memory from large query results may not be reclaimed."
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="600"
|
||||
value={
|
||||
connectionDBState.connectionSettings
|
||||
?.connection_lifetime ?? undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_CONNECTION_LIFETIME',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="connection-lifetime"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{getSupportedDrivers(
|
||||
'connectDbForm.isolation_level'
|
||||
).includes(connectionDBState.dbType) && (
|
||||
<div className={styles.connection_settings_input_layout}>
|
||||
<label className="flex items-center gap-1">
|
||||
<b>Isolation Level</b>
|
||||
<IconTooltip message="The transaction isolation level in which the queries made to the source will be run" />
|
||||
</label>
|
||||
<select
|
||||
className={`form-control ${styles.connnection_settings_form_input} cursor-pointer`}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_ISOLATION_LEVEL',
|
||||
data: e.target.value as IsolationLevelOptions,
|
||||
})
|
||||
}
|
||||
value={connectionDBState.isolationLevel}
|
||||
>
|
||||
<option value="read-committed">read-committed</option>
|
||||
<option value="repeatable-read">repeatable-read</option>
|
||||
<option value="serializable">serializable</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
{getSupportedDrivers(
|
||||
'connectDbForm.prepared_statements'
|
||||
).includes(connectionDBState.dbType) ? (
|
||||
<div
|
||||
className={`${styles.add_mar_bottom_mid} ${styles.checkbox_margin_top}`}
|
||||
>
|
||||
<label className="inline-flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={connectionDBState.preparedStatements}
|
||||
className="legacy-input-fix"
|
||||
onChange={e => {
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_PREPARED_STATEMENTS',
|
||||
data: e.target.checked,
|
||||
});
|
||||
}}
|
||||
/>{' '}
|
||||
|
||||
<b>Use Prepared Statements</b>
|
||||
<IconTooltip message="Prepared statements are disabled by default" />
|
||||
</label>
|
||||
</div>
|
||||
) : null}
|
||||
{getSupportedDrivers(
|
||||
'connectDbForm.ssl_certificates'
|
||||
).includes(connectionDBState.dbType) && (
|
||||
<div className={styles.add_mar_top}>
|
||||
<div
|
||||
onClick={() =>
|
||||
setCertificateSettingsState(!certificateSettingsState)
|
||||
}
|
||||
className={styles.connection_settings_header}
|
||||
>
|
||||
{certificateSettingsState ? (
|
||||
<FaCaretDown />
|
||||
) : (
|
||||
<FaCaretRight />
|
||||
)}
|
||||
{' '}
|
||||
SSL Certificates Settings
|
||||
</div>
|
||||
<div className={styles.text_muted}>
|
||||
Certificates will be loaded from{' '}
|
||||
<a href="https://hasura.io/docs/latest/graphql/cloud/projects/create.html#existing-database">
|
||||
environment variables
|
||||
</a>
|
||||
</div>
|
||||
{certificateSettingsState ? (
|
||||
<div className="mt-xs">
|
||||
<div className="mb-xs">
|
||||
<label className="flex items-center gap-1">
|
||||
<b>SSL Mode</b>
|
||||
<IconTooltip message="SSL certificate verification mode" />
|
||||
</label>
|
||||
<select
|
||||
className="form-control"
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_SSL_MODE',
|
||||
data:
|
||||
(e.target.value as SSLModeOptions) ||
|
||||
undefined,
|
||||
})
|
||||
}
|
||||
value={
|
||||
connectionDBState.sslConfiguration?.sslmode
|
||||
}
|
||||
>
|
||||
<option value="">--</option>
|
||||
<option value="disable">disable</option>
|
||||
<option value="verify-ca">verify-ca</option>
|
||||
<option value="verify-full">verify-full</option>
|
||||
</select>
|
||||
</div>
|
||||
<LabeledInput
|
||||
label="SSL Root Certificate"
|
||||
type="text"
|
||||
placeholder="SSL_ROOT_CERT"
|
||||
tooltipText="Environment variable that stores trusted certificate authorities"
|
||||
value={
|
||||
connectionDBState.sslConfiguration?.sslrootcert
|
||||
?.from_env ?? undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_SSL_ROOT_CERT',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<LabeledInput
|
||||
label="SSL Certificate"
|
||||
type="text"
|
||||
placeholder="SSL_CERT"
|
||||
tooltipText="Environment variable that stores the client certificate (Optional)"
|
||||
value={
|
||||
connectionDBState.sslConfiguration?.sslcert
|
||||
?.from_env ?? undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_SSL_CERT',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<LabeledInput
|
||||
label="SSL Key"
|
||||
type="text"
|
||||
placeholder="SSL_KEY"
|
||||
tooltipText="Environment variable that stores the client private key (Optional)"
|
||||
value={
|
||||
connectionDBState.sslConfiguration?.sslkey
|
||||
?.from_env ?? undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_SSL_KEY',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<LabeledInput
|
||||
label="SSL Password"
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="SSL_PASSWORD"
|
||||
tooltipText="Environment variable that stores the password if the client private key is encrypted (Optional)"
|
||||
value={
|
||||
connectionDBState.sslConfiguration?.sslpassword
|
||||
?.from_env ?? undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_SSL_PASSWORD',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Collapse.Content>
|
||||
</Collapse>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectionSettingsForm;
|
@ -1,3 +1,4 @@
|
||||
import { ConsoleType } from '../../../../../../utils/envUtils';
|
||||
import {
|
||||
checkNestedFieldValueInErrJson,
|
||||
maskPostgresError,
|
||||
@ -173,7 +174,7 @@ describe('Test checkNestedFieldValueInErrJson', () => {
|
||||
describe('newSampleDBTrial test', () => {
|
||||
test('consoleType==oss', () => {
|
||||
const sampleDBTrialService = newSampleDBTrial({
|
||||
consoleType: 'oss',
|
||||
consoleType: 'oss' as ConsoleType,
|
||||
hasuraCloudProjectId: 'project_id',
|
||||
cohortConfig: {
|
||||
databaseUrl: 'test_db_url',
|
||||
@ -188,7 +189,7 @@ describe('newSampleDBTrial test', () => {
|
||||
|
||||
test('consoleType==cloud, null config', () => {
|
||||
const sampleDBTrialService = newSampleDBTrial({
|
||||
consoleType: 'cloud',
|
||||
consoleType: 'cloud' as ConsoleType,
|
||||
hasuraCloudProjectId: 'project_id',
|
||||
cohortConfig: null,
|
||||
});
|
||||
@ -200,7 +201,7 @@ describe('newSampleDBTrial test', () => {
|
||||
|
||||
test('consoleType==cloud', () => {
|
||||
const sampleDBTrialService = newSampleDBTrial({
|
||||
consoleType: 'cloud',
|
||||
consoleType: 'cloud' as ConsoleType,
|
||||
hasuraCloudProjectId: 'project_id',
|
||||
cohortConfig: {
|
||||
databaseUrl: 'test_db_url',
|
||||
@ -215,7 +216,7 @@ describe('newSampleDBTrial test', () => {
|
||||
|
||||
test('isExploringSampleDB', () => {
|
||||
const sampleDBTrialService = newSampleDBTrial({
|
||||
consoleType: 'cloud',
|
||||
consoleType: 'cloud' as ConsoleType,
|
||||
hasuraCloudProjectId: 'project_id',
|
||||
cohortConfig: {
|
||||
databaseUrl: 'test_db_url',
|
||||
@ -322,7 +323,7 @@ describe('newSampleDBTrial test', () => {
|
||||
|
||||
test('hasAddedSampleDB', () => {
|
||||
const sampleDBTrialService = newSampleDBTrial({
|
||||
consoleType: 'cloud',
|
||||
consoleType: 'cloud' as ConsoleType,
|
||||
hasuraCloudProjectId: 'project_id',
|
||||
cohortConfig: {
|
||||
databaseUrl: 'test_db_url',
|
||||
@ -441,7 +442,7 @@ describe('maskPostgresError tests', () => {
|
||||
errorJson: {
|
||||
status_code: '42501',
|
||||
},
|
||||
consoleType: 'cloud',
|
||||
consoleType: 'cloud' as ConsoleType,
|
||||
},
|
||||
output: null,
|
||||
},
|
||||
@ -468,7 +469,7 @@ describe('maskPostgresError tests', () => {
|
||||
errorJson: {
|
||||
status_code: '42501',
|
||||
},
|
||||
consoleType: 'cloud',
|
||||
consoleType: 'cloud' as ConsoleType,
|
||||
},
|
||||
output: maskedErrorMessage,
|
||||
},
|
||||
@ -500,7 +501,7 @@ describe('maskPostgresError tests', () => {
|
||||
errorJson: {
|
||||
status_code: '42501',
|
||||
},
|
||||
consoleType: 'cloud',
|
||||
consoleType: 'cloud' as ConsoleType,
|
||||
},
|
||||
output: null,
|
||||
},
|
||||
@ -535,7 +536,7 @@ describe('maskPostgresError tests', () => {
|
||||
errorJson: {
|
||||
status_code: '42501',
|
||||
},
|
||||
consoleType: 'cloud',
|
||||
consoleType: 'cloud' as ConsoleType,
|
||||
},
|
||||
output: null,
|
||||
},
|
||||
@ -570,7 +571,7 @@ describe('maskPostgresError tests', () => {
|
||||
errorJson: {
|
||||
status_code: '42501',
|
||||
},
|
||||
consoleType: 'cloud',
|
||||
consoleType: 'cloud' as ConsoleType,
|
||||
},
|
||||
output: maskedErrorMessage,
|
||||
},
|
||||
@ -614,7 +615,7 @@ describe('maskPostgresError tests', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
consoleType: 'cloud',
|
||||
consoleType: 'cloud' as ConsoleType,
|
||||
},
|
||||
output: maskedErrorMessage,
|
||||
},
|
||||
@ -658,7 +659,7 @@ describe('maskPostgresError tests', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
consoleType: 'oss',
|
||||
consoleType: 'oss' as ConsoleType,
|
||||
},
|
||||
output: null,
|
||||
},
|
||||
|
@ -1,4 +1,5 @@
|
||||
import pickBy from 'lodash.pickby';
|
||||
import produce from 'immer';
|
||||
import { Driver, getSupportedDrivers } from '../../../../dataSources';
|
||||
import { makeConnectionStringFromConnectionParams } from './ManageDBUtils';
|
||||
import { addDataSource, renameDataSource } from '../../../../metadata/actions';
|
||||
@ -256,6 +257,7 @@ export type ConnectDBActions =
|
||||
| { type: 'UPDATE_DB_PASSWORD'; data: string }
|
||||
| { type: 'UPDATE_DB_DATABASE_NAME'; data: string }
|
||||
| { type: 'UPDATE_MAX_CONNECTIONS'; data: string }
|
||||
| { type: 'UPDATE_CUMULATIVE_MAX_CONNECTIONS'; data: string }
|
||||
| { type: 'UPDATE_RETRIES'; data: string }
|
||||
| { type: 'UPDATE_IDLE_TIMEOUT'; data: string }
|
||||
| { type: 'UPDATE_POOL_TIMEOUT'; data: string }
|
||||
@ -385,6 +387,12 @@ export const connectDBReducer = (
|
||||
max_connections: setNumberFromString(action.data),
|
||||
},
|
||||
};
|
||||
case 'UPDATE_CUMULATIVE_MAX_CONNECTIONS':
|
||||
return produce(state, (draft: ConnectDBState) => {
|
||||
draft.connectionSettings = state.connectionSettings ?? {};
|
||||
draft.connectionSettings.cumulative_max_connections =
|
||||
setNumberFromString(action.data);
|
||||
});
|
||||
case 'UPDATE_RETRIES':
|
||||
return {
|
||||
...state,
|
||||
@ -660,6 +668,10 @@ export const makeReadReplicaConnectionObject = (
|
||||
if (stateVal.connectionSettings?.max_connections) {
|
||||
pool_settings.max_connections = stateVal.connectionSettings.max_connections;
|
||||
}
|
||||
if (stateVal.connectionSettings?.cumulative_max_connections) {
|
||||
pool_settings.cumulative_max_connections =
|
||||
stateVal.connectionSettings.cumulative_max_connections;
|
||||
}
|
||||
if (stateVal.connectionSettings?.idle_timeout) {
|
||||
pool_settings.idle_timeout = stateVal.connectionSettings.idle_timeout;
|
||||
}
|
||||
|
@ -215,6 +215,7 @@ export const supportedFeatures: DeepRequired<SupportedFeaturesType> = {
|
||||
isolation_level: false,
|
||||
connectionSettings: false,
|
||||
retries: false,
|
||||
cumulativeMaxConnections: false,
|
||||
extensions_schema: false,
|
||||
pool_timeout: false,
|
||||
connection_lifetime: false,
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { TriggerOperation } from '@/components/Common/FilterQuery/state';
|
||||
import globals from '@/Globals';
|
||||
import { isEnvironmentSupportMultiTenantConnectionPooling } from '@/utils/proConsole';
|
||||
import React from 'react';
|
||||
import { DeepRequired } from 'ts-essentials';
|
||||
import { DataSourcesAPI } from '../..';
|
||||
@ -234,6 +236,8 @@ export const supportedFeatures: DeepRequired<SupportedFeaturesType> = {
|
||||
isolation_level: false,
|
||||
connectionSettings: true,
|
||||
retries: false,
|
||||
cumulativeMaxConnections:
|
||||
isEnvironmentSupportMultiTenantConnectionPooling(globals),
|
||||
extensions_schema: false,
|
||||
pool_timeout: false,
|
||||
connection_lifetime: false,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { isEnvironmentSupportMultiTenantConnectionPooling } from '@/utils/proConsole';
|
||||
import { DeepRequired } from 'ts-essentials';
|
||||
|
||||
import {
|
||||
@ -756,6 +757,8 @@ export const supportedFeatures: DeepRequired<SupportedFeaturesType> = {
|
||||
prepared_statements: true,
|
||||
isolation_level: true,
|
||||
connectionSettings: true,
|
||||
cumulativeMaxConnections:
|
||||
isEnvironmentSupportMultiTenantConnectionPooling(globals),
|
||||
retries: true,
|
||||
extensions_schema: true,
|
||||
pool_timeout: true,
|
||||
|
@ -407,25 +407,31 @@ export type SupportedFeaturesType = {
|
||||
statementTimeout: boolean;
|
||||
tracking: boolean;
|
||||
};
|
||||
connectDbForm: {
|
||||
enabled: boolean;
|
||||
connectionParameters: boolean;
|
||||
databaseURL: boolean;
|
||||
environmentVariable: boolean;
|
||||
read_replicas: {
|
||||
create: boolean;
|
||||
edit: boolean;
|
||||
};
|
||||
prepared_statements: boolean;
|
||||
isolation_level: boolean;
|
||||
connectionSettings: boolean;
|
||||
retries: boolean;
|
||||
extensions_schema: boolean;
|
||||
pool_timeout: boolean;
|
||||
connection_lifetime: boolean;
|
||||
ssl_certificates: boolean;
|
||||
namingConvention: boolean;
|
||||
connectDbForm: ConnectDbForm;
|
||||
};
|
||||
|
||||
type ConnectDbForm = {
|
||||
enabled: boolean;
|
||||
connectionParameters: boolean;
|
||||
databaseURL: boolean;
|
||||
environmentVariable: boolean;
|
||||
read_replicas: {
|
||||
create: boolean;
|
||||
edit: boolean;
|
||||
};
|
||||
extensions_schema: boolean;
|
||||
namingConvention: boolean;
|
||||
} & DbConnectionSettings;
|
||||
|
||||
export type DbConnectionSettings = {
|
||||
connectionSettings: boolean;
|
||||
cumulativeMaxConnections: boolean;
|
||||
retries: boolean;
|
||||
pool_timeout: boolean;
|
||||
connection_lifetime: boolean;
|
||||
isolation_level: boolean;
|
||||
prepared_statements: boolean;
|
||||
ssl_certificates: boolean;
|
||||
};
|
||||
|
||||
type Tables = ReduxState['tables'];
|
||||
|
@ -1057,6 +1057,7 @@ export interface SSLConfigOptions {
|
||||
|
||||
export interface ConnectionPoolSettings {
|
||||
max_connections?: number;
|
||||
cumulative_max_connections?: number;
|
||||
idle_timeout?: number;
|
||||
retries?: number;
|
||||
pool_timeout?: number;
|
||||
|
31
console/src/utils/__tests__/envUtils.spec.ts
Normal file
31
console/src/utils/__tests__/envUtils.spec.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { parseConsoleType } from '../envUtils';
|
||||
|
||||
describe('parseConsoleType', () => {
|
||||
describe('when parseConsoleType is called with env var "oss"', () => {
|
||||
it('returns oss', () => {
|
||||
expect(parseConsoleType('oss')).toBe('oss');
|
||||
});
|
||||
});
|
||||
describe('when parseConsoleType is called with env var "cloud"', () => {
|
||||
it('returns cloud', () => {
|
||||
expect(parseConsoleType('cloud')).toBe('cloud');
|
||||
});
|
||||
});
|
||||
describe('when parseConsoleType is called with env var "pro"', () => {
|
||||
it('returns pro', () => {
|
||||
expect(parseConsoleType('pro')).toBe('pro');
|
||||
});
|
||||
});
|
||||
describe('when parseConsoleType is called with env var "pro-lite"', () => {
|
||||
it('returns pro-lite', () => {
|
||||
expect(parseConsoleType('pro-lite')).toBe('pro-lite');
|
||||
});
|
||||
});
|
||||
describe('when parseConsoleType is called with env var "invalid"', () => {
|
||||
it('returns invalid', () => {
|
||||
expect(() => parseConsoleType('invalid')).toThrow(
|
||||
'Unmanaged console type "invalid"'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
isEnvironmentSupportMultiTenantConnectionPooling,
|
||||
isMonitoringTabSupportedEnvironment,
|
||||
isProConsole,
|
||||
ProConsoleEnv,
|
||||
@ -142,3 +143,79 @@ describe('isMonitoringTabSupportedEnvironment', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isEnvironmentSupportMultiTenantConnectionPooling', () => {
|
||||
// Server Runtimes
|
||||
describe('when consoleMode is server and consoleType is cloud (ie. Production cloud runtime)', () => {
|
||||
it('returns true', () => {
|
||||
const env: ProConsoleEnv = {
|
||||
consoleMode: 'server',
|
||||
consoleType: 'cloud',
|
||||
};
|
||||
expect(isEnvironmentSupportMultiTenantConnectionPooling(env)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when consoleMode is server and consoleType is pro (ie. Self hosted runtime)', () => {
|
||||
it('returns true', () => {
|
||||
const env: ProConsoleEnv = {
|
||||
consoleMode: 'server',
|
||||
consoleType: 'pro',
|
||||
};
|
||||
expect(isEnvironmentSupportMultiTenantConnectionPooling(env)).toBe(false);
|
||||
});
|
||||
});
|
||||
describe('when consoleMode is server and consoleType is pro-lite', () => {
|
||||
it('returns false', () => {
|
||||
const env: ProConsoleEnv = {
|
||||
consoleMode: 'server',
|
||||
consoleType: 'pro-lite',
|
||||
};
|
||||
expect(isEnvironmentSupportMultiTenantConnectionPooling(env)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when consoleMode is server and consoleType is oss', () => {
|
||||
it('returns false', () => {
|
||||
const env: ProConsoleEnv = {
|
||||
consoleMode: 'server',
|
||||
consoleType: 'oss',
|
||||
};
|
||||
expect(isEnvironmentSupportMultiTenantConnectionPooling(env)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// CLI runtimes
|
||||
// Cloud and Self hosted EE (with LUX)
|
||||
describe('when consoleMode is cli and pro is true', () => {
|
||||
it('returns true', () => {
|
||||
const env: ProConsoleEnv = {
|
||||
consoleMode: 'cli',
|
||||
pro: true,
|
||||
consoleType: undefined,
|
||||
};
|
||||
expect(isEnvironmentSupportMultiTenantConnectionPooling(env)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// OSS console CLI
|
||||
describe('when consoleMode is cli and consoleType is oss', () => {
|
||||
it('returns false', () => {
|
||||
const env: ProConsoleEnv = {
|
||||
consoleMode: 'cli',
|
||||
consoleType: 'oss',
|
||||
};
|
||||
expect(isEnvironmentSupportMultiTenantConnectionPooling(env)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// EE lite CLI mode
|
||||
describe('when consoleMode is cli and pro is undefined', () => {
|
||||
it('returns false', () => {
|
||||
const env: ProConsoleEnv = {
|
||||
consoleMode: 'cli',
|
||||
};
|
||||
expect(isEnvironmentSupportMultiTenantConnectionPooling(env)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
14
console/src/utils/envUtils.ts
Normal file
14
console/src/utils/envUtils.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export type ConsoleType = 'oss' | 'cloud' | 'pro' | 'pro-lite';
|
||||
|
||||
export function parseConsoleType(envConsoleType: unknown): ConsoleType {
|
||||
switch (envConsoleType) {
|
||||
case 'oss':
|
||||
case 'cloud':
|
||||
case 'pro':
|
||||
case 'pro-lite':
|
||||
return envConsoleType;
|
||||
|
||||
default:
|
||||
throw new Error(`Unmanaged console type "${envConsoleType}"`);
|
||||
}
|
||||
}
|
@ -33,3 +33,16 @@ export const isMonitoringTabSupportedEnvironment = (env: ProConsoleEnv) => {
|
||||
// there should not be any other console modes
|
||||
throw new Error(`Invalid consoleMode: ${env.consoleMode}`);
|
||||
};
|
||||
|
||||
export const isEnvironmentSupportMultiTenantConnectionPooling = (
|
||||
env: ProConsoleEnv
|
||||
) => {
|
||||
if (env.consoleMode === 'server') return env.consoleType === 'cloud';
|
||||
// cloud and current self hosted setup will have pro:true
|
||||
// FIX ME : currently in CLI mode there is no way to differentiate cloud and pro mode
|
||||
// This can be added once the CLI adds support of consoleType in the env vars provided to console.
|
||||
else if (env.consoleMode === 'cli') return env.pro === true;
|
||||
|
||||
// there should not be any other console modes
|
||||
throw new Error(`Invalid consoleMode: ${env.consoleMode}`);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user