mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus: Azure authentication in configuration UI (#35860)
* Azure authentication settings * Persisting credentials * Azure settings * Prometheus-specific settings component * Azure Prometheus Resource ID configuration * DataSourceHttpSettings with extensibility for Azure * Feature toggle for Azure auth * Fix snapshot * Update format of persisted credentials * AzureSettings renamed to AzureAuthSettings
This commit is contained in:
parent
013218e075
commit
4664cba935
@ -56,7 +56,14 @@ const HttpAccessHelp = () => (
|
||||
);
|
||||
|
||||
export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = (props) => {
|
||||
const { defaultUrl, dataSourceConfig, onChange, showAccessOptions, sigV4AuthToggleEnabled } = props;
|
||||
const {
|
||||
defaultUrl,
|
||||
dataSourceConfig,
|
||||
onChange,
|
||||
showAccessOptions,
|
||||
sigV4AuthToggleEnabled,
|
||||
azureAuthSettings,
|
||||
} = props;
|
||||
let urlTooltip;
|
||||
const [isAccessHelpVisible, setIsAccessHelpVisible] = useState(false);
|
||||
const theme = useTheme();
|
||||
@ -207,6 +214,22 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = (props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{azureAuthSettings?.azureAuthEnabled && (
|
||||
<div className="gf-form-inline">
|
||||
<Switch
|
||||
label="Azure Authentication"
|
||||
labelClass="width-13"
|
||||
checked={dataSourceConfig.jsonData.azureAuth || false}
|
||||
onChange={(event) => {
|
||||
onSettingsChange({
|
||||
jsonData: { ...dataSourceConfig.jsonData, azureAuth: event!.currentTarget.checked },
|
||||
});
|
||||
}}
|
||||
tooltip="Use Azure authentication for Azure endpoint."
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sigV4AuthToggleEnabled && (
|
||||
<div className="gf-form-inline">
|
||||
<Switch
|
||||
@ -238,6 +261,12 @@ export const DataSourceHttpSettings: React.FC<HttpSettingsProps> = (props) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{azureAuthSettings?.azureAuthEnabled &&
|
||||
azureAuthSettings?.azureSettingsUI &&
|
||||
dataSourceConfig.jsonData.azureAuth && (
|
||||
<azureAuthSettings.azureSettingsUI dataSourceConfig={dataSourceConfig} onChange={onChange} />
|
||||
)}
|
||||
|
||||
{dataSourceConfig.jsonData.sigV4Auth && <SigV4AuthSettings {...props} />}
|
||||
|
||||
{(dataSourceConfig.jsonData.tlsAuth || dataSourceConfig.jsonData.tlsAuthWithCACert) && (
|
||||
|
@ -1,5 +1,11 @@
|
||||
import React from 'react';
|
||||
import { DataSourceSettings } from '@grafana/data';
|
||||
|
||||
export interface AzureAuthSettings {
|
||||
azureAuthEnabled: boolean;
|
||||
azureSettingsUI?: React.ComponentType<HttpSettingsBaseProps>;
|
||||
}
|
||||
|
||||
export interface HttpSettingsBaseProps {
|
||||
/** The configuration object of the data source */
|
||||
dataSourceConfig: DataSourceSettings<any, any>;
|
||||
@ -14,4 +20,6 @@ export interface HttpSettingsProps extends HttpSettingsBaseProps {
|
||||
showAccessOptions?: boolean;
|
||||
/** Show the SigV4 auth toggle option */
|
||||
sigV4AuthToggleEnabled?: boolean;
|
||||
/** Azure authentication settings **/
|
||||
azureAuthSettings?: AzureAuthSettings;
|
||||
}
|
||||
|
@ -0,0 +1,51 @@
|
||||
import React, { FunctionComponent, useMemo } from 'react';
|
||||
import { InlineFormLabel, Input } from '@grafana/ui';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { KnownAzureClouds, AzureCredentials } from './AzureCredentials';
|
||||
import { getCredentials, updateCredentials } from './AzureCredentialsConfig';
|
||||
import { AzureCredentialsForm } from './AzureCredentialsForm';
|
||||
import { HttpSettingsBaseProps } from '@grafana/ui/src/components/DataSourceSettings/types';
|
||||
|
||||
export const AzureAuthSettings: FunctionComponent<HttpSettingsBaseProps> = (props: HttpSettingsBaseProps) => {
|
||||
const { dataSourceConfig, onChange } = props;
|
||||
|
||||
const credentials = useMemo(() => getCredentials(dataSourceConfig), [dataSourceConfig]);
|
||||
|
||||
const onCredentialsChange = (credentials: AzureCredentials): void => {
|
||||
onChange(updateCredentials(dataSourceConfig, credentials));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h6>Azure Authentication</h6>
|
||||
<AzureCredentialsForm
|
||||
managedIdentityEnabled={config.azure.managedIdentityEnabled}
|
||||
credentials={credentials}
|
||||
azureCloudOptions={KnownAzureClouds}
|
||||
onCredentialsChange={onCredentialsChange}
|
||||
/>
|
||||
<h6>Azure Configuration</h6>
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12">AAD resource ID</InlineFormLabel>
|
||||
<div className="width-15">
|
||||
<Input
|
||||
className="width-30"
|
||||
value={dataSourceConfig.jsonData.azureEndpointResourceId || ''}
|
||||
onChange={(event) =>
|
||||
onChange({
|
||||
...dataSourceConfig,
|
||||
jsonData: { ...dataSourceConfig.jsonData, azureEndpointResourceId: event.currentTarget.value },
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AzureAuthSettings;
|
@ -0,0 +1,48 @@
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
export enum AzureCloud {
|
||||
Public = 'AzureCloud',
|
||||
China = 'AzureChinaCloud',
|
||||
USGovernment = 'AzureUSGovernment',
|
||||
Germany = 'AzureGermanCloud',
|
||||
None = '',
|
||||
}
|
||||
|
||||
export const KnownAzureClouds = [
|
||||
{ value: AzureCloud.Public, label: 'Azure' },
|
||||
{ value: AzureCloud.China, label: 'Azure China' },
|
||||
{ value: AzureCloud.USGovernment, label: 'Azure US Government' },
|
||||
{ value: AzureCloud.Germany, label: 'Azure Germany' },
|
||||
] as SelectableValue[];
|
||||
|
||||
export type AzureAuthType = 'msi' | 'clientsecret';
|
||||
|
||||
export type ConcealedSecret = symbol;
|
||||
|
||||
interface AzureCredentialsBase {
|
||||
authType: AzureAuthType;
|
||||
defaultSubscriptionId?: string;
|
||||
}
|
||||
|
||||
export interface AzureManagedIdentityCredentials extends AzureCredentialsBase {
|
||||
authType: 'msi';
|
||||
}
|
||||
|
||||
export interface AzureClientSecretCredentials extends AzureCredentialsBase {
|
||||
authType: 'clientsecret';
|
||||
azureCloud?: string;
|
||||
tenantId?: string;
|
||||
clientId?: string;
|
||||
clientSecret?: string | ConcealedSecret;
|
||||
}
|
||||
|
||||
export type AzureCredentials = AzureManagedIdentityCredentials | AzureClientSecretCredentials;
|
||||
|
||||
export function isCredentialsComplete(credentials: AzureCredentials): boolean {
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
return true;
|
||||
case 'clientsecret':
|
||||
return !!(credentials.azureCloud && credentials.tenantId && credentials.clientId && credentials.clientSecret);
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
import { DataSourceSettings } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { AzureCloud, AzureCredentials, ConcealedSecret } from './AzureCredentials';
|
||||
|
||||
const concealed: ConcealedSecret = Symbol('Concealed client secret');
|
||||
|
||||
function getDefaultAzureCloud(): string {
|
||||
return config.azure.cloud || AzureCloud.Public;
|
||||
}
|
||||
|
||||
function getSecret(options: DataSourceSettings<any, any>): undefined | string | ConcealedSecret {
|
||||
if (options.secureJsonFields.azureClientSecret) {
|
||||
// The secret is concealed on server
|
||||
return concealed;
|
||||
} else {
|
||||
const secret = options.secureJsonData?.azureClientSecret;
|
||||
return typeof secret === 'string' && secret.length > 0 ? secret : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function getCredentials(options: DataSourceSettings<any, any>): AzureCredentials {
|
||||
const credentials = options.jsonData.azureCredentials as AzureCredentials | undefined;
|
||||
|
||||
// If no credentials saved, then return empty credentials
|
||||
// of type based on whether the managed identity enabled
|
||||
if (!credentials) {
|
||||
return {
|
||||
authType: config.azure.managedIdentityEnabled ? 'msi' : 'clientsecret',
|
||||
azureCloud: getDefaultAzureCloud(),
|
||||
};
|
||||
}
|
||||
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
if (config.azure.managedIdentityEnabled) {
|
||||
return {
|
||||
authType: 'msi',
|
||||
};
|
||||
} else {
|
||||
// If authentication type is managed identity but managed identities were disabled in Grafana config,
|
||||
// then we should fallback to an empty app registration (client secret) configuration
|
||||
return {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: getDefaultAzureCloud(),
|
||||
};
|
||||
}
|
||||
case 'clientsecret':
|
||||
return {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: credentials.azureCloud || getDefaultAzureCloud(),
|
||||
tenantId: credentials.tenantId,
|
||||
clientId: credentials.clientId,
|
||||
clientSecret: getSecret(options),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function updateCredentials(
|
||||
options: DataSourceSettings<any, any>,
|
||||
credentials: AzureCredentials
|
||||
): DataSourceSettings<any, any> {
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
if (!config.azure.managedIdentityEnabled) {
|
||||
throw new Error('Managed Identity authentication is not enabled in Grafana config.');
|
||||
}
|
||||
|
||||
options = {
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
azureCredentials: {
|
||||
authType: 'msi',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return options;
|
||||
|
||||
case 'clientsecret':
|
||||
options = {
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
azureCredentials: {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: credentials.azureCloud || getDefaultAzureCloud(),
|
||||
tenantId: credentials.tenantId,
|
||||
clientId: credentials.clientId,
|
||||
},
|
||||
},
|
||||
secureJsonData: {
|
||||
...options.secureJsonData,
|
||||
azureClientSecret:
|
||||
typeof credentials.clientSecret === 'string' && credentials.clientSecret.length > 0
|
||||
? credentials.clientSecret
|
||||
: undefined,
|
||||
},
|
||||
secureJsonFields: {
|
||||
...options.secureJsonFields,
|
||||
azureClientSecret: typeof credentials.clientSecret === 'symbol',
|
||||
},
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import AzureCredentialsForm, { Props } from './AzureCredentialsForm';
|
||||
|
||||
const setup = (propsFunc?: (props: Props) => Props) => {
|
||||
let props: Props = {
|
||||
managedIdentityEnabled: false,
|
||||
credentials: {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: 'azuremonitor',
|
||||
tenantId: 'e7f3f661-a933-3h3f-0294-31c4f962ec48',
|
||||
clientId: '34509fad-c0r9-45df-9e25-f1ee34af6900',
|
||||
clientSecret: undefined,
|
||||
defaultSubscriptionId: '44987801-6nn6-49he-9b2d-9106972f9789',
|
||||
},
|
||||
azureCloudOptions: [
|
||||
{ value: 'azuremonitor', label: 'Azure' },
|
||||
{ value: 'govazuremonitor', label: 'Azure US Government' },
|
||||
{ value: 'germanyazuremonitor', label: 'Azure Germany' },
|
||||
{ value: 'chinaazuremonitor', label: 'Azure China' },
|
||||
],
|
||||
onCredentialsChange: jest.fn(),
|
||||
getSubscriptions: jest.fn(),
|
||||
};
|
||||
|
||||
if (propsFunc) {
|
||||
props = propsFunc(props);
|
||||
}
|
||||
|
||||
return shallow(<AzureCredentialsForm {...props} />);
|
||||
};
|
||||
|
||||
describe('Render', () => {
|
||||
it('should render component', () => {
|
||||
const wrapper = setup();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should disable azure monitor secret input', () => {
|
||||
const wrapper = setup((props) => ({
|
||||
...props,
|
||||
credentials: {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: 'azuremonitor',
|
||||
tenantId: 'e7f3f661-a933-3h3f-0294-31c4f962ec48',
|
||||
clientId: '34509fad-c0r9-45df-9e25-f1ee34af6900',
|
||||
clientSecret: Symbol(),
|
||||
},
|
||||
}));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should enable azure monitor load subscriptions button', () => {
|
||||
const wrapper = setup((props) => ({
|
||||
...props,
|
||||
credentials: {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: 'azuremonitor',
|
||||
tenantId: 'e7f3f661-a933-3h3f-0294-31c4f962ec48',
|
||||
clientId: '34509fad-c0r9-45df-9e25-f1ee34af6900',
|
||||
clientSecret: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
|
||||
},
|
||||
}));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,279 @@
|
||||
import React, { ChangeEvent, FunctionComponent, useEffect, useReducer, useState } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { InlineFormLabel, Button } from '@grafana/ui/src/components';
|
||||
import { Select } from '@grafana/ui/src/components/Forms/Legacy/Select/Select';
|
||||
import { Input } from '@grafana/ui/src/components/Forms/Legacy/Input/Input';
|
||||
import { AzureAuthType, AzureCredentials, isCredentialsComplete } from './AzureCredentials';
|
||||
|
||||
export interface Props {
|
||||
managedIdentityEnabled: boolean;
|
||||
credentials: AzureCredentials;
|
||||
azureCloudOptions?: SelectableValue[];
|
||||
onCredentialsChange: (updatedCredentials: AzureCredentials) => void;
|
||||
getSubscriptions?: () => Promise<SelectableValue[]>;
|
||||
}
|
||||
|
||||
const authTypeOptions: Array<SelectableValue<AzureAuthType>> = [
|
||||
{
|
||||
value: 'msi',
|
||||
label: 'Managed Identity',
|
||||
},
|
||||
{
|
||||
value: 'clientsecret',
|
||||
label: 'App Registration',
|
||||
},
|
||||
];
|
||||
|
||||
export const AzureCredentialsForm: FunctionComponent<Props> = (props: Props) => {
|
||||
const { credentials, azureCloudOptions, onCredentialsChange, getSubscriptions } = props;
|
||||
const hasRequiredFields = isCredentialsComplete(credentials);
|
||||
|
||||
const [subscriptions, setSubscriptions] = useState<Array<SelectableValue<string>>>([]);
|
||||
const [loadSubscriptionsClicked, onLoadSubscriptions] = useReducer((val) => val + 1, 0);
|
||||
useEffect(() => {
|
||||
if (!getSubscriptions || !hasRequiredFields) {
|
||||
updateSubscriptions([]);
|
||||
return;
|
||||
}
|
||||
let canceled = false;
|
||||
getSubscriptions().then((result) => {
|
||||
if (!canceled) {
|
||||
updateSubscriptions(result, loadSubscriptionsClicked);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
canceled = true;
|
||||
};
|
||||
// This effect is intended to be called only once initially and on Load Subscriptions click
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [loadSubscriptionsClicked]);
|
||||
|
||||
const updateSubscriptions = (received: Array<SelectableValue<string>>, autoSelect = false) => {
|
||||
setSubscriptions(received);
|
||||
if (getSubscriptions) {
|
||||
if (autoSelect && !credentials.defaultSubscriptionId && received.length > 0) {
|
||||
// Selecting the default subscription if subscriptions received but no default subscription selected
|
||||
onSubscriptionChange(received[0]);
|
||||
} else if (credentials.defaultSubscriptionId) {
|
||||
const found = received.find((opt) => opt.value === credentials.defaultSubscriptionId);
|
||||
if (!found) {
|
||||
// Unselecting the default subscription if it isn't found among the received subscriptions
|
||||
onSubscriptionChange(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onAuthTypeChange = (selected: SelectableValue<AzureAuthType>) => {
|
||||
if (onCredentialsChange) {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
authType: selected.value || 'msi',
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const onAzureCloudChange = (selected: SelectableValue<string>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
azureCloud: selected.value,
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const onTenantIdChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
tenantId: event.target.value,
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const onClientIdChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
clientId: event.target.value,
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const onClientSecretChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
clientSecret: event.target.value,
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const onClientSecretReset = () => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
clientSecret: '',
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubscriptionChange = (selected: SelectableValue<string> | undefined) => {
|
||||
if (onCredentialsChange) {
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
defaultSubscriptionId: selected?.value,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="gf-form-group">
|
||||
{props.managedIdentityEnabled && (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12" tooltip="Choose the type of authentication to Azure services">
|
||||
Authentication
|
||||
</InlineFormLabel>
|
||||
<Select
|
||||
className="width-15"
|
||||
value={authTypeOptions.find((opt) => opt.value === credentials.authType)}
|
||||
options={authTypeOptions}
|
||||
onChange={onAuthTypeChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{credentials.authType === 'clientsecret' && (
|
||||
<>
|
||||
{azureCloudOptions && (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12" tooltip="Choose an Azure Cloud">
|
||||
Azure Cloud
|
||||
</InlineFormLabel>
|
||||
<Select
|
||||
className="width-15"
|
||||
value={azureCloudOptions.find((opt) => opt.value === credentials.azureCloud)}
|
||||
options={azureCloudOptions}
|
||||
onChange={onAzureCloudChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12">Directory (tenant) ID</InlineFormLabel>
|
||||
<div className="width-15">
|
||||
<Input
|
||||
className="width-30"
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value={credentials.tenantId || ''}
|
||||
onChange={onTenantIdChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12">Application (client) ID</InlineFormLabel>
|
||||
<div className="width-15">
|
||||
<Input
|
||||
className="width-30"
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value={credentials.clientId || ''}
|
||||
onChange={onClientIdChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{typeof credentials.clientSecret === 'symbol' ? (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12">Client Secret</InlineFormLabel>
|
||||
<Input className="width-25" placeholder="configured" disabled={true} />
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<div className="max-width-30 gf-form-inline">
|
||||
<Button variant="secondary" type="button" onClick={onClientSecretReset}>
|
||||
reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12">Client Secret</InlineFormLabel>
|
||||
<div className="width-15">
|
||||
<Input
|
||||
className="width-30"
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value={credentials.clientSecret || ''}
|
||||
onChange={onClientSecretChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{getSubscriptions && (
|
||||
<>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12">Default Subscription</InlineFormLabel>
|
||||
<div className="width-25">
|
||||
<Select
|
||||
value={
|
||||
credentials.defaultSubscriptionId
|
||||
? subscriptions.find((opt) => opt.value === credentials.defaultSubscriptionId)
|
||||
: undefined
|
||||
}
|
||||
options={subscriptions}
|
||||
onChange={onSubscriptionChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<div className="max-width-30 gf-form-inline">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={onLoadSubscriptions}
|
||||
disabled={!hasRequiredFields}
|
||||
>
|
||||
Load Subscriptions
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AzureCredentialsForm;
|
@ -1,13 +1,20 @@
|
||||
import React from 'react';
|
||||
import { AlertingSettings, DataSourceHttpSettings } from '@grafana/ui';
|
||||
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
|
||||
import { PromSettings } from './PromSettings';
|
||||
import { PromOptions } from '../types';
|
||||
import { config } from 'app/core/config';
|
||||
import { PromOptions } from '../types';
|
||||
import { AzureAuthSettings } from './AzureAuthSettings';
|
||||
import { PromSettings } from './PromSettings';
|
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps<PromOptions>;
|
||||
export const ConfigEditor = (props: Props) => {
|
||||
const { options, onOptionsChange } = props;
|
||||
|
||||
const azureAuthSettings = {
|
||||
azureAuthEnabled: config.featureToggles['prometheus_azure_auth'] ?? false,
|
||||
azureSettingsUI: AzureAuthSettings,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataSourceHttpSettings
|
||||
@ -16,6 +23,7 @@ export const ConfigEditor = (props: Props) => {
|
||||
showAccessOptions={true}
|
||||
onChange={onOptionsChange}
|
||||
sigV4AuthToggleEnabled={config.sigV4AuthEnabled}
|
||||
azureAuthSettings={azureAuthSettings}
|
||||
/>
|
||||
|
||||
<AlertingSettings<PromOptions> options={options} onOptionsChange={onOptionsChange} />
|
||||
|
@ -0,0 +1,620 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Render should disable azure monitor secret input 1`] = `
|
||||
<div
|
||||
className="gf-form-group"
|
||||
>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
tooltip="Choose an Azure Cloud"
|
||||
>
|
||||
Azure Cloud
|
||||
</FormLabel>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-15"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "Azure",
|
||||
"value": "azuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure US Government",
|
||||
"value": "govazuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure Germany",
|
||||
"value": "germanyazuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure China",
|
||||
"value": "chinaazuremonitor",
|
||||
},
|
||||
]
|
||||
}
|
||||
tabSelectsValue={true}
|
||||
value={
|
||||
Object {
|
||||
"label": "Azure",
|
||||
"value": "azuremonitor",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Directory (tenant) ID
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="e7f3f661-a933-3h3f-0294-31c4f962ec48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Application (client) ID
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="34509fad-c0r9-45df-9e25-f1ee34af6900"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Client Secret
|
||||
</FormLabel>
|
||||
<Input
|
||||
className="width-25"
|
||||
disabled={true}
|
||||
placeholder="configured"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<div
|
||||
className="max-width-30 gf-form-inline"
|
||||
>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
variant="secondary"
|
||||
>
|
||||
reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Default Subscription
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-25"
|
||||
>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className=""
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={Array []}
|
||||
tabSelectsValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<div
|
||||
className="max-width-30 gf-form-inline"
|
||||
>
|
||||
<Button
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
>
|
||||
Load Subscriptions
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Render should enable azure monitor load subscriptions button 1`] = `
|
||||
<div
|
||||
className="gf-form-group"
|
||||
>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
tooltip="Choose an Azure Cloud"
|
||||
>
|
||||
Azure Cloud
|
||||
</FormLabel>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-15"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "Azure",
|
||||
"value": "azuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure US Government",
|
||||
"value": "govazuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure Germany",
|
||||
"value": "germanyazuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure China",
|
||||
"value": "chinaazuremonitor",
|
||||
},
|
||||
]
|
||||
}
|
||||
tabSelectsValue={true}
|
||||
value={
|
||||
Object {
|
||||
"label": "Azure",
|
||||
"value": "azuremonitor",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Directory (tenant) ID
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="e7f3f661-a933-3h3f-0294-31c4f962ec48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Application (client) ID
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="34509fad-c0r9-45df-9e25-f1ee34af6900"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Client Secret
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="e7f3f661-a933-4b3f-8176-51c4f982ec48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Default Subscription
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-25"
|
||||
>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className=""
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={Array []}
|
||||
tabSelectsValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<div
|
||||
className="max-width-30 gf-form-inline"
|
||||
>
|
||||
<Button
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
>
|
||||
Load Subscriptions
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Render should render component 1`] = `
|
||||
<div
|
||||
className="gf-form-group"
|
||||
>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
tooltip="Choose an Azure Cloud"
|
||||
>
|
||||
Azure Cloud
|
||||
</FormLabel>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className="width-15"
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"label": "Azure",
|
||||
"value": "azuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure US Government",
|
||||
"value": "govazuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure Germany",
|
||||
"value": "germanyazuremonitor",
|
||||
},
|
||||
Object {
|
||||
"label": "Azure China",
|
||||
"value": "chinaazuremonitor",
|
||||
},
|
||||
]
|
||||
}
|
||||
tabSelectsValue={true}
|
||||
value={
|
||||
Object {
|
||||
"label": "Azure",
|
||||
"value": "azuremonitor",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Directory (tenant) ID
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="e7f3f661-a933-3h3f-0294-31c4f962ec48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Application (client) ID
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value="34509fad-c0r9-45df-9e25-f1ee34af6900"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Client Secret
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-15"
|
||||
>
|
||||
<Input
|
||||
className="width-30"
|
||||
onChange={[Function]}
|
||||
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<FormLabel
|
||||
className="width-12"
|
||||
>
|
||||
Default Subscription
|
||||
</FormLabel>
|
||||
<div
|
||||
className="width-25"
|
||||
>
|
||||
<Select
|
||||
allowCustomValue={false}
|
||||
autoFocus={false}
|
||||
backspaceRemovesValue={true}
|
||||
className=""
|
||||
components={
|
||||
Object {
|
||||
"Group": [Function],
|
||||
"IndicatorsContainer": [Function],
|
||||
"MenuList": [Function],
|
||||
"Option": [Function],
|
||||
"SingleValue": [Function],
|
||||
}
|
||||
}
|
||||
isClearable={false}
|
||||
isDisabled={false}
|
||||
isLoading={false}
|
||||
isMulti={false}
|
||||
isSearchable={true}
|
||||
maxMenuHeight={300}
|
||||
onChange={[Function]}
|
||||
openMenuOnFocus={false}
|
||||
options={Array []}
|
||||
tabSelectsValue={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="gf-form-inline"
|
||||
>
|
||||
<div
|
||||
className="gf-form"
|
||||
>
|
||||
<div
|
||||
className="max-width-30 gf-form-inline"
|
||||
>
|
||||
<Button
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant="secondary"
|
||||
>
|
||||
Load Subscriptions
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
Loading…
Reference in New Issue
Block a user