AzureCredentialsForm.tsx 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. import React, { ChangeEvent, FunctionComponent, useEffect, useReducer, useState } from 'react';
  2. import { SelectableValue } from '@grafana/data';
  3. import { InlineFormLabel, Button } from '@grafana/ui/src/components';
  4. import { Input } from '@grafana/ui/src/components/Forms/Legacy/Input/Input';
  5. import { Select } from '@grafana/ui/src/components/Forms/Legacy/Select/Select';
  6. import { AzureAuthType, AzureCredentials, isCredentialsComplete } from './AzureCredentials';
  7. export interface Props {
  8. managedIdentityEnabled: boolean;
  9. credentials: AzureCredentials;
  10. azureCloudOptions?: SelectableValue[];
  11. onCredentialsChange: (updatedCredentials: AzureCredentials) => void;
  12. getSubscriptions?: () => Promise<SelectableValue[]>;
  13. }
  14. const authTypeOptions: Array<SelectableValue<AzureAuthType>> = [
  15. {
  16. value: 'msi',
  17. label: 'Managed Identity',
  18. },
  19. {
  20. value: 'clientsecret',
  21. label: 'App Registration',
  22. },
  23. ];
  24. export const AzureCredentialsForm: FunctionComponent<Props> = (props: Props) => {
  25. const { credentials, azureCloudOptions, onCredentialsChange, getSubscriptions } = props;
  26. const hasRequiredFields = isCredentialsComplete(credentials);
  27. const [subscriptions, setSubscriptions] = useState<Array<SelectableValue<string>>>([]);
  28. const [loadSubscriptionsClicked, onLoadSubscriptions] = useReducer((val) => val + 1, 0);
  29. useEffect(() => {
  30. if (!getSubscriptions || !hasRequiredFields) {
  31. updateSubscriptions([]);
  32. return;
  33. }
  34. let canceled = false;
  35. getSubscriptions().then((result) => {
  36. if (!canceled) {
  37. updateSubscriptions(result, loadSubscriptionsClicked);
  38. }
  39. });
  40. return () => {
  41. canceled = true;
  42. };
  43. // This effect is intended to be called only once initially and on Load Subscriptions click
  44. // eslint-disable-next-line react-hooks/exhaustive-deps
  45. }, [loadSubscriptionsClicked]);
  46. const updateSubscriptions = (received: Array<SelectableValue<string>>, autoSelect = false) => {
  47. setSubscriptions(received);
  48. if (getSubscriptions) {
  49. if (autoSelect && !credentials.defaultSubscriptionId && received.length > 0) {
  50. // Selecting the default subscription if subscriptions received but no default subscription selected
  51. onSubscriptionChange(received[0]);
  52. } else if (credentials.defaultSubscriptionId) {
  53. const found = received.find((opt) => opt.value === credentials.defaultSubscriptionId);
  54. if (!found) {
  55. // Unselecting the default subscription if it isn't found among the received subscriptions
  56. onSubscriptionChange(undefined);
  57. }
  58. }
  59. }
  60. };
  61. const onAuthTypeChange = (selected: SelectableValue<AzureAuthType>) => {
  62. if (onCredentialsChange) {
  63. setSubscriptions([]);
  64. const updated: AzureCredentials = {
  65. ...credentials,
  66. authType: selected.value || 'msi',
  67. defaultSubscriptionId: undefined,
  68. };
  69. onCredentialsChange(updated);
  70. }
  71. };
  72. const onAzureCloudChange = (selected: SelectableValue<string>) => {
  73. if (onCredentialsChange && credentials.authType === 'clientsecret') {
  74. setSubscriptions([]);
  75. const updated: AzureCredentials = {
  76. ...credentials,
  77. azureCloud: selected.value,
  78. defaultSubscriptionId: undefined,
  79. };
  80. onCredentialsChange(updated);
  81. }
  82. };
  83. const onTenantIdChange = (event: ChangeEvent<HTMLInputElement>) => {
  84. if (onCredentialsChange && credentials.authType === 'clientsecret') {
  85. setSubscriptions([]);
  86. const updated: AzureCredentials = {
  87. ...credentials,
  88. tenantId: event.target.value,
  89. defaultSubscriptionId: undefined,
  90. };
  91. onCredentialsChange(updated);
  92. }
  93. };
  94. const onClientIdChange = (event: ChangeEvent<HTMLInputElement>) => {
  95. if (onCredentialsChange && credentials.authType === 'clientsecret') {
  96. setSubscriptions([]);
  97. const updated: AzureCredentials = {
  98. ...credentials,
  99. clientId: event.target.value,
  100. defaultSubscriptionId: undefined,
  101. };
  102. onCredentialsChange(updated);
  103. }
  104. };
  105. const onClientSecretChange = (event: ChangeEvent<HTMLInputElement>) => {
  106. if (onCredentialsChange && credentials.authType === 'clientsecret') {
  107. setSubscriptions([]);
  108. const updated: AzureCredentials = {
  109. ...credentials,
  110. clientSecret: event.target.value,
  111. defaultSubscriptionId: undefined,
  112. };
  113. onCredentialsChange(updated);
  114. }
  115. };
  116. const onClientSecretReset = () => {
  117. if (onCredentialsChange && credentials.authType === 'clientsecret') {
  118. setSubscriptions([]);
  119. const updated: AzureCredentials = {
  120. ...credentials,
  121. clientSecret: '',
  122. defaultSubscriptionId: undefined,
  123. };
  124. onCredentialsChange(updated);
  125. }
  126. };
  127. const onSubscriptionChange = (selected: SelectableValue<string> | undefined) => {
  128. if (onCredentialsChange) {
  129. const updated: AzureCredentials = {
  130. ...credentials,
  131. defaultSubscriptionId: selected?.value,
  132. };
  133. onCredentialsChange(updated);
  134. }
  135. };
  136. return (
  137. <div className="gf-form-group">
  138. {props.managedIdentityEnabled && (
  139. <div className="gf-form-inline">
  140. <div className="gf-form">
  141. <InlineFormLabel className="width-12" tooltip="Choose the type of authentication to Azure services">
  142. Authentication
  143. </InlineFormLabel>
  144. <Select
  145. className="width-15"
  146. value={authTypeOptions.find((opt) => opt.value === credentials.authType)}
  147. options={authTypeOptions}
  148. onChange={onAuthTypeChange}
  149. />
  150. </div>
  151. </div>
  152. )}
  153. {credentials.authType === 'clientsecret' && (
  154. <>
  155. {azureCloudOptions && (
  156. <div className="gf-form-inline">
  157. <div className="gf-form">
  158. <InlineFormLabel className="width-12" tooltip="Choose an Azure Cloud">
  159. Azure Cloud
  160. </InlineFormLabel>
  161. <Select
  162. className="width-15"
  163. value={azureCloudOptions.find((opt) => opt.value === credentials.azureCloud)}
  164. options={azureCloudOptions}
  165. onChange={onAzureCloudChange}
  166. />
  167. </div>
  168. </div>
  169. )}
  170. <div className="gf-form-inline">
  171. <div className="gf-form">
  172. <InlineFormLabel className="width-12">Directory (tenant) ID</InlineFormLabel>
  173. <div className="width-15">
  174. <Input
  175. className="width-30"
  176. placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
  177. value={credentials.tenantId || ''}
  178. onChange={onTenantIdChange}
  179. />
  180. </div>
  181. </div>
  182. </div>
  183. <div className="gf-form-inline">
  184. <div className="gf-form">
  185. <InlineFormLabel className="width-12">Application (client) ID</InlineFormLabel>
  186. <div className="width-15">
  187. <Input
  188. className="width-30"
  189. placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
  190. value={credentials.clientId || ''}
  191. onChange={onClientIdChange}
  192. />
  193. </div>
  194. </div>
  195. </div>
  196. {typeof credentials.clientSecret === 'symbol' ? (
  197. <div className="gf-form-inline">
  198. <div className="gf-form">
  199. <InlineFormLabel className="width-12">Client Secret</InlineFormLabel>
  200. <Input className="width-25" placeholder="configured" disabled={true} />
  201. </div>
  202. <div className="gf-form">
  203. <div className="max-width-30 gf-form-inline">
  204. <Button variant="secondary" type="button" onClick={onClientSecretReset}>
  205. reset
  206. </Button>
  207. </div>
  208. </div>
  209. </div>
  210. ) : (
  211. <div className="gf-form-inline">
  212. <div className="gf-form">
  213. <InlineFormLabel className="width-12">Client Secret</InlineFormLabel>
  214. <div className="width-15">
  215. <Input
  216. className="width-30"
  217. placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
  218. value={credentials.clientSecret || ''}
  219. onChange={onClientSecretChange}
  220. />
  221. </div>
  222. </div>
  223. </div>
  224. )}
  225. </>
  226. )}
  227. {getSubscriptions && (
  228. <>
  229. <div className="gf-form-inline">
  230. <div className="gf-form">
  231. <InlineFormLabel className="width-12">Default Subscription</InlineFormLabel>
  232. <div className="width-25">
  233. <Select
  234. value={
  235. credentials.defaultSubscriptionId
  236. ? subscriptions.find((opt) => opt.value === credentials.defaultSubscriptionId)
  237. : undefined
  238. }
  239. options={subscriptions}
  240. onChange={onSubscriptionChange}
  241. />
  242. </div>
  243. </div>
  244. </div>
  245. <div className="gf-form-inline">
  246. <div className="gf-form">
  247. <div className="max-width-30 gf-form-inline">
  248. <Button
  249. variant="secondary"
  250. size="sm"
  251. type="button"
  252. onClick={onLoadSubscriptions}
  253. disabled={!hasRequiredFields}
  254. >
  255. Load Subscriptions
  256. </Button>
  257. </div>
  258. </div>
  259. </div>
  260. </>
  261. )}
  262. </div>
  263. );
  264. };
  265. export default AzureCredentialsForm;