Internationalisation: Mark up MSSQL (#105532)
* scaffolding * markup * fix typos + extract translations * update crowdin.yml * uppercase Grafana
This commit is contained in:
@@ -152,12 +152,12 @@ jobs:
|
||||
run: |
|
||||
filesChanged="$(gh pr diff --name-only ${{ steps.crowdin-download.outputs.pull_request_url }})"
|
||||
|
||||
if [[ $(echo "$filesChanged" | grep -cv -e 'public/locales/[a-zA-Z\-]*/grafana.json' -e 'public/app/plugins/datasource/azuremonitor/locales/[a-zA-Z\-]*/grafana-azure-monitor-datasource.json') -ne 0 ]]; then
|
||||
if [[ $(echo "$filesChanged" | grep -cv -e 'public/locales/[a-zA-Z\-]*/grafana.json' -e 'public/app/plugins/datasource/azuremonitor/locales/[a-zA-Z\-]*/grafana-azure-monitor-datasource.json' -e 'public/app/plugins/datasource/mssql/locales/[a-zA-Z\-]*/mssql.json') -ne 0 ]]; then
|
||||
echo "Non-i18n changes detected, not approving"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $(echo "$filesChanged" | grep -c -e "public/locales/en-US" -e "public/app/plugins/datasource/azuremonitor/locales/en-US") -ne 0 ]]; then
|
||||
if [[ $(echo "$filesChanged" | grep -c -e "public/locales/en-US" -e "public/app/plugins/datasource/azuremonitor/locales/en-US" -e "public/app/plugins/datasource/mssql/locales/en-US") -ne 0 ]]; then
|
||||
echo "en-US changes detected, not approving"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -6,6 +6,7 @@ on:
|
||||
paths:
|
||||
- 'public/locales/en-US/grafana.json'
|
||||
- 'public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json'
|
||||
- 'public/app/plugins/datasource/mssql/locales/en-US/mssql.json'
|
||||
branches:
|
||||
- main
|
||||
|
||||
|
||||
@@ -15,4 +15,10 @@ files: [
|
||||
"type": "i18next_json",
|
||||
"dest": "plugins/azuremonitor/en-US/%original_file_name%"
|
||||
},
|
||||
{
|
||||
"source": "public/app/plugins/datasource/mssql/locales/en-US/mssql.json",
|
||||
"translation": "public/app/plugins/datasource/mssql/locales/%locale%/%original_file_name%",
|
||||
"type": "i18next_json",
|
||||
"dest": "plugins/mssql/en-US/%original_file_name%"
|
||||
},
|
||||
]
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ const getEnvConfig = require('./scripts/webpack/env-util');
|
||||
|
||||
const envConfig = getEnvConfig();
|
||||
const enableBettererRules = envConfig.frontend_dev_betterer_eslint_rules;
|
||||
const pluginsToTranslate = ['public/app/plugins/datasource/azuremonitor'];
|
||||
const pluginsToTranslate = ['public/app/plugins/datasource/azuremonitor', 'public/app/plugins/datasource/mssql'];
|
||||
|
||||
/**
|
||||
* @type {Array<import('eslint').Linter.Config>}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
metadata "google.golang.org/grpc/metadata"
|
||||
|
||||
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Trans } from '@grafana/i18n';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
export function CheatSheet() {
|
||||
@@ -8,29 +9,54 @@ export function CheatSheet() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>MSSQL cheat sheet</h2>
|
||||
Time series:
|
||||
<h2>
|
||||
<Trans i18nKey="cheat-sheet.title">MSSQL cheat sheet</Trans>
|
||||
</h2>
|
||||
<Trans i18nKey="cheat-sheet.time-series">Time series:</Trans>
|
||||
<ul className={styles.ulPadding}>
|
||||
<li>
|
||||
return column named time (in UTC), as a unix time stamp or any sql native date data type. You can use the
|
||||
macros below.
|
||||
<Trans i18nKey="cheat-sheet.time-series-tip">
|
||||
return column named time (in UTC), as a unix time stamp or any sql native date data type. You can use the
|
||||
macros below.
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans i18nKey="cheat-sheet.time-series-tip-2">
|
||||
any other columns returned will be the time point values.
|
||||
</Trans>
|
||||
</li>
|
||||
<li>any other columns returned will be the time point values.</li>
|
||||
</ul>
|
||||
Optional:
|
||||
<Trans i18nKey="cheat-sheet.optional">Optional:</Trans>
|
||||
<ul className={styles.ulPadding}>
|
||||
<li>
|
||||
return column named <i>metric</i> to represent the series name.
|
||||
<Trans i18nKey="cheat-sheet.optional-tip" values={{ columnName: 'metric' }}>
|
||||
return column named <i>{'{{columnName}}'}</i> to represent the series name.
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans i18nKey="cheat-sheet.optional-tip-2" values={{ columnName: 'metric' }}>
|
||||
If multiple value columns are returned the {'{{columnName}}'} column is used as prefix.
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans i18nKey="cheat-sheet.optional-tip-3" values={{ columnName: 'metric' }}>
|
||||
If no column named {'{{columnName}}'} is found the column name of the value column is used as series name
|
||||
</Trans>
|
||||
</li>
|
||||
<li>If multiple value columns are returned the metric column is used as prefix.</li>
|
||||
<li>If no column named metric is found the column name of the value column is used as series name</li>
|
||||
</ul>
|
||||
<p>Resultsets of time series queries need to be sorted by time.</p>
|
||||
Table:
|
||||
<p>
|
||||
<Trans i18nKey="cheat-sheet.resultsets-time-sorted">
|
||||
Resultsets of time series queries need to be sorted by time.
|
||||
</Trans>
|
||||
</p>
|
||||
<Trans i18nKey="cheat-sheet.table">Table:</Trans>
|
||||
<ul className={styles.ulPadding}>
|
||||
<li>return any set of columns</li>
|
||||
<li>
|
||||
<Trans i18nKey="cheat-sheet.table-tip">return any set of columns</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
Macros:
|
||||
<Trans i18nKey="cheat-sheet.macros">Macros:</Trans>
|
||||
{/* eslint-disable @grafana/no-untranslated-strings */}
|
||||
<ul className={styles.ulPadding}>
|
||||
<li>$__time(column) -> column AS time</li>
|
||||
<li>$__timeEpoch(column) -> DATEDIFF(second, '1970-01-01', column) AS time</li>
|
||||
@@ -44,9 +70,12 @@ export function CheatSheet() {
|
||||
</li>
|
||||
<li>
|
||||
$__timeGroup(column, '5m'[, fillvalue]) -> CAST(ROUND(DATEDIFF(second, '1970-01-01',
|
||||
column)/300.0, 0) as bigint)*300 by setting fillvalue grafana will fill in missing values according to the
|
||||
interval fillvalue can be either a literal value, NULL or previous; previous will fill in the previous seen
|
||||
value or NULL if none has been seen yet
|
||||
column)/300.0, 0) as bigint)*300{' '}
|
||||
<Trans i18nKey="cheat-sheet.fillvalue" values={{ null: 'NULL', previous: 'previous' }}>
|
||||
by setting fillvalue Grafana will fill in missing values according to the interval. fillvalue can be either
|
||||
a literal value, {'{{null}}'} or {'{{previous}}'}; {'{{previous}}'} will fill in the previous seen value or{' '}
|
||||
{'{{null}}'} if none has been seen yet
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
$__timeGroupAlias(column, '5m'[, fillvalue]) -> CAST(ROUND(DATEDIFF(second,
|
||||
@@ -55,7 +84,13 @@ export function CheatSheet() {
|
||||
<li>$__unixEpochGroup(column,'5m') -> FLOOR(column/300)*300</li>
|
||||
<li>$__unixEpochGroupAlias(column,'5m') -> FLOOR(column/300)*300 AS [time]</li>
|
||||
</ul>
|
||||
<p>Example of group by and order by with $__timeGroup:</p>
|
||||
{/* eslint-enable @grafana/no-untranslated-strings */}
|
||||
<p>
|
||||
<Trans i18nKey="cheat-sheet.example-time-group" values={{ timeGroupMacro: '$__timeGroup' }}>
|
||||
Example of group by and order by with {'{{timeGroupMacro}}'}:
|
||||
</Trans>
|
||||
</p>
|
||||
{/* eslint-disable @grafana/no-untranslated-strings */}
|
||||
<pre>
|
||||
<code>
|
||||
SELECT $__timeGroup(date_time_col, '1h') AS time, sum(value) as value <br />
|
||||
@@ -67,7 +102,11 @@ export function CheatSheet() {
|
||||
<br />
|
||||
</code>
|
||||
</pre>
|
||||
Or build your own conditionals using these macros which just return the values:
|
||||
{/* eslint-enable @grafana/no-untranslated-strings */}
|
||||
<Trans i18nKey="cheat-sheet.condtional-macros">
|
||||
Or build your own conditionals using these macros which just return the values:
|
||||
</Trans>
|
||||
{/* eslint-disable @grafana/no-untranslated-strings */}
|
||||
<ul className={styles.ulPadding}>
|
||||
<li>$__timeFrom() -> '2017-04-21T05:01:17Z'</li>
|
||||
<li>$__timeTo() -> '2017-04-21T05:01:17Z'</li>
|
||||
@@ -76,6 +115,7 @@ export function CheatSheet() {
|
||||
<li>$__unixEpochNanoFrom() -> 1494410783152415214</li>
|
||||
<li>$__unixEpochNanoTo() -> 1494497183142514872</li>
|
||||
</ul>
|
||||
{/* eslint-enable @grafana/no-untranslated-strings */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ChangeEvent } from 'react';
|
||||
|
||||
import { AzureCredentials, AzureAuthType } from '@grafana/azure-sdk';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Trans, useTranslate } from '@grafana/i18n';
|
||||
import { Button, Field, Select, Input } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
@@ -22,6 +23,7 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
onCredentialsChange,
|
||||
disabled,
|
||||
} = props;
|
||||
const { t } = useTranslate();
|
||||
|
||||
const onAuthTypeChange = (selected: SelectableValue<AzureAuthType>) => {
|
||||
if (onCredentialsChange) {
|
||||
@@ -116,27 +118,30 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
const authTypeOptions: Array<SelectableValue<AzureAuthType>> = [
|
||||
{
|
||||
value: 'clientsecret',
|
||||
label: 'App Registration',
|
||||
label: t('azureauth.azure-credentials-form.auth-options-app-registration', 'App Registration'),
|
||||
},
|
||||
];
|
||||
if (managedIdentityEnabled) {
|
||||
authTypeOptions.push({
|
||||
value: 'msi',
|
||||
label: 'Managed Identity',
|
||||
label: t('azureauth.azure-credentials-form.auth-options-managed-identity', 'Managed Identity'),
|
||||
});
|
||||
}
|
||||
if (azureEntraPasswordCredentialsEnabled) {
|
||||
authTypeOptions.push({
|
||||
value: 'ad-password',
|
||||
label: 'Azure Entra Password',
|
||||
label: t('azureauth.azure-credentials-form.auth-options-azure-entra', 'Azure Entra Password'),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Field
|
||||
label="Authentication"
|
||||
description="Choose the type of authentication to Azure services"
|
||||
label={t('azureauth.azure-credentials-form.label-authentication', 'Authentication')}
|
||||
description={t(
|
||||
'azureauth.azure-credentials-form.description-authentication',
|
||||
'Choose the type of authentication to Azure services'
|
||||
)}
|
||||
htmlFor="authentication-type"
|
||||
>
|
||||
<Select
|
||||
@@ -150,77 +155,94 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
{credentials.authType === 'clientsecret' && (
|
||||
<>
|
||||
{azureCloudOptions && (
|
||||
<Field label="Azure Cloud" htmlFor="azure-cloud-type" disabled={disabled}>
|
||||
<Field
|
||||
label={t('azureauth.azure-credentials-form.label-azure-cloud', 'Azure Cloud')}
|
||||
htmlFor="azure-cloud-type"
|
||||
disabled={disabled}
|
||||
>
|
||||
<Select
|
||||
value={azureCloudOptions.find((opt) => opt.value === credentials.azureCloud)}
|
||||
options={azureCloudOptions}
|
||||
onChange={onAzureCloudChange}
|
||||
isDisabled={disabled}
|
||||
inputId="azure-cloud-type"
|
||||
aria-label="Azure Cloud"
|
||||
aria-label={t('azureauth.azure-credentials-form.aria-label-azure-cloud', 'Azure Cloud')}
|
||||
width={20}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
<Field
|
||||
label="Directory (tenant) ID"
|
||||
label={t('azureauth.azure-credentials-form.label-tenant-id', 'Directory (tenant) ID')}
|
||||
required
|
||||
htmlFor="tenant-id"
|
||||
invalid={!credentials.tenantId}
|
||||
error={'Tenant ID is required'}
|
||||
error={t('azureauth.azure-credentials-form.required-tenant-id', 'Tenant ID is required')}
|
||||
>
|
||||
<Input
|
||||
width={45}
|
||||
// eslint-disable-next-line @grafana/no-untranslated-strings
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value={credentials.tenantId || ''}
|
||||
onChange={onTenantIdChange}
|
||||
disabled={disabled}
|
||||
aria-label="Tenant ID"
|
||||
aria-label={t('azureauth.azure-credentials-form.aria-label-tenant-id', 'Tenant ID')}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Application (client) ID"
|
||||
label={t('azureauth.azure-credentials-form.label-client-id', 'Application (client) ID')}
|
||||
required
|
||||
htmlFor="client-id"
|
||||
invalid={!credentials.clientId}
|
||||
error={'Client ID is required'}
|
||||
error={t('azureauth.azure-credentials-form.required-client-id', 'Client ID is required')}
|
||||
>
|
||||
<Input
|
||||
width={45}
|
||||
// eslint-disable-next-line @grafana/no-untranslated-strings
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value={credentials.clientId || ''}
|
||||
onChange={onClientIdChange}
|
||||
disabled={disabled}
|
||||
aria-label="Client ID"
|
||||
aria-label={t('azureauth.azure-credentials-form.arialabel-client-id', 'Client ID')}
|
||||
/>
|
||||
</Field>
|
||||
{!disabled &&
|
||||
(typeof credentials.clientSecret === 'symbol' ? (
|
||||
<Field label="Client Secret" htmlFor="client-secret" required>
|
||||
<Field
|
||||
label={t('azureauth.azure-credentials-form.label-configured-client-secret', 'Client Secret')}
|
||||
htmlFor="client-secret"
|
||||
required
|
||||
>
|
||||
<div className="width-30" style={{ display: 'flex', gap: '4px' }}>
|
||||
<Input
|
||||
aria-label="Client Secret"
|
||||
placeholder="configured"
|
||||
aria-label={t(
|
||||
'azureauth.azure-credentials-form.aria-label-configured-client-secret',
|
||||
'Client Secret'
|
||||
)}
|
||||
placeholder={t(
|
||||
'azureauth.azure-credentials-form.placeholder-configured-client-secret',
|
||||
'configured'
|
||||
)}
|
||||
disabled={true}
|
||||
data-testid={'client-secret'}
|
||||
width={45}
|
||||
/>
|
||||
<Button variant="secondary" type="button" onClick={onClientSecretReset} disabled={disabled}>
|
||||
Reset
|
||||
<Trans i18nKey="azureauth.azure-credentials-form.client-secret-reset">Reset</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</Field>
|
||||
) : (
|
||||
<Field
|
||||
label="Client Secret"
|
||||
label={t('azureauth.azure-credentials-form.label-client-secret', 'Client Secret')}
|
||||
required
|
||||
htmlFor="client-secret"
|
||||
invalid={!credentials.clientSecret}
|
||||
error={'Client secret is required'}
|
||||
error={t('azureauth.azure-credentials-form.required-client-secret', 'Client secret is required')}
|
||||
>
|
||||
<Input
|
||||
width={45}
|
||||
aria-label="Client Secret"
|
||||
aria-label={t('azureauth.azure-credentials-form.aria-label-client-secret', 'Client Secret')}
|
||||
// eslint-disable-next-line @grafana/no-untranslated-strings
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value={credentials.clientSecret || ''}
|
||||
onChange={onClientSecretChange}
|
||||
@@ -233,57 +255,73 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
)}
|
||||
{credentials.authType === 'ad-password' && azureEntraPasswordCredentialsEnabled && (
|
||||
<>
|
||||
<Field label="User Id" required htmlFor="user-id" invalid={!credentials.userId} error={'User ID is required'}>
|
||||
<Field
|
||||
label={t('azureauth.azure-credentials-form.label-user-id', 'User Id')}
|
||||
required
|
||||
htmlFor="user-id"
|
||||
invalid={!credentials.userId}
|
||||
error={'User ID is required'}
|
||||
>
|
||||
<Input
|
||||
width={45}
|
||||
value={credentials.userId || ''}
|
||||
onChange={onUserIdChange}
|
||||
disabled={disabled}
|
||||
aria-label="User ID"
|
||||
aria-label={t('azureauth.azure-credentials-form.aria-label-user-id', 'User ID')}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Application Client ID"
|
||||
label={t('azureauth.azure-credentials-form.label-application-client-id', 'Application Client ID')}
|
||||
required
|
||||
htmlFor="application-client-id"
|
||||
invalid={!credentials.clientId}
|
||||
error={'Application Client ID is required'}
|
||||
error={t(
|
||||
'azureauth.azure-credentials-form.required-application-client-id',
|
||||
'Application Client ID is required'
|
||||
)}
|
||||
>
|
||||
<Input
|
||||
width={45}
|
||||
value={credentials.clientId || ''}
|
||||
onChange={onClientIdChange}
|
||||
disabled={disabled}
|
||||
aria-label="Application Client ID"
|
||||
aria-label={t(
|
||||
'azureauth.azure-credentials-form.aria-label-application-client-id',
|
||||
'Application Client ID'
|
||||
)}
|
||||
/>
|
||||
</Field>
|
||||
{!disabled &&
|
||||
(typeof credentials.password === 'symbol' ? (
|
||||
<Field label="Password" htmlFor="password" required>
|
||||
<Field
|
||||
label={t('azureauth.azure-credentials-form.label-password-configured', 'Password')}
|
||||
htmlFor="password"
|
||||
required
|
||||
>
|
||||
<div className="width-30" style={{ display: 'flex', gap: '4px' }}>
|
||||
<Input
|
||||
aria-label="Password"
|
||||
placeholder="configured"
|
||||
aria-label={t('azureauth.azure-credentials-form.aria-label-password-configured', 'Password')}
|
||||
placeholder={t('azureauth.azure-credentials-form.placeholder-password-configured', 'configured')}
|
||||
disabled={true}
|
||||
data-testid={'password'}
|
||||
width={45}
|
||||
/>
|
||||
<Button variant="secondary" type="button" onClick={onPasswordReset} disabled={disabled}>
|
||||
Reset
|
||||
<Trans i18nKey="azureauth.azure-credentials-form.password-reset">Reset</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</Field>
|
||||
) : (
|
||||
<Field
|
||||
label="Password"
|
||||
label={t('azureauth.azure-credentials-form.label-password', 'Password')}
|
||||
required
|
||||
htmlFor="password"
|
||||
invalid={!credentials.password}
|
||||
error={'Password is required'}
|
||||
error={t('azureauth.azure-credentials-form.required-password', 'Password is required')}
|
||||
>
|
||||
<Input
|
||||
width={45}
|
||||
aria-label="Password"
|
||||
aria-label={t('azureauth.azure-credentials-form.aria-label-password', 'Password')}
|
||||
value={credentials.password || ''}
|
||||
onChange={onPasswordChange}
|
||||
id="password"
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
updateDatasourcePluginJsonDataOption,
|
||||
updateDatasourcePluginResetOption,
|
||||
} from '@grafana/data';
|
||||
import { Trans, useTranslate } from '@grafana/i18n';
|
||||
import { ConfigSection, ConfigSubSection, DataSourceDescription } from '@grafana/plugin-ui';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { ConnectionLimits, useMigrateDatabaseFields } from '@grafana/sql';
|
||||
@@ -18,7 +19,7 @@ import {
|
||||
Alert,
|
||||
FieldSet,
|
||||
Input,
|
||||
Link,
|
||||
TextLink,
|
||||
SecretInput,
|
||||
Select,
|
||||
useStyles2,
|
||||
@@ -45,6 +46,7 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
|
||||
useMigrateDatabaseFields(props);
|
||||
|
||||
const { options: dsSettings, onOptionsChange } = props;
|
||||
const { t } = useTranslate();
|
||||
const styles = useStyles2(getStyles);
|
||||
const jsonData = dsSettings.jsonData;
|
||||
const azureAuthIsSupported = config.azureAuthEnabled;
|
||||
@@ -131,62 +133,98 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
|
||||
docsLink="https://grafana.com/docs/grafana/latest/datasources/mssql/"
|
||||
hasRequiredFields
|
||||
/>
|
||||
<Alert title="User Permission" severity="info">
|
||||
The database user should only be granted SELECT permissions on the specified database and 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 <em>highly</em> recommend you create a specific MS SQL user with restricted permissions. Check
|
||||
out the{' '}
|
||||
<Link rel="noreferrer" target="_blank" href="http://docs.grafana.org/features/datasources/mssql/">
|
||||
Microsoft SQL Server Data Source Docs
|
||||
</Link>{' '}
|
||||
for more information.
|
||||
<Alert title={t('configuration.configuration-editor.title-user-permission', 'User Permission')} severity="info">
|
||||
<Trans
|
||||
i18nKey="configuration.configuration-editor.body-user-permission"
|
||||
values={{ permissionType: 'SELECT', example1: 'USE otherdb;', example2: 'DROP TABLE user;' }}
|
||||
>
|
||||
The database user should only be granted {'{{permissionType}}'} permissions on the specified database and
|
||||
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>{'{{example1}}'}</code> and <code>{'{{example2}}'}</code> would
|
||||
be executed. To protect against this we <em>highly</em> recommend you create a specific MS SQL user with
|
||||
restricted permissions. Check out the{' '}
|
||||
<TextLink external href="http://docs.grafana.org/features/datasources/mssql/">
|
||||
Microsoft SQL Server Data Source Docs
|
||||
</TextLink>{' '}
|
||||
for more information.
|
||||
</Trans>
|
||||
</Alert>
|
||||
<Divider />
|
||||
<ConfigSection title="Connection">
|
||||
<Field label="Host" required invalid={!dsSettings.url} error={'Host is required'}>
|
||||
<ConfigSection title={t('configuration.configuration-editor.title-connection', 'Connection')}>
|
||||
<Field
|
||||
label={t('configuration.configuration-editor.title-host', 'Host')}
|
||||
required
|
||||
invalid={!dsSettings.url}
|
||||
error={t('configuration.configuration-editor.required-host', 'Host is required')}
|
||||
>
|
||||
<Input
|
||||
width={LONG_WIDTH}
|
||||
name="host"
|
||||
type="text"
|
||||
value={dsSettings.url || ''}
|
||||
// eslint-disable-next-line @grafana/no-untranslated-strings
|
||||
placeholder="localhost:1433"
|
||||
onChange={onDSOptionChanged('url')}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Database" required invalid={!jsonData.database} error={'Database is required'}>
|
||||
<Field
|
||||
label={t('configuration.configuration-editor.title-database', 'Database')}
|
||||
required
|
||||
invalid={!jsonData.database}
|
||||
error={t('configuration.configuration-editor.required-database', 'Database is required')}
|
||||
>
|
||||
<Input
|
||||
width={LONG_WIDTH}
|
||||
name="database"
|
||||
value={jsonData.database || ''}
|
||||
placeholder="database name"
|
||||
placeholder={t('configuration.configuration-editor.placeholder-database', 'database name')}
|
||||
onChange={onUpdateDatasourceJsonDataOption(props, 'database')}
|
||||
/>
|
||||
</Field>
|
||||
</ConfigSection>
|
||||
|
||||
<ConfigSection title="TLS/SSL Auth">
|
||||
<ConfigSection title={t('configuration.configuration-editor.title-tls-auth', 'TLS/SSL Auth')}>
|
||||
<Field
|
||||
htmlFor="encrypt"
|
||||
description={
|
||||
<>
|
||||
Determines whether or to which extent a secure SSL TCP/IP connection will be negotiated with the server.
|
||||
<Trans i18nKey="configuration.configuration-editor.description-encrypt">
|
||||
Determines whether or to which extent a secure SSL TCP/IP connection will be negotiated with the server.
|
||||
</Trans>
|
||||
<ul className={styles.ulPadding}>
|
||||
<li>
|
||||
<i>disable</i> - Data sent between client and server is not encrypted.
|
||||
<Trans
|
||||
i18nKey="configuration.configuration-editor.description-encrypt-disable"
|
||||
values={{ encryptionValue: 'disable' }}
|
||||
>
|
||||
<i>{'{{encryptionValue}}'}</i> - Data sent between client and server is not encrypted.
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<i>false</i> - Data sent between client and server is not encrypted beyond the login packet. (default)
|
||||
<Trans
|
||||
i18nKey="configuration.configuration-editor.description-encrypt-false"
|
||||
values={{ encryptionValue: 'false' }}
|
||||
>
|
||||
<i>{'{{encryptionValue}}'}</i> - Data sent between client and server is not encrypted beyond the
|
||||
login packet. (default)
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<i>true</i> - Data sent between client and server is encrypted.
|
||||
<Trans
|
||||
i18nKey="configuration.configuration-editor.description-encrypt-true"
|
||||
values={{ encryptionValue: 'true' }}
|
||||
>
|
||||
<i>{'{{encryptionValue}}'}</i> - Data sent between client and server is encrypted.
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
If you're using an older version of Microsoft SQL Server like 2008 and 2008R2 you may need to disable
|
||||
encryption to be able to connect.
|
||||
<Trans i18nKey="configuration.configuration-editor.description-encrypt-older-version">
|
||||
If you're using an older version of Microsoft SQL Server like 2008 and 2008R2 you may need to
|
||||
disable encryption to be able to connect.
|
||||
</Trans>
|
||||
</>
|
||||
}
|
||||
label="Encrypt"
|
||||
label={t('configuration.configuration-editor.label-encrypt', 'Encrypt')}
|
||||
>
|
||||
<Select
|
||||
options={encryptOptions}
|
||||
@@ -199,7 +237,10 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
|
||||
|
||||
{jsonData.encrypt === MSSQLEncryptOptions.true ? (
|
||||
<>
|
||||
<Field htmlFor="skipTlsVerify" label="Skip TLS Verify">
|
||||
<Field
|
||||
htmlFor="skipTlsVerify"
|
||||
label={t('configuration.configuration-editor.label-skip-tls', 'Skip TLS Verify')}
|
||||
>
|
||||
<Switch id="skipTlsVerify" onChange={onSkipTLSVerifyChanged} value={jsonData.tlsSkipVerify || false} />
|
||||
</Field>
|
||||
{jsonData.tlsSkipVerify ? null : (
|
||||
@@ -207,22 +248,32 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
|
||||
<Field
|
||||
description={
|
||||
<span>
|
||||
Path to file containing the public key certificate of the CA that signed the SQL Server
|
||||
certificate. Needed when the server certificate is self signed.
|
||||
<Trans i18nKey="configuration.configuration-editor.description-tls-cert">
|
||||
Path to file containing the public key certificate of the CA that signed the SQL Server
|
||||
certificate. Needed when the server certificate is self signed.
|
||||
</Trans>
|
||||
</span>
|
||||
}
|
||||
label="TLS/SSL Root Certificate"
|
||||
label={t('configuration.configuration-editor.label-tls-cert', 'TLS/SSL Root Certificate')}
|
||||
>
|
||||
<Input
|
||||
value={jsonData.sslRootCertFile || ''}
|
||||
onChange={onUpdateDatasourceJsonDataOption(props, 'sslRootCertFile')}
|
||||
placeholder="TLS/SSL root certificate file path"
|
||||
placeholder={t(
|
||||
'configuration.configuration-editor.placeholder-tls-cert',
|
||||
'TLS/SSL root certificate file path'
|
||||
)}
|
||||
width={LONG_WIDTH}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Hostname in server certificate">
|
||||
<Field
|
||||
label={t('configuration.configuration-editor.label-common-name', 'Hostname in server certificate')}
|
||||
>
|
||||
<Input
|
||||
placeholder="Common Name (CN) in server certificate"
|
||||
placeholder={t(
|
||||
'configuration.configuration-editor.placeholder-common-name',
|
||||
'Common Name (CN) in server certificate'
|
||||
)}
|
||||
value={jsonData.serverName || ''}
|
||||
onChange={onUpdateDatasourceJsonDataOption(props, 'serverName')}
|
||||
width={LONG_WIDTH}
|
||||
@@ -234,40 +285,54 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
|
||||
) : null}
|
||||
</ConfigSection>
|
||||
|
||||
<ConfigSection title="Authentication">
|
||||
<ConfigSection title={t('configuration.configuration-editor.title-authentication', 'Authentication')}>
|
||||
<Field
|
||||
label="Authentication Type"
|
||||
label={t('configuration.configuration-editor.label-auth-type', 'Authentication Type')}
|
||||
htmlFor="authenticationType"
|
||||
description={
|
||||
<ul className={styles.ulPadding}>
|
||||
<li>
|
||||
<i>SQL Server Authentication</i> This is the default mechanism to connect to MS SQL Server. Enter the
|
||||
SQL Server Authentication login or the Windows Authentication login in the DOMAIN\User format.
|
||||
<Trans i18nKey="configuration.configuration-editor.description-auth-type-sql-server">
|
||||
<i>SQL Server Authentication</i> This is the default mechanism to connect to MS SQL Server. Enter the
|
||||
SQL Server Authentication login or the Windows Authentication login in the DOMAIN\User format.
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<i>Windows Authentication</i> Windows Integrated Security - single sign on for users who are already
|
||||
logged onto Windows and have enabled this option for MS SQL Server.
|
||||
<Trans i18nKey="configuration.configuration-editor.description-auth-type-windows-auth">
|
||||
<i>Windows Authentication</i> Windows Integrated Security - single sign on for users who are already
|
||||
logged onto Windows and have enabled this option for MS SQL Server.
|
||||
</Trans>
|
||||
</li>
|
||||
{azureAuthIsSupported && (
|
||||
<li>
|
||||
<i>Azure Authentication</i> Securely authenticate and access Azure resources and applications using
|
||||
Azure AD credentials - Managed Service Identity and Client Secret Credentials are supported.
|
||||
<Trans i18nKey="configuration.configuration-editor.description-auth-type-azure-auth">
|
||||
<i>Azure Authentication</i> Securely authenticate and access Azure resources and applications using
|
||||
Azure AD credentials - Managed Service Identity and Client Secret Credentials are supported.
|
||||
</Trans>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<i>Windows AD: Username + password</i> Windows Active Directory - Sign on for domain user via
|
||||
username/password.
|
||||
<Trans i18nKey="configuration.configuration-editor.description-auth-type-username-password">
|
||||
<i>Windows AD: Username + password</i> Windows Active Directory - Sign on for domain user via
|
||||
username/password.
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<i>Windows AD: Keytab</i> Windows Active Directory - Sign on for domain user via keytab file.
|
||||
<Trans i18nKey="configuration.configuration-editor.description-auth-type-keytab">
|
||||
<i>Windows AD: Keytab</i> Windows Active Directory - Sign on for domain user via keytab file.
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<i>Windows AD: Credential cache</i> Windows Active Directory - Sign on for domain user via credential
|
||||
cache.
|
||||
<Trans i18nKey="configuration.configuration-editor.description-auth-type-credential-cache">
|
||||
<i>Windows AD: Credential cache</i> Windows Active Directory - Sign on for domain user via credential
|
||||
cache.
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<i>Windows AD: Credential cache file</i> Windows Active Directory - Sign on for domain user via
|
||||
credential cache file.
|
||||
<Trans i18nKey="configuration.configuration-editor.description-auth-type-credential-cache-file">
|
||||
<i>Windows AD: Credential cache file</i> Windows Active Directory - Sign on for domain user via
|
||||
credential cache file.
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
@@ -292,30 +357,33 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
|
||||
!jsonData.authenticationType) && (
|
||||
<>
|
||||
<Field
|
||||
label="Username"
|
||||
label={t('configuration.configuration-editor.label-username', 'Username')}
|
||||
required
|
||||
invalid={!dsSettings.user}
|
||||
error={'Username is required'}
|
||||
error={t('configuration.configuration-editor.required-username', 'Username is required')}
|
||||
description={jsonData.authenticationType === MSSQLAuthenticationType.kerberosRaw ? UsernameMessage : ''}
|
||||
>
|
||||
<Input
|
||||
value={dsSettings.user || ''}
|
||||
placeholder={
|
||||
jsonData.authenticationType === MSSQLAuthenticationType.kerberosRaw ? 'name@EXAMPLE.COM' : 'user'
|
||||
jsonData.authenticationType === MSSQLAuthenticationType.kerberosRaw
|
||||
? // eslint-disable-next-line @grafana/no-untranslated-strings
|
||||
'name@EXAMPLE.COM'
|
||||
: t('configuration.configuration-editor.placeholder-user', 'user')
|
||||
}
|
||||
onChange={onDSOptionChanged('user')}
|
||||
width={LONG_WIDTH}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Password"
|
||||
label={t('configuration.configuration-editor.label-password', 'Password')}
|
||||
required
|
||||
invalid={!dsSettings.secureJsonFields.password && !dsSettings.secureJsonData?.password}
|
||||
error={'Password is required'}
|
||||
error={t('configuration.configuration-editor.required-password', 'Password is required')}
|
||||
>
|
||||
<SecretInput
|
||||
width={LONG_WIDTH}
|
||||
placeholder="Password"
|
||||
placeholder={t('configuration.configuration-editor.placeholder-password', 'Password')}
|
||||
isConfigured={dsSettings.secureJsonFields && dsSettings.secureJsonFields.password}
|
||||
onReset={onResetPassword}
|
||||
onChange={onUpdateDatasourceSecureJsonDataOption(props, 'password')}
|
||||
@@ -326,7 +394,9 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
|
||||
)}
|
||||
|
||||
{azureAuthIsSupported && jsonData.authenticationType === MSSQLAuthenticationType.azureAuth && (
|
||||
<FieldSet label="Azure Authentication Settings">
|
||||
<FieldSet
|
||||
label={t('configuration.configuration-editor.label-auth-settings', 'Azure Authentication Settings')}
|
||||
>
|
||||
<azureAuthSettings.azureAuthSettingsUI dataSourceConfig={dsSettings} onChange={onOptionsChange} />
|
||||
</FieldSet>
|
||||
)}
|
||||
@@ -334,25 +404,37 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
|
||||
|
||||
<Divider />
|
||||
<ConfigSection
|
||||
title="Additional settings"
|
||||
description="Additional settings are optional settings that can be configured for more control over your data source. This includes connection limits, connection timeout, group-by time interval, and Secure Socks Proxy."
|
||||
title={t('configuration.configuration-editor.title-additional-settings', 'Additional settings')}
|
||||
description={t(
|
||||
'configuration.configuration-editor.description-additional-settings',
|
||||
'Additional settings are optional settings that can be configured for more control over your data source. This includes connection limits, connection timeout, group-by time interval, and Secure Socks Proxy.'
|
||||
)}
|
||||
isCollapsible={true}
|
||||
isInitiallyOpen={true}
|
||||
>
|
||||
<ConnectionLimits options={dsSettings} onOptionsChange={onOptionsChange} />
|
||||
|
||||
<ConfigSubSection title="Connection details">
|
||||
<ConfigSubSection
|
||||
title={t('configuration.configuration-editor.title-connection-details', 'Connection details')}
|
||||
>
|
||||
<Field
|
||||
description={
|
||||
<span>
|
||||
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.
|
||||
<Trans
|
||||
i18nKey="configuration.configuration-editor.description-min-interval"
|
||||
values={{ exampleInterval: '1m' }}
|
||||
>
|
||||
A lower limit for the auto group by time interval. Recommended to be set to write frequency, for
|
||||
example
|
||||
<code>{'{{exampleInterval}}'}</code> if your data is written every minute.
|
||||
</Trans>
|
||||
</span>
|
||||
}
|
||||
label="Min time interval"
|
||||
label={t('configuration.configuration-editor.label-min-interval', 'Min time interval')}
|
||||
>
|
||||
<Input
|
||||
width={LONG_WIDTH}
|
||||
// eslint-disable-next-line @grafana/no-untranslated-strings
|
||||
placeholder="1m"
|
||||
value={jsonData.timeInterval || ''}
|
||||
onChange={onUpdateDatasourceJsonDataOption(props, 'timeInterval')}
|
||||
@@ -361,11 +443,16 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
|
||||
<Field
|
||||
description={
|
||||
<span>
|
||||
The number of seconds to wait before canceling the request when connecting to the database. The default
|
||||
is <code>0</code>, meaning no timeout.
|
||||
<Trans
|
||||
i18nKey="configuration.configuration-editor.description-connection-timeout"
|
||||
values={{ defaultTimeout: '0' }}
|
||||
>
|
||||
The number of seconds to wait before canceling the request when connecting to the database. The
|
||||
default is <code>{'{{defaultTimeout}}'}</code>, meaning no timeout.
|
||||
</Trans>
|
||||
</span>
|
||||
}
|
||||
label="Connection timeout"
|
||||
label={t('configuration.configuration-editor.label-connection-timeout', 'Connection timeout')}
|
||||
>
|
||||
<NumberInput
|
||||
width={LONG_WIDTH}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { SyntheticEvent } from 'react';
|
||||
|
||||
import { DataSourcePluginOptionsEditorProps, updateDatasourcePluginJsonDataOption } from '@grafana/data';
|
||||
import { Trans, useTranslate } from '@grafana/i18n';
|
||||
import { ConfigSubSection } from '@grafana/plugin-ui';
|
||||
import { FieldSet, Input, Field } from '@grafana/ui';
|
||||
import { FieldSet, Input, Field, TextLink } from '@grafana/ui';
|
||||
|
||||
import { MSSQLAuthenticationType, MssqlOptions } from '../types';
|
||||
|
||||
export const UsernameMessage = (
|
||||
<span>
|
||||
Use the format <code>user@EXAMPLE.COM</code>. Realm is derived from the username.
|
||||
<Trans i18nKey="configuration.kerberos.username-message">
|
||||
Use the format <code>user@EXAMPLE.COM</code>. Realm is derived from the username.
|
||||
</Trans>
|
||||
</span>
|
||||
);
|
||||
|
||||
export const KerberosConfig = (props: DataSourcePluginOptionsEditorProps<MssqlOptions>) => {
|
||||
const { options: settings, onOptionsChange } = props;
|
||||
const { t } = useTranslate();
|
||||
const jsonData = settings.jsonData;
|
||||
const LONG_WIDTH = 40;
|
||||
|
||||
@@ -36,23 +40,29 @@ export const KerberosConfig = (props: DataSourcePluginOptionsEditorProps<MssqlOp
|
||||
return (
|
||||
<>
|
||||
{jsonData.authenticationType === MSSQLAuthenticationType.kerberosKeytab && (
|
||||
<FieldSet label="Windows AD: Keytab">
|
||||
<FieldSet label={t('configuration.kerberos-config.label-keytab', 'Windows AD: Keytab')}>
|
||||
<Field
|
||||
label="Username"
|
||||
label={t('configuration.kerberos-config.label-username', 'Username')}
|
||||
required
|
||||
invalid={!settings.user}
|
||||
error={'Username is required'}
|
||||
error={t('configuration.kerberos-config.required-username', 'Username is required')}
|
||||
description={UsernameMessage}
|
||||
>
|
||||
<Input
|
||||
value={settings.user || ''}
|
||||
placeholder="name@EXAMPLE.COM"
|
||||
placeholder={t('configuration.kerberos-config.placeholder-username', 'name@EXAMPLE.COM')}
|
||||
onChange={(e) => onOptionsChange({ ...settings, ...{ ['user']: e.currentTarget.value } })}
|
||||
width={LONG_WIDTH}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Keytab file path" required invalid={!keytabFilePath} error={'Keytab file path is required'}>
|
||||
<Field
|
||||
label={t('configuration.kerberos-config.label-keytab-file-path', 'Keytab file path')}
|
||||
required
|
||||
invalid={!keytabFilePath}
|
||||
error={'Keytab file path is required'}
|
||||
>
|
||||
<Input
|
||||
// eslint-disable-next-line @grafana/no-untranslated-strings
|
||||
placeholder="/home/grot/grot.keytab"
|
||||
onChange={onKeytabFileChanged}
|
||||
width={LONG_WIDTH}
|
||||
@@ -64,14 +74,18 @@ export const KerberosConfig = (props: DataSourcePluginOptionsEditorProps<MssqlOp
|
||||
)}
|
||||
|
||||
{jsonData.authenticationType === MSSQLAuthenticationType.kerberosCredentialCache && (
|
||||
<FieldSet label="Windows AD: Credential cache">
|
||||
<FieldSet label={t('configuration.kerberos-config.label-credential-cache', 'Windows AD: Credential cache')}>
|
||||
<Field
|
||||
label="Credential cache path"
|
||||
label={t('configuration.kerberos-config.label-credential-cache-path', 'Credential cache path')}
|
||||
required
|
||||
invalid={!credentialCache}
|
||||
error={'Credential cache path is required'}
|
||||
error={t(
|
||||
'configuration.kerberos-config.required-credential-cache-path',
|
||||
'Credential cache path is required'
|
||||
)}
|
||||
>
|
||||
<Input
|
||||
// eslint-disable-next-line @grafana/no-untranslated-strings
|
||||
placeholder="/tmp/krb5cc_1000"
|
||||
onChange={onCredentialCacheChanged}
|
||||
width={LONG_WIDTH}
|
||||
@@ -83,28 +97,34 @@ export const KerberosConfig = (props: DataSourcePluginOptionsEditorProps<MssqlOp
|
||||
)}
|
||||
|
||||
{jsonData.authenticationType === MSSQLAuthenticationType.kerberosCredentialCacheLookupFile && (
|
||||
<FieldSet label="Windows AD: Credential cache file">
|
||||
<FieldSet
|
||||
label={t('configuration.kerberos-config.label-credential-cache-file', 'Windows AD: Credential cache file')}
|
||||
>
|
||||
<Field
|
||||
label="Username"
|
||||
label={t('configuration.kerberos-config.label-username', 'Username')}
|
||||
required
|
||||
invalid={!settings.user}
|
||||
error={'Username is required'}
|
||||
error={t('configuration.kerberos-config.required-username', 'Username is required')}
|
||||
description={UsernameMessage}
|
||||
>
|
||||
<Input
|
||||
value={settings.user || ''}
|
||||
placeholder="name@EXAMPLE.COM"
|
||||
placeholder={t('configuration.kerberos-config.placeholder-username', 'name@EXAMPLE.COM')}
|
||||
onChange={(e) => onOptionsChange({ ...settings, ...{ ['user']: e.currentTarget.value } })}
|
||||
width={LONG_WIDTH}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="Credential cache file path"
|
||||
label={t('configuration.kerberos-config.label-credential-cache-file-path', 'Credential cache file path')}
|
||||
required
|
||||
invalid={!credentialCacheLookupFile}
|
||||
error={'Credential cache file path is required'}
|
||||
error={t(
|
||||
'configuration.kerberos-config.required-credential-cache-file-path',
|
||||
'Credential cache file path is required'
|
||||
)}
|
||||
>
|
||||
<Input
|
||||
// eslint-disable-next-line @grafana/no-untranslated-strings
|
||||
placeholder="/home/grot/cache.json"
|
||||
onChange={onCredentialCacheFileChanged}
|
||||
width={LONG_WIDTH}
|
||||
@@ -120,6 +140,7 @@ export const KerberosConfig = (props: DataSourcePluginOptionsEditorProps<MssqlOp
|
||||
|
||||
export const KerberosAdvancedSettings = (props: DataSourcePluginOptionsEditorProps<MssqlOptions>) => {
|
||||
const { options: settings } = props;
|
||||
const { t } = useTranslate();
|
||||
const jsonData = settings.jsonData;
|
||||
const configFilePath = jsonData?.configFilePath;
|
||||
const LONG_WIDTH = 40;
|
||||
@@ -137,19 +158,28 @@ export const KerberosAdvancedSettings = (props: DataSourcePluginOptionsEditorPro
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfigSubSection title="Windows AD: Advanced Settings">
|
||||
<ConfigSubSection
|
||||
title={t('configuration.kerberos-advanced-settings.title-advanced-settings', 'Windows AD: Advanced Settings')}
|
||||
>
|
||||
<FieldSet>
|
||||
<Field
|
||||
label="UDP Preference Limit"
|
||||
label={t('configuration.kerberos-advanced-settings.label-udp-preference-limit', 'UDP Preference Limit')}
|
||||
// TODO
|
||||
description={
|
||||
<span>
|
||||
The default is <code>1</code> and means always use TCP and is optional.
|
||||
<Trans
|
||||
i18nKey="configuration.kerberos-advanced-settings.description-udp-preference-limit"
|
||||
values={{ default: '1' }}
|
||||
>
|
||||
The default is <code>{'{{default}}'}</code> and means always use TCP and is optional.
|
||||
</Trans>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
width={LONG_WIDTH}
|
||||
// eslint-disable-next-line @grafana/no-untranslated-strings
|
||||
placeholder="0"
|
||||
defaultValue={jsonData.UDPConnectionLimit}
|
||||
onChange={(e) => {
|
||||
@@ -161,31 +191,42 @@ export const KerberosAdvancedSettings = (props: DataSourcePluginOptionsEditorPro
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="DNS Lookup KDC"
|
||||
label={t('configuration.kerberos-advanced-settings.label-dns-lookup-kdc', 'DNS Lookup KDC')}
|
||||
description={
|
||||
<span>
|
||||
Indicate whether DNS `SRV` records should be used to locate the KDCs and other servers for a realm. The
|
||||
default is <code>true</code>.
|
||||
<Trans
|
||||
i18nKey="configuration.kerberos-advanced-settings.description-dns-lookup-kdc"
|
||||
values={{ default: 'true' }}
|
||||
>
|
||||
Indicate whether DNS `SRV` records should be used to locate the KDCs and other servers for a realm.
|
||||
The default is <code>{'{{default}}'}</code>.
|
||||
</Trans>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
width={LONG_WIDTH}
|
||||
// eslint-disable-next-line @grafana/no-untranslated-strings
|
||||
placeholder="true"
|
||||
defaultValue={jsonData.enableDNSLookupKDC}
|
||||
onChange={onDNSLookupKDCChanged}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
label="krb5 config file path"
|
||||
label={t('configuration.kerberos-advanced-settings.label-krb5-config-file-path', 'krb5 config file path')}
|
||||
description={
|
||||
<span>
|
||||
The path to the configuration file for the{' '}
|
||||
<a href="https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html">
|
||||
MIT krb5 package
|
||||
</a>
|
||||
. The default is <code>/etc/krb5.conf</code>.
|
||||
<Trans
|
||||
i18nKey="configuration.kerberos-advanced-settings.description-krb5-config-file-path"
|
||||
values={{ default: '/etc/krb5.conf' }}
|
||||
>
|
||||
The path to the configuration file for the{' '}
|
||||
<TextLink external href="https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html">
|
||||
MIT krb5 package
|
||||
</TextLink>
|
||||
. The default is <code>{'{{default}}'}</code>.
|
||||
</Trans>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"azureauth": {
|
||||
"azure-credentials-form": {
|
||||
"aria-label-application-client-id": "Application Client ID",
|
||||
"aria-label-azure-cloud": "Azure Cloud",
|
||||
"aria-label-client-secret": "Client Secret",
|
||||
"aria-label-configured-client-secret": "Client Secret",
|
||||
"aria-label-password": "Password",
|
||||
"aria-label-password-configured": "Password",
|
||||
"aria-label-tenant-id": "Tenant ID",
|
||||
"aria-label-user-id": "User ID",
|
||||
"arialabel-client-id": "Client ID",
|
||||
"auth-options-app-registration": "App Registration",
|
||||
"auth-options-azure-entra": "Azure Entra Password",
|
||||
"auth-options-managed-identity": "Managed Identity",
|
||||
"client-secret-reset": "Reset",
|
||||
"description-authentication": "Choose the type of authentication to Azure services",
|
||||
"label-application-client-id": "Application Client ID",
|
||||
"label-authentication": "Authentication",
|
||||
"label-azure-cloud": "Azure Cloud",
|
||||
"label-client-id": "Application (client) ID",
|
||||
"label-client-secret": "Client Secret",
|
||||
"label-configured-client-secret": "Client Secret",
|
||||
"label-password": "Password",
|
||||
"label-password-configured": "Password",
|
||||
"label-tenant-id": "Directory (tenant) ID",
|
||||
"label-user-id": "User Id",
|
||||
"password-reset": "Reset",
|
||||
"placeholder-configured-client-secret": "configured",
|
||||
"placeholder-password-configured": "configured",
|
||||
"required-application-client-id": "Application Client ID is required",
|
||||
"required-client-id": "Client ID is required",
|
||||
"required-client-secret": "Client secret is required",
|
||||
"required-password": "Password is required",
|
||||
"required-tenant-id": "Tenant ID is required"
|
||||
}
|
||||
},
|
||||
"cheat-sheet": {
|
||||
"condtional-macros": "Or build your own conditionals using these macros which just return the values:",
|
||||
"example-time-group": "Example of group by and order by with {{timeGroupMacro}}:",
|
||||
"fillvalue": "by setting fillvalue Grafana will fill in missing values according to the interval. fillvalue can be either a literal value, {{null}} or {{previous}}; {{previous}} will fill in the previous seen value or {{null}} if none has been seen yet",
|
||||
"macros": "Macros:",
|
||||
"optional": "Optional:",
|
||||
"optional-tip": "return column named <1>{{columnName}}</1> to represent the series name.",
|
||||
"optional-tip-2": "If multiple value columns are returned the {{columnName}} column is used as prefix.",
|
||||
"optional-tip-3": "If no column named {{columnName}} is found the column name of the value column is used as series name",
|
||||
"resultsets-time-sorted": "Resultsets of time series queries need to be sorted by time.",
|
||||
"table": "Table:",
|
||||
"table-tip": "return any set of columns",
|
||||
"time-series": "Time series:",
|
||||
"time-series-tip": "return column named time (in UTC), as a unix time stamp or any sql native date data type. You can use the macros below.",
|
||||
"time-series-tip-2": "any other columns returned will be the time point values.",
|
||||
"title": "MSSQL cheat sheet"
|
||||
},
|
||||
"configuration": {
|
||||
"configuration-editor": {
|
||||
"body-user-permission": "The database user should only be granted {{permissionType}} permissions on the specified database and tables you want to query. Grafana does not validate that queries are safe so queries can contain any SQL statement. For example, statements like <3>{{example1}}</3> and <5>{{example2}}</5> would be executed. To protect against this we <7>highly</7> recommend you create a specific MS SQL user with restricted permissions. Check out the <10>Microsoft SQL Server Data Source Docs</10> for more information.",
|
||||
"description-additional-settings": "Additional settings are optional settings that can be configured for more control over your data source. This includes connection limits, connection timeout, group-by time interval, and Secure Socks Proxy.",
|
||||
"description-auth-type-azure-auth": "<0>Azure Authentication</0> Securely authenticate and access Azure resources and applications using Azure AD credentials - Managed Service Identity and Client Secret Credentials are supported.",
|
||||
"description-auth-type-credential-cache": "<0>Windows AD: Credential cache</0> Windows Active Directory - Sign on for domain user via credential cache.",
|
||||
"description-auth-type-credential-cache-file": "<0>Windows AD: Credential cache file</0> Windows Active Directory - Sign on for domain user via credential cache file.",
|
||||
"description-auth-type-keytab": "<0>Windows AD: Keytab</0> Windows Active Directory - Sign on for domain user via keytab file.",
|
||||
"description-auth-type-sql-server": "<0>SQL Server Authentication</0> This is the default mechanism to connect to MS SQL Server. Enter the SQL Server Authentication login or the Windows Authentication login in the DOMAIN\\User format.",
|
||||
"description-auth-type-username-password": "<0>Windows AD: Username + password</0> Windows Active Directory - Sign on for domain user via username/password.",
|
||||
"description-auth-type-windows-auth": "<0>Windows Authentication</0> Windows Integrated Security - single sign on for users who are already logged onto Windows and have enabled this option for MS SQL Server.",
|
||||
"description-connection-timeout": "The number of seconds to wait before canceling the request when connecting to the database. The default is <1>{{defaultTimeout}}</1>, meaning no timeout.",
|
||||
"description-encrypt": "Determines whether or to which extent a secure SSL TCP/IP connection will be negotiated with the server.",
|
||||
"description-encrypt-disable": "<0>{{encryptionValue}}</0> - Data sent between client and server is not encrypted.",
|
||||
"description-encrypt-false": "<0>{{encryptionValue}}</0> - Data sent between client and server is not encrypted beyond the login packet. (default)",
|
||||
"description-encrypt-older-version": "If you're using an older version of Microsoft SQL Server like 2008 and 2008R2 you may need to disable encryption to be able to connect.",
|
||||
"description-encrypt-true": "<0>{{encryptionValue}}</0> - Data sent between client and server is encrypted.",
|
||||
"description-min-interval": "A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example<1>{{exampleInterval}}</1> if your data is written every minute.",
|
||||
"description-tls-cert": "Path to file containing the public key certificate of the CA that signed the SQL Server certificate. Needed when the server certificate is self signed.",
|
||||
"label-auth-settings": "Azure Authentication Settings",
|
||||
"label-auth-type": "Authentication Type",
|
||||
"label-common-name": "Hostname in server certificate",
|
||||
"label-connection-timeout": "Connection timeout",
|
||||
"label-encrypt": "Encrypt",
|
||||
"label-min-interval": "Min time interval",
|
||||
"label-password": "Password",
|
||||
"label-skip-tls": "Skip TLS Verify",
|
||||
"label-tls-cert": "TLS/SSL Root Certificate",
|
||||
"label-username": "Username",
|
||||
"placeholder-common-name": "Common Name (CN) in server certificate",
|
||||
"placeholder-database": "database name",
|
||||
"placeholder-password": "Password",
|
||||
"placeholder-tls-cert": "TLS/SSL root certificate file path",
|
||||
"placeholder-user": "user",
|
||||
"required-database": "Database is required",
|
||||
"required-host": "Host is required",
|
||||
"required-password": "Password is required",
|
||||
"required-username": "Username is required",
|
||||
"title-additional-settings": "Additional settings",
|
||||
"title-authentication": "Authentication",
|
||||
"title-connection": "Connection",
|
||||
"title-connection-details": "Connection details",
|
||||
"title-database": "Database",
|
||||
"title-host": "Host",
|
||||
"title-tls-auth": "TLS/SSL Auth",
|
||||
"title-user-permission": "User Permission"
|
||||
},
|
||||
"kerberos": {
|
||||
"username-message": "Use the format <1>user@EXAMPLE.COM</1>. Realm is derived from the username."
|
||||
},
|
||||
"kerberos-advanced-settings": {
|
||||
"description-dns-lookup-kdc": "Indicate whether DNS `SRV` records should be used to locate the KDCs and other servers for a realm. The default is <1>{{default}}</1>.",
|
||||
"description-krb5-config-file-path": "The path to the configuration file for the <2>MIT krb5 package</2>. The default is <4>{{default}}</4>.",
|
||||
"description-udp-preference-limit": "The default is <1>{{default}}</1> and means always use TCP and is optional.",
|
||||
"label-dns-lookup-kdc": "DNS Lookup KDC",
|
||||
"label-krb5-config-file-path": "krb5 config file path",
|
||||
"label-udp-preference-limit": "UDP Preference Limit",
|
||||
"title-advanced-settings": "Windows AD: Advanced Settings"
|
||||
},
|
||||
"kerberos-config": {
|
||||
"label-credential-cache": "Windows AD: Credential cache",
|
||||
"label-credential-cache-file": "Windows AD: Credential cache file",
|
||||
"label-credential-cache-file-path": "Credential cache file path",
|
||||
"label-credential-cache-path": "Credential cache path",
|
||||
"label-keytab": "Windows AD: Keytab",
|
||||
"label-keytab-file-path": "Keytab file path",
|
||||
"label-username": "Username",
|
||||
"placeholder-username": "name@EXAMPLE.COM",
|
||||
"required-credential-cache-file-path": "Credential cache file path is required",
|
||||
"required-credential-cache-path": "Credential cache path is required",
|
||||
"required-username": "Username is required"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
locales: ['en-US'], // Only en-US is updated - Crowdin will PR with other languages
|
||||
sort: true,
|
||||
createOldCatalogs: false,
|
||||
failOnWarnings: true,
|
||||
verbose: false,
|
||||
resetDefaultValueLocale: 'en-US', // Updates extracted values when they change in code
|
||||
|
||||
defaultNamespace: 'mssql',
|
||||
input: ['../**/*.{tsx,ts}'],
|
||||
output: './locales/$LOCALE/$NAMESPACE.json',
|
||||
};
|
||||
@@ -1,11 +1,15 @@
|
||||
import { DataSourcePlugin } from '@grafana/data';
|
||||
import { initPluginTranslations } from '@grafana/i18n';
|
||||
import { SQLQuery, SqlQueryEditorLazy } from '@grafana/sql';
|
||||
|
||||
import { CheatSheet } from './CheatSheet';
|
||||
import { ConfigurationEditor } from './configuration/ConfigurationEditor';
|
||||
import { MssqlDatasource } from './datasource';
|
||||
import pluginJson from './plugin.json';
|
||||
import { MssqlOptions } from './types';
|
||||
|
||||
initPluginTranslations(pluginJson.id);
|
||||
|
||||
export const plugin = new DataSourcePlugin<MssqlDatasource, SQLQuery, MssqlOptions>(MssqlDatasource)
|
||||
.setQueryEditor(SqlQueryEditorLazy)
|
||||
.setQueryEditorHelp(CheatSheet)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "12.1.0-pre",
|
||||
"@grafana/i18n": "12.1.0-pre",
|
||||
"@grafana/plugin-ui": "0.10.5",
|
||||
"@grafana/runtime": "12.1.0-pre",
|
||||
"@grafana/sql": "12.1.0-pre",
|
||||
@@ -25,6 +26,7 @@
|
||||
"@types/lodash": "4.17.15",
|
||||
"@types/node": "22.12.0",
|
||||
"@types/react": "18.3.18",
|
||||
"i18next-parser": "9.3.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.7.3",
|
||||
"webpack": "5.97.1"
|
||||
@@ -35,7 +37,8 @@
|
||||
"scripts": {
|
||||
"build": "webpack -c ./webpack.config.ts --env production",
|
||||
"build:commit": "webpack -c ./webpack.config.ts --env production --env commit=$(git rev-parse --short HEAD)",
|
||||
"dev": "webpack -w -c ./webpack.config.ts --env development"
|
||||
"dev": "webpack -w -c ./webpack.config.ts --env development",
|
||||
"i18n-extract": "i18next --config locales/i18next-parser.config.cjs"
|
||||
},
|
||||
"packageManager": "yarn@4.6.0"
|
||||
}
|
||||
|
||||
@@ -35,5 +35,26 @@
|
||||
|
||||
"queryOptions": {
|
||||
"minInterval": true
|
||||
}
|
||||
},
|
||||
"languages": [
|
||||
"en-US",
|
||||
"fr-FR",
|
||||
"es-ES",
|
||||
"de-DE",
|
||||
"pt-BR",
|
||||
"zh-Hans",
|
||||
"it-IT",
|
||||
"ja-JP",
|
||||
"id-ID",
|
||||
"ko-KR",
|
||||
"ru-RU",
|
||||
"cs-CZ",
|
||||
"nl-NL",
|
||||
"hu-HU",
|
||||
"pt-PT",
|
||||
"pl-PL",
|
||||
"sv-SE",
|
||||
"tr-TR",
|
||||
"zh-Hant"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import config from '@grafana/plugin-configs/webpack.config';
|
||||
import type { Configuration } from 'webpack';
|
||||
import { merge } from 'webpack-merge';
|
||||
|
||||
import grafanaConfig from '@grafana/plugin-configs/webpack.config';
|
||||
|
||||
const config = async (env: Record<string, unknown>): Promise<Configuration> => {
|
||||
const baseConfig = await grafanaConfig(env);
|
||||
|
||||
return merge(baseConfig, {
|
||||
externals: ['i18next'],
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-barrel-files/no-barrel-files
|
||||
export default config;
|
||||
|
||||
@@ -2685,6 +2685,7 @@ __metadata:
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@grafana/data": "npm:12.1.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:12.1.0-pre"
|
||||
"@grafana/i18n": "npm:12.1.0-pre"
|
||||
"@grafana/plugin-configs": "npm:12.1.0-pre"
|
||||
"@grafana/plugin-ui": "npm:0.10.5"
|
||||
"@grafana/runtime": "npm:12.1.0-pre"
|
||||
@@ -2697,6 +2698,7 @@ __metadata:
|
||||
"@types/lodash": "npm:4.17.15"
|
||||
"@types/node": "npm:22.12.0"
|
||||
"@types/react": "npm:18.3.18"
|
||||
i18next-parser: "npm:9.3.0"
|
||||
lodash: "npm:4.17.21"
|
||||
react: "npm:18.3.1"
|
||||
rxjs: "npm:7.8.1"
|
||||
|
||||
Reference in New Issue
Block a user