SQL: Update configuration pages (#75525)

* update psql config page to follow guidelines

* make small changes to mysql config page

* update shared tls config to match guidelines

* revert back to text area

* remove type assertion

* prettier

* remove unused imports

* update required fields

* add secure sox proxy to additional settings

* make additional settings collapsible

* make user permissions collapsable

* make db name and password optional

* mysql: move from alert to collapse
This commit is contained in:
Gareth Dawson 2023-10-23 11:29:28 +01:00 committed by GitHub
parent e94e283cc6
commit f6a0f6912f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 440 additions and 316 deletions

View File

@ -7,7 +7,8 @@ import {
onUpdateDatasourceSecureJsonDataOption, onUpdateDatasourceSecureJsonDataOption,
updateDatasourcePluginResetOption, updateDatasourcePluginResetOption,
} from '@grafana/data'; } from '@grafana/data';
import { InlineField, SecretTextArea } from '@grafana/ui'; import { Stack } from '@grafana/experimental';
import { Field, Icon, Label, SecretTextArea, Tooltip } from '@grafana/ui';
export interface Props<T extends DataSourceJsonData, S> { export interface Props<T extends DataSourceJsonData, S> {
editorProps: DataSourcePluginOptionsEditorProps<T, S>; editorProps: DataSourcePluginOptionsEditorProps<T, S>;
@ -18,20 +19,31 @@ export interface Props<T extends DataSourceJsonData, S> {
} }
export const TLSSecretsConfig = <T extends DataSourceJsonData, S extends {} = {}>(props: Props<T, S>) => { export const TLSSecretsConfig = <T extends DataSourceJsonData, S extends {} = {}>(props: Props<T, S>) => {
const { labelWidth, editorProps, showCACert, showKeyPair = true } = props; const { editorProps, showCACert, showKeyPair = true } = props;
const { secureJsonFields } = editorProps.options; const { secureJsonFields } = editorProps.options;
return ( return (
<> <>
{showKeyPair ? ( {showKeyPair ? (
<InlineField <Field
tooltip={ label={
<span>To authenticate with an TLS/SSL client certificate, provide the client certificate here.</span> <Label>
<Stack gap={0.5}>
<span>TLS/SSL Client Certificate</span>
<Tooltip
content={
<span>
To authenticate with an TLS/SSL client certificate, provide the client certificate here.
</span>
}
>
<Icon name="info-circle" size="sm" />
</Tooltip>
</Stack>
</Label>
} }
labelWidth={labelWidth}
label="TLS/SSL Client Certificate"
> >
<SecretTextArea <SecretTextArea
placeholder="Begins with -----BEGIN CERTIFICATE-----" placeholder="-----BEGIN CERTIFICATE-----"
cols={45} cols={45}
rows={7} rows={7}
isConfigured={secureJsonFields && secureJsonFields.tlsClientCert} isConfigured={secureJsonFields && secureJsonFields.tlsClientCert}
@ -39,17 +51,28 @@ export const TLSSecretsConfig = <T extends DataSourceJsonData, S extends {} = {}
onReset={() => { onReset={() => {
updateDatasourcePluginResetOption(editorProps, 'tlsClientCert'); updateDatasourcePluginResetOption(editorProps, 'tlsClientCert');
}} }}
></SecretTextArea> />
</InlineField> </Field>
) : null} ) : null}
{showCACert ? ( {showCACert ? (
<InlineField <Field
tooltip={<span>If the selected TLS/SSL mode requires a server root certificate, provide it here.</span>} label={
labelWidth={labelWidth} <Label>
label="TLS/SSL Root Certificate" <Stack gap={0.5}>
<span>TLS/SSL Root Certificate</span>
<Tooltip
content={
<span>If the selected TLS/SSL mode requires a server root certificate, provide it here.</span>
}
>
<Icon name="info-circle" size="sm" />
</Tooltip>
</Stack>
</Label>
}
> >
<SecretTextArea <SecretTextArea
placeholder="Begins with -----BEGIN CERTIFICATE-----" placeholder="-----BEGIN CERTIFICATE-----"
cols={45} cols={45}
rows={7} rows={7}
isConfigured={secureJsonFields && secureJsonFields.tlsCACert} isConfigured={secureJsonFields && secureJsonFields.tlsCACert}
@ -57,17 +80,26 @@ export const TLSSecretsConfig = <T extends DataSourceJsonData, S extends {} = {}
onReset={() => { onReset={() => {
updateDatasourcePluginResetOption(editorProps, 'tlsCACert'); updateDatasourcePluginResetOption(editorProps, 'tlsCACert');
}} }}
></SecretTextArea> />
</InlineField> </Field>
) : null} ) : null}
{showKeyPair ? ( {showKeyPair ? (
<InlineField <Field
tooltip={<span>To authenticate with a client TLS/SSL certificate, provide the key here.</span>} label={
labelWidth={labelWidth} <Label>
label="TLS/SSL Client Key" <Stack gap={0.5}>
<span>TLS/SSL Client Key</span>
<Tooltip
content={<span>To authenticate with a client TLS/SSL certificate, provide the key here.</span>}
>
<Icon name="info-circle" size="sm" />
</Tooltip>
</Stack>
</Label>
}
> >
<SecretTextArea <SecretTextArea
placeholder="Begins with -----BEGIN RSA PRIVATE KEY-----" placeholder="-----BEGIN RSA PRIVATE KEY-----"
cols={45} cols={45}
rows={7} rows={7}
isConfigured={secureJsonFields && secureJsonFields.tlsClientKey} isConfigured={secureJsonFields && secureJsonFields.tlsClientKey}
@ -75,8 +107,8 @@ export const TLSSecretsConfig = <T extends DataSourceJsonData, S extends {} = {}
onReset={() => { onReset={() => {
updateDatasourcePluginResetOption(editorProps, 'tlsClientKey'); updateDatasourcePluginResetOption(editorProps, 'tlsClientKey');
}} }}
></SecretTextArea> />
</InlineField> </Field>
) : null} ) : null}
</> </>
); );

View File

@ -1,4 +1,4 @@
import React, { SyntheticEvent } from 'react'; import React, { SyntheticEvent, useState } from 'react';
import { import {
DataSourcePluginOptionsEditorProps, DataSourcePluginOptionsEditorProps,
@ -7,16 +7,15 @@ import {
updateDatasourcePluginJsonDataOption, updateDatasourcePluginJsonDataOption,
updateDatasourcePluginResetOption, updateDatasourcePluginResetOption,
} from '@grafana/data'; } from '@grafana/data';
import { ConfigSection, DataSourceDescription, Stack } from '@grafana/experimental'; import { ConfigSection, ConfigSubSection, DataSourceDescription, Stack } from '@grafana/experimental';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { import {
Alert, Collapse,
Divider, Divider,
Field, Field,
Icon, Icon,
Input, Input,
Label, Label,
Link,
SecretInput, SecretInput,
SecureSocksProxySettings, SecureSocksProxySettings,
Switch, Switch,
@ -29,6 +28,8 @@ import { useMigrateDatabaseFields } from 'app/features/plugins/sql/components/co
import { MySQLOptions } from '../types'; import { MySQLOptions } from '../types';
export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<MySQLOptions>) => { export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<MySQLOptions>) => {
const [isOpen, setIsOpen] = useState(true);
const { options, onOptionsChange } = props; const { options, onOptionsChange } = props;
const jsonData = options.jsonData; const jsonData = options.jsonData;
@ -57,11 +58,22 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<My
<DataSourceDescription <DataSourceDescription
dataSourceName="MySQL" dataSourceName="MySQL"
docsLink="https://grafana.com/docs/grafana/latest/datasources/mysql/" docsLink="https://grafana.com/docs/grafana/latest/datasources/mysql/"
hasRequiredFields={false} hasRequiredFields={true}
/> />
<Divider /> <Divider />
<Collapse collapsible label="User Permission" isOpen={isOpen} onToggle={() => setIsOpen((x) => !x)}>
The database user should only be granted SELECT permissions on the specified database &amp; tables you want to
query. <br />
Grafana does not validate that queries are safe so queries can contain any SQL statement. For example,
statements like <code>USE otherdb;</code> and <code>DROP TABLE user;</code> would be executed. <br />
To protect against this we <strong>Highly</strong> recommend you create a specific MySQL user with restricted
permissions. Check out the docs for more information.
</Collapse>
<Divider />
<ConfigSection title="Connection"> <ConfigSection title="Connection">
<Field label="Host URL" required> <Field label="Host URL" required>
<Input <Input
@ -73,11 +85,7 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<My
onChange={onDSOptionChanged('url')} onChange={onDSOptionChanged('url')}
/> />
</Field> </Field>
</ConfigSection>
<Divider />
<ConfigSection title="Authentication">
<Field label="Database name"> <Field label="Database name">
<Input <Input
width={WIDTH_LONG} width={WIDTH_LONG}
@ -87,8 +95,12 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<My
onChange={onUpdateDatasourceJsonDataOption(props, 'database')} onChange={onUpdateDatasourceJsonDataOption(props, 'database')}
/> />
</Field> </Field>
</ConfigSection>
<Field label="Username"> <Divider />
<ConfigSection title="Authentication">
<Field label="Username" required>
<Input <Input
width={WIDTH_LONG} width={WIDTH_LONG}
value={options.user || ''} value={options.user || ''}
@ -136,13 +148,6 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<My
</Field> </Field>
</ConfigSection> </ConfigSection>
{config.secureSocksDSProxyEnabled && (
<>
<Divider />
<SecureSocksProxySettings options={options} onOptionsChange={onOptionsChange} />
</>
)}
{jsonData.tlsAuth || jsonData.tlsAuthWithCACert ? ( {jsonData.tlsAuth || jsonData.tlsAuthWithCACert ? (
<> <>
<Divider /> <Divider />
@ -153,7 +158,7 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<My
showCACert={jsonData.tlsAuthWithCACert} showCACert={jsonData.tlsAuthWithCACert}
showKeyPair={jsonData.tlsAuth} showKeyPair={jsonData.tlsAuth}
editorProps={props} editorProps={props}
labelWidth={25} labelWidth={WIDTH_LONG}
/> />
) : null} ) : null}
</ConfigSection> </ConfigSection>
@ -162,84 +167,74 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<My
<Divider /> <Divider />
<ConfigSection title="Additional settings"> <ConfigSection title="Additional settings" isCollapsible>
<Field <ConfigSubSection title="MySQL Options">
label={ <Field
<Label> label={
<Stack gap={0.5}> <Label>
<span>Session timezone</span> <Stack gap={0.5}>
<Tooltip <span>Session timezone</span>
content={ <Tooltip
<span> content={
Specify the time zone used in the database session, e.g. <code>Europe/Berlin</code> or <span>
<code>+02:00</code>. This is necessary, if the timezone of the database (or the host of the Specify the time zone used in the database session, e.g. <code>Europe/Berlin</code> or
database) is set to something other than UTC. The value is set in the session with <code>+02:00</code>. This is necessary, if the timezone of the database (or the host of the
<code>SET time_zone=&apos;...&apos;</code>. If you leave this field empty, the timezone is not database) is set to something other than UTC. The value is set in the session with
updated. You can find more information in the MySQL documentation. <code>SET time_zone=&apos;...&apos;</code>. If you leave this field empty, the timezone is not
</span> updated. You can find more information in the MySQL documentation.
} </span>
> }
<Icon name="info-circle" size="sm" /> >
</Tooltip> <Icon name="info-circle" size="sm" />
</Stack> </Tooltip>
</Label> </Stack>
} </Label>
> }
<Input >
width={WIDTH_LONG} <Input
value={jsonData.timezone || ''} width={WIDTH_LONG}
onChange={onUpdateDatasourceJsonDataOption(props, 'timezone')} value={jsonData.timezone || ''}
placeholder="Europe/Berlin or +02:00" onChange={onUpdateDatasourceJsonDataOption(props, 'timezone')}
/> placeholder="Europe/Berlin or +02:00"
</Field> />
</Field>
<Field <Field
label={ label={
<Label> <Label>
<Stack gap={0.5}> <Stack gap={0.5}>
<span>Min time interval</span> <span>Min time interval</span>
<Tooltip <Tooltip
content={ content={
<span> <span>
A lower limit for the auto group by time interval. Recommended to be set to write frequency, for A lower limit for the auto group by time interval. Recommended to be set to write frequency, for
example example
<code>1m</code> if your data is written every minute. <code>1m</code> if your data is written every minute.
</span> </span>
} }
> >
<Icon name="info-circle" size="sm" /> <Icon name="info-circle" size="sm" />
</Tooltip> </Tooltip>
</Stack> </Stack>
</Label> </Label>
} }
description="A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example 1m if your data is written every minute." description="A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example 1m if your data is written every minute."
> >
<Input <Input
width={WIDTH_LONG} width={WIDTH_LONG}
placeholder="1m" placeholder="1m"
value={jsonData.timeInterval || ''} value={jsonData.timeInterval || ''}
onChange={onUpdateDatasourceJsonDataOption(props, 'timeInterval')} onChange={onUpdateDatasourceJsonDataOption(props, 'timeInterval')}
/> />
</Field> </Field>
</ConfigSubSection>
<ConnectionLimits options={options} onOptionsChange={onOptionsChange} />
{config.secureSocksDSProxyEnabled && (
<SecureSocksProxySettings options={options} onOptionsChange={onOptionsChange} />
)}
</ConfigSection> </ConfigSection>
<Divider />
<ConnectionLimits options={options} onOptionsChange={onOptionsChange} />
<Divider />
<Alert title="User Permission" severity="info">
The database user should only be granted SELECT permissions on the specified database &amp; tables you want to
query. Grafana does not validate that queries are safe so queries can contain any SQL statement. For example,
statements like <code>USE otherdb;</code> and <code>DROP TABLE user;</code> would be executed. To protect
against this we <strong>Highly</strong> recommend you create a specific MySQL user with restricted permissions.
Check out the{' '}
<Link rel="noreferrer" target="_blank" href="http://docs.grafana.org/features/datasources/mysql/">
MySQL Data Source Docs
</Link>{' '}
for more information.
</Alert>
</> </>
); );
}; };

View File

@ -8,17 +8,20 @@ import {
updateDatasourcePluginJsonDataOption, updateDatasourcePluginJsonDataOption,
updateDatasourcePluginResetOption, updateDatasourcePluginResetOption,
} from '@grafana/data'; } from '@grafana/data';
import { ConfigSection, ConfigSubSection, DataSourceDescription, Stack } from '@grafana/experimental';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { import {
Alert, Divider,
InlineSwitch,
FieldSet,
InlineField,
InlineFieldRow,
Input, Input,
Select, Select,
SecretInput, SecretInput,
Link, Field,
Tooltip,
Label,
Icon,
Switch,
SecureSocksProxySettings,
Collapse,
} from '@grafana/ui'; } from '@grafana/ui';
import { ConnectionLimits } from 'app/features/plugins/sql/components/configuration/ConnectionLimits'; import { ConnectionLimits } from 'app/features/plugins/sql/components/configuration/ConnectionLimits';
import { TLSSecretsConfig } from 'app/features/plugins/sql/components/configuration/TLSSecretsConfig'; import { TLSSecretsConfig } from 'app/features/plugins/sql/components/configuration/TLSSecretsConfig';
@ -46,9 +49,9 @@ export const postgresVersions: Array<SelectableValue<number>> = [
export const PostgresConfigEditor = (props: DataSourcePluginOptionsEditorProps<PostgresOptions, SecureJsonData>) => { export const PostgresConfigEditor = (props: DataSourcePluginOptionsEditorProps<PostgresOptions, SecureJsonData>) => {
const [versionOptions, setVersionOptions] = useState(postgresVersions); const [versionOptions, setVersionOptions] = useState(postgresVersions);
const [isOpen, setIsOpen] = useState(true);
useAutoDetectFeatures({ props, setVersionOptions }); useAutoDetectFeatures({ props, setVersionOptions });
useMigrateDatabaseFields(props); useMigrateDatabaseFields(props);
const { options, onOptionsChange } = props; const { options, onOptionsChange } = props;
@ -86,229 +89,323 @@ export const PostgresConfigEditor = (props: DataSourcePluginOptionsEditorProps<P
}; };
}; };
const labelWidthSSLDetails = 25; const WIDTH_LONG = 40;
const labelWidthConnection = 20;
const labelWidthShort = 20;
return ( return (
<> <>
<FieldSet label="PostgreSQL Connection" width={400}> <DataSourceDescription
<InlineField labelWidth={labelWidthConnection} label="Host"> dataSourceName="Postgres"
docsLink="https://grafana.com/docs/grafana/latest/datasources/postgres/"
hasRequiredFields={true}
/>
<Divider />
<Collapse collapsible label="User Permissions" isOpen={isOpen} onToggle={() => setIsOpen((x) => !x)}>
The database user should only be granted SELECT permissions on the specified database &amp; tables you want to
query. <br />
Grafana does not validate that queries are safe so queries can contain any SQL statement. For example,
statements like <code>DELETE FROM user;</code> and <code>DROP TABLE user;</code> would be executed. <br />
To protect against this we <strong>Highly</strong> recommend you create a specific PostgreSQL user with
restricted permissions. Check out the docs for more information.
</Collapse>
<Divider />
<ConfigSection title="Connection">
<Field label="Host URL" required>
<Input <Input
width={40} width={WIDTH_LONG}
name="host" name="host"
type="text" type="text"
value={options.url || ''} value={options.url || ''}
placeholder="localhost:5432" placeholder="localhost:5432"
onChange={onDSOptionChanged('url')} onChange={onDSOptionChanged('url')}
></Input> />
</InlineField> </Field>
<InlineField labelWidth={labelWidthConnection} label="Database">
<Field label="Database name" required>
<Input <Input
width={40} width={WIDTH_LONG}
name="database" name="database"
value={jsonData.database || ''} value={jsonData.database || ''}
placeholder="database name" placeholder="Database"
onChange={onUpdateDatasourceJsonDataOption(props, 'database')} onChange={onUpdateDatasourceJsonDataOption(props, 'database')}
></Input> />
</InlineField> </Field>
<InlineFieldRow> </ConfigSection>
<InlineField labelWidth={labelWidthConnection} label="User">
<Input value={options.user || ''} placeholder="user" onChange={onDSOptionChanged('user')}></Input> <Divider />
</InlineField>
<InlineField label="Password"> <ConfigSection title="Authentication">
<SecretInput <Field label="Username" required>
placeholder="Password" <Input
isConfigured={options.secureJsonFields?.password} width={WIDTH_LONG}
onReset={onResetPassword} value={options.user || ''}
onBlur={onUpdateDatasourceSecureJsonDataOption(props, 'password')} placeholder="Username"
></SecretInput> onChange={onDSOptionChanged('user')}
</InlineField> />
</InlineFieldRow> </Field>
<InlineField
labelWidth={labelWidthConnection} <Field label="Password" required>
label="TLS/SSL Mode" <SecretInput
htmlFor="tlsMode" width={WIDTH_LONG}
tooltip="This option determines whether or with what priority a secure TLS/SSL TCP/IP connection will be negotiated with the server." placeholder="Password"
isConfigured={options.secureJsonFields && options.secureJsonFields.password}
onReset={onResetPassword}
onBlur={onUpdateDatasourceSecureJsonDataOption(props, 'password')}
/>
</Field>
<Field
label={
<Label>
<Stack gap={0.5}>
<span>TLS/SSL Mode</span>
<Tooltip
content={
<span>
This option determines whether or with what priority a secure TLS/SSL TCP/IP connection will be
negotiated with the server
</span>
}
>
<Icon name="info-circle" size="sm" />
</Tooltip>
</Stack>
</Label>
}
> >
<Select <Select
options={tlsModes} options={tlsModes}
inputId="tlsMode"
value={jsonData.sslmode || PostgresTLSModes.verifyFull} value={jsonData.sslmode || PostgresTLSModes.verifyFull}
onChange={onJSONDataOptionSelected('sslmode')} onChange={onJSONDataOptionSelected('sslmode')}
></Select> width={WIDTH_LONG}
</InlineField> />
</Field>
{options.jsonData.sslmode !== PostgresTLSModes.disable ? ( {options.jsonData.sslmode !== PostgresTLSModes.disable ? (
<InlineField <Field
labelWidth={labelWidthConnection} label={
label="TLS/SSL Method" <Label>
htmlFor="tlsMethod" <Stack gap={0.5}>
tooltip={ <span>TLS/SSL Method</span>
<span> <Tooltip
This option determines how TLS/SSL certifications are configured. Selecting <i>File system path</i> will content={
allow you to configure certificates by specifying paths to existing certificates on the local file <span>
system where Grafana is running. Be sure that the file is readable by the user executing the Grafana This option determines how TLS/SSL certifications are configured. Selecting{' '}
process. <i>File system path</i> will allow you to configure certificates by specifying paths to existing
<br /> certificates on the local file system where Grafana is running. Be sure that the file is
<br /> readable by the user executing the Grafana process.
Selecting <i>Certificate content</i> will allow you to configure certificates by specifying its content. <br />
The content will be stored encrypted in Grafana&apos;s database. When connecting to the database the <br />
certificates will be written as files to Grafana&apos;s configured data path on the local file system. Selecting <i>Certificate content</i> will allow you to configure certificates by specifying its
</span> content. The content will be stored encrypted in Grafana&apos;s database. When connecting to the
database the certificates will be written as files to Grafana&apos;s configured data path on the
local file system.
</span>
}
>
<Icon name="info-circle" size="sm" />
</Tooltip>
</Stack>
</Label>
} }
> >
<Select <Select
options={tlsMethods} options={tlsMethods}
inputId="tlsMethod"
value={jsonData.tlsConfigurationMethod || PostgresTLSMethods.filePath} value={jsonData.tlsConfigurationMethod || PostgresTLSMethods.filePath}
onChange={onJSONDataOptionSelected('tlsConfigurationMethod')} onChange={onJSONDataOptionSelected('tlsConfigurationMethod')}
></Select> width={WIDTH_LONG}
</InlineField>
) : null}
</FieldSet>
{config.secureSocksDSProxyEnabled && (
<FieldSet label="Secure Socks Proxy">
<InlineField labelWidth={26} label="Enabled" tooltip="Connect to this datasource via the secure socks proxy.">
<InlineSwitch
value={options.jsonData.enableSecureSocksProxy ?? false}
onChange={(event) =>
onOptionsChange({
...options,
jsonData: { ...options.jsonData, enableSecureSocksProxy: event!.currentTarget.checked },
})
}
/> />
</InlineField> </Field>
</FieldSet> ) : null}
)} </ConfigSection>
{jsonData.sslmode !== PostgresTLSModes.disable ? ( {jsonData.sslmode !== PostgresTLSModes.disable ? (
<FieldSet label="TLS/SSL Auth Details"> <>
{jsonData.tlsConfigurationMethod === PostgresTLSMethods.fileContent ? ( <Divider />
<TLSSecretsConfig <ConfigSection title="TLS/SSL Auth Details">
showCACert={ {jsonData.tlsConfigurationMethod === PostgresTLSMethods.fileContent ? (
jsonData.sslmode === PostgresTLSModes.verifyCA || jsonData.sslmode === PostgresTLSModes.verifyFull <TLSSecretsConfig
} showCACert={
editorProps={props} jsonData.sslmode === PostgresTLSModes.verifyCA || jsonData.sslmode === PostgresTLSModes.verifyFull
labelWidth={labelWidthSSLDetails}
></TLSSecretsConfig>
) : (
<>
<InlineField
tooltip={
<span>
If the selected TLS/SSL mode requires a server root certificate, provide the path to the file here.
</span>
} }
labelWidth={labelWidthSSLDetails} editorProps={props}
label="TLS/SSL Root Certificate" labelWidth={WIDTH_LONG}
> />
<Input ) : (
value={jsonData.sslRootCertFile || ''} <>
onChange={onUpdateDatasourceJsonDataOption(props, 'sslRootCertFile')} <Field
placeholder="TLS/SSL root cert file" label={
></Input> <Label>
</InlineField> <Stack gap={0.5}>
<InlineField <span>TLS/SSL Root Certificate</span>
tooltip={ <Tooltip
<span> content={
To authenticate with an TLS/SSL client certificate, provide the path to the file here. Be sure that <span>
the file is readable by the user executing the grafana process. If the selected TLS/SSL mode requires a server root certificate, provide the path to the
</span> file here.
} </span>
labelWidth={labelWidthSSLDetails} }
label="TLS/SSL Client Certificate" >
> <Icon name="info-circle" size="sm" />
<Input </Tooltip>
value={jsonData.sslCertFile || ''} </Stack>
onChange={onUpdateDatasourceJsonDataOption(props, 'sslCertFile')} </Label>
placeholder="TLS/SSL client cert file" }
></Input> >
</InlineField> <Input
<InlineField value={jsonData.sslRootCertFile || ''}
tooltip={ onChange={onUpdateDatasourceJsonDataOption(props, 'sslRootCertFile')}
<span> placeholder="TLS/SSL root cert file"
To authenticate with a client TLS/SSL certificate, provide the path to the corresponding key file width={WIDTH_LONG}
here. Be sure that the file is <i>only</i> readable by the user executing the grafana process. />
</span> </Field>
} <Field
labelWidth={labelWidthSSLDetails} label={
label="TLS/SSL Client Key" <Label>
> <Stack gap={0.5}>
<Input <span>TLS/SSL Client Certificate</span>
value={jsonData.sslKeyFile || ''} <Tooltip
onChange={onUpdateDatasourceJsonDataOption(props, 'sslKeyFile')} content={
placeholder="TLS/SSL client key file" <span>
></Input> To authenticate with an TLS/SSL client certificate, provide the path to the file here. Be
</InlineField> sure that the file is readable by the user executing the grafana process.
</> </span>
)} }
</FieldSet> >
<Icon name="info-circle" size="sm" />
</Tooltip>
</Stack>
</Label>
}
>
<Input
value={jsonData.sslCertFile || ''}
onChange={onUpdateDatasourceJsonDataOption(props, 'sslCertFile')}
placeholder="TLS/SSL client cert file"
width={WIDTH_LONG}
/>
</Field>
<Field
label={
<Label>
<Stack gap={0.5}>
<span>TLS/SSL Client Key</span>
<Tooltip
content={
<span>
To authenticate with a client TLS/SSL certificate, provide the path to the corresponding
key file here. Be sure that the file is <i>only</i> readable by the user executing the
grafana process.
</span>
}
>
<Icon name="info-circle" size="sm" />
</Tooltip>
</Stack>
</Label>
}
>
<Input
value={jsonData.sslKeyFile || ''}
onChange={onUpdateDatasourceJsonDataOption(props, 'sslKeyFile')}
placeholder="TLS/SSL client key file"
width={WIDTH_LONG}
/>
</Field>
</>
)}
</ConfigSection>
</>
) : null} ) : null}
<ConnectionLimits options={options} onOptionsChange={onOptionsChange} /> <Divider />
<FieldSet label="PostgreSQL details"> <ConfigSection title="Additional settings" isCollapsible>
<InlineField <ConfigSubSection title="PostgreSQL Options">
tooltip="This option controls what functions are available in the PostgreSQL query builder" <Field
labelWidth={labelWidthShort} label={
htmlFor="postgresVersion" <Label>
label="Version" <Stack gap={0.5}>
> <span>Version</span>
<Select <Tooltip
value={jsonData.postgresVersion || 903} content={
inputId="postgresVersion" <span>This option controls what functions are available in the PostgreSQL query builder</span>
onChange={onJSONDataOptionSelected('postgresVersion')} }
options={versionOptions} >
></Select> <Icon name="info-circle" size="sm" />
</InlineField> </Tooltip>
<InlineField </Stack>
tooltip={ </Label>
<span> }
TimescaleDB is a time-series database built as a PostgreSQL extension. If enabled, Grafana will use >
<code>time_bucket</code> in the <code>$__timeGroup</code> macro and display TimescaleDB specific aggregate <Select
functions in the query builder. value={jsonData.postgresVersion || 903}
</span> onChange={onJSONDataOptionSelected('postgresVersion')}
} options={versionOptions}
labelWidth={labelWidthShort} width={WIDTH_LONG}
label="TimescaleDB" />
htmlFor="timescaledb" </Field>
> <Field
<InlineSwitch label={
id="timescaledb" <Label>
value={jsonData.timescaledb || false} <Stack gap={0.5}>
onChange={onTimeScaleDBChanged} <span>Min time interval</span>
></InlineSwitch> <Tooltip
</InlineField> content={
<InlineField <span>
tooltip={ A lower limit for the auto group by time interval. Recommended to be set to write frequency, for
<span> example
A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example <code>1m</code> if your data is written every minute.
<code>1m</code> if your data is written every minute. </span>
</span> }
} >
labelWidth={labelWidthShort} <Icon name="info-circle" size="sm" />
label="Min time interval" </Tooltip>
> </Stack>
<Input </Label>
placeholder="1m" }
value={jsonData.timeInterval || ''} >
onChange={onUpdateDatasourceJsonDataOption(props, 'timeInterval')} <Input
></Input> placeholder="1m"
</InlineField> value={jsonData.timeInterval || ''}
</FieldSet> onChange={onUpdateDatasourceJsonDataOption(props, 'timeInterval')}
width={WIDTH_LONG}
/>
</Field>
<Field
label={
<Label>
<Stack gap={0.5}>
<span>TimescaleDB</span>
<Tooltip
content={
<span>
TimescaleDB is a time-series database built as a PostgreSQL extension. If enabled, Grafana will
use
<code>time_bucket</code> in the <code>$__timeGroup</code> macro and display TimescaleDB specific
aggregate functions in the query builder.
</span>
}
>
<Icon name="info-circle" size="sm" />
</Tooltip>
</Stack>
</Label>
}
>
<Switch value={jsonData.timescaledb || false} onChange={onTimeScaleDBChanged} width={WIDTH_LONG} />
</Field>
</ConfigSubSection>
<Alert title="User Permission" severity="info"> <ConnectionLimits options={options} onOptionsChange={onOptionsChange} />
The database user should only be granted SELECT permissions on the specified database &amp; tables you want to
query. Grafana does not validate that queries are safe so queries can contain any SQL statement. For example, {config.secureSocksDSProxyEnabled && (
statements like <code>DELETE FROM user;</code> and <code>DROP TABLE user;</code> would be executed. To protect <SecureSocksProxySettings options={options} onOptionsChange={() => onOptionsChange(options)} />
against this we <strong>Highly</strong> recommend you create a specific PostgreSQL user with restricted )}
permissions. Check out the{' '} </ConfigSection>
<Link rel="noreferrer" target="_blank" href="http://docs.grafana.org/features/datasources/postgres/">
PostgreSQL Data Source Docs
</Link>{' '}
for more information.
</Alert>
</> </>
); );
}; };