DataSourceSettingsPage.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. // Libraries
  2. import React, { PureComponent } from 'react';
  3. import { hot } from 'react-hot-loader';
  4. import { connect } from 'react-redux';
  5. // Components
  6. import PageHeader from 'app/core/components/PageHeader/PageHeader';
  7. import PageLoader from 'app/core/components/PageLoader/PageLoader';
  8. import PluginSettings from './PluginSettings';
  9. import BasicSettings from './BasicSettings';
  10. import ButtonRow from './ButtonRow';
  11. // Services & Utils
  12. import appEvents from 'app/core/app_events';
  13. import { getBackendSrv } from 'app/core/services/backend_srv';
  14. import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  15. // Actions & selectors
  16. import { getDataSource, getDataSourceMeta } from '../state/selectors';
  17. import { deleteDataSource, loadDataSource, setDataSourceName, setIsDefault, updateDataSource } from '../state/actions';
  18. import { getNavModel } from 'app/core/selectors/navModel';
  19. import { getRouteParamsId } from 'app/core/selectors/location';
  20. // Types
  21. import { NavModel, Plugin } from 'app/types/';
  22. import { DataSourceSettings } from '@grafana/ui/src/types/';
  23. import { getDataSourceLoadingNav } from '../state/navModel';
  24. export interface Props {
  25. navModel: NavModel;
  26. dataSource: DataSourceSettings;
  27. dataSourceMeta: Plugin;
  28. pageId: number;
  29. deleteDataSource: typeof deleteDataSource;
  30. loadDataSource: typeof loadDataSource;
  31. setDataSourceName: typeof setDataSourceName;
  32. updateDataSource: typeof updateDataSource;
  33. setIsDefault: typeof setIsDefault;
  34. }
  35. interface State {
  36. dataSource: DataSourceSettings;
  37. isTesting?: boolean;
  38. testingMessage?: string;
  39. testingStatus?: string;
  40. }
  41. enum DataSourceStates {
  42. Alpha = 'alpha',
  43. Beta = 'beta',
  44. }
  45. export class DataSourceSettingsPage extends PureComponent<Props, State> {
  46. constructor(props) {
  47. super(props);
  48. this.state = {
  49. dataSource: {} as DataSourceSettings,
  50. };
  51. }
  52. async componentDidMount() {
  53. const { loadDataSource, pageId } = this.props;
  54. await loadDataSource(pageId);
  55. }
  56. onSubmit = async event => {
  57. event.preventDefault();
  58. await this.props.updateDataSource({ ...this.state.dataSource, name: this.props.dataSource.name });
  59. this.testDataSource();
  60. };
  61. onDelete = () => {
  62. appEvents.emit('confirm-modal', {
  63. title: 'Delete',
  64. text: 'Are you sure you want to delete this data source?',
  65. yesText: 'Delete',
  66. icon: 'fa-trash',
  67. onConfirm: () => {
  68. this.confirmDelete();
  69. },
  70. });
  71. };
  72. confirmDelete = () => {
  73. this.props.deleteDataSource();
  74. };
  75. onModelChange = dataSource => {
  76. this.setState({
  77. dataSource: dataSource,
  78. });
  79. };
  80. isReadOnly() {
  81. return this.props.dataSource.readOnly === true;
  82. }
  83. shouldRenderInfoBox() {
  84. const { state } = this.props.dataSourceMeta;
  85. return state === DataSourceStates.Alpha || state === DataSourceStates.Beta;
  86. }
  87. getInfoText() {
  88. const { dataSourceMeta } = this.props;
  89. switch (dataSourceMeta.state) {
  90. case DataSourceStates.Alpha:
  91. return (
  92. 'This plugin is marked as being in alpha state, which means it is in early development phase and updates' +
  93. ' will include breaking changes.'
  94. );
  95. case DataSourceStates.Beta:
  96. return (
  97. 'This plugin is marked as being in a beta development state. This means it is in currently in active' +
  98. ' development and could be missing important features.'
  99. );
  100. }
  101. return null;
  102. }
  103. renderIsReadOnlyMessage() {
  104. return (
  105. <div className="grafana-info-box span8">
  106. This datasource was added by config and cannot be modified using the UI. Please contact your server admin to
  107. update this datasource.
  108. </div>
  109. );
  110. }
  111. async testDataSource() {
  112. const dsApi = await getDatasourceSrv().get(this.state.dataSource.name);
  113. if (!dsApi.testDatasource) {
  114. return;
  115. }
  116. this.setState({ isTesting: true, testingMessage: 'Testing...', testingStatus: 'info' });
  117. getBackendSrv().withNoBackendCache(async () => {
  118. try {
  119. const result = await dsApi.testDatasource();
  120. this.setState({
  121. isTesting: false,
  122. testingStatus: result.status,
  123. testingMessage: result.message,
  124. });
  125. } catch (err) {
  126. let message = '';
  127. if (err.statusText) {
  128. message = 'HTTP Error ' + err.statusText;
  129. } else {
  130. message = err.message;
  131. }
  132. this.setState({
  133. isTesting: false,
  134. testingStatus: 'error',
  135. testingMessage: message,
  136. });
  137. }
  138. });
  139. }
  140. render() {
  141. const { dataSource, dataSourceMeta, navModel, setDataSourceName, setIsDefault } = this.props;
  142. const { testingMessage, testingStatus } = this.state;
  143. return (
  144. <div>
  145. <PageHeader model={navModel} />
  146. {Object.keys(dataSource).length === 0 ? (
  147. <PageLoader pageName="Data source settings" />
  148. ) : (
  149. <div className="page-container page-body">
  150. <div>
  151. <form onSubmit={this.onSubmit}>
  152. {this.isReadOnly() && this.renderIsReadOnlyMessage()}
  153. {this.shouldRenderInfoBox() && <div className="grafana-info-box">{this.getInfoText()}</div>}
  154. <BasicSettings
  155. dataSourceName={dataSource.name}
  156. isDefault={dataSource.isDefault}
  157. onDefaultChange={state => setIsDefault(state)}
  158. onNameChange={name => setDataSourceName(name)}
  159. />
  160. {dataSourceMeta.module && (
  161. <PluginSettings
  162. dataSource={dataSource}
  163. dataSourceMeta={dataSourceMeta}
  164. onModelChange={this.onModelChange}
  165. />
  166. )}
  167. <div className="gf-form-group section">
  168. {testingMessage && (
  169. <div className={`alert-${testingStatus} alert`}>
  170. <div className="alert-icon">
  171. {testingStatus === 'error' ? (
  172. <i className="fa fa-exclamation-triangle" />
  173. ) : (
  174. <i className="fa fa-check" />
  175. )}
  176. </div>
  177. <div className="alert-body">
  178. <div className="alert-title">{testingMessage}</div>
  179. </div>
  180. </div>
  181. )}
  182. </div>
  183. <ButtonRow
  184. onSubmit={event => this.onSubmit(event)}
  185. isReadOnly={this.isReadOnly()}
  186. onDelete={this.onDelete}
  187. />
  188. </form>
  189. </div>
  190. </div>
  191. )}
  192. </div>
  193. );
  194. }
  195. }
  196. function mapStateToProps(state) {
  197. const pageId = getRouteParamsId(state.location);
  198. const dataSource = getDataSource(state.dataSources, pageId);
  199. return {
  200. navModel: getNavModel(state.navIndex, `datasource-settings-${pageId}`, getDataSourceLoadingNav('settings')),
  201. dataSource: getDataSource(state.dataSources, pageId),
  202. dataSourceMeta: getDataSourceMeta(state.dataSources, dataSource.type),
  203. pageId: pageId,
  204. };
  205. }
  206. const mapDispatchToProps = {
  207. deleteDataSource,
  208. loadDataSource,
  209. setDataSourceName,
  210. updateDataSource,
  211. setIsDefault,
  212. };
  213. export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DataSourceSettingsPage));