actions.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import { ThunkAction } from 'redux-thunk';
  2. import config from '../../../core/config';
  3. import { getBackendSrv } from 'app/core/services/backend_srv';
  4. import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  5. import { LayoutMode } from 'app/core/components/LayoutSelector/LayoutSelector';
  6. import { updateLocation, updateNavIndex, UpdateNavIndexAction } from 'app/core/actions';
  7. import { UpdateLocationAction } from 'app/core/actions/location';
  8. import { buildNavModel } from './navModel';
  9. import { DataSourceSettings } from '@grafana/ui/src/types';
  10. import { Plugin, StoreState } from 'app/types';
  11. export enum ActionTypes {
  12. LoadDataSources = 'LOAD_DATA_SOURCES',
  13. LoadDataSourceTypes = 'LOAD_DATA_SOURCE_TYPES',
  14. LoadedDataSourceTypes = 'LOADED_DATA_SOURCE_TYPES',
  15. LoadDataSource = 'LOAD_DATA_SOURCE',
  16. LoadDataSourceMeta = 'LOAD_DATA_SOURCE_META',
  17. SetDataSourcesSearchQuery = 'SET_DATA_SOURCES_SEARCH_QUERY',
  18. SetDataSourcesLayoutMode = 'SET_DATA_SOURCES_LAYOUT_MODE',
  19. SetDataSourceTypeSearchQuery = 'SET_DATA_SOURCE_TYPE_SEARCH_QUERY',
  20. SetDataSourceName = 'SET_DATA_SOURCE_NAME',
  21. SetIsDefault = 'SET_IS_DEFAULT',
  22. }
  23. interface LoadDataSourcesAction {
  24. type: ActionTypes.LoadDataSources;
  25. payload: DataSourceSettings[];
  26. }
  27. interface SetDataSourcesSearchQueryAction {
  28. type: ActionTypes.SetDataSourcesSearchQuery;
  29. payload: string;
  30. }
  31. interface SetDataSourcesLayoutModeAction {
  32. type: ActionTypes.SetDataSourcesLayoutMode;
  33. payload: LayoutMode;
  34. }
  35. interface LoadDataSourceTypesAction {
  36. type: ActionTypes.LoadDataSourceTypes;
  37. }
  38. interface LoadedDataSourceTypesAction {
  39. type: ActionTypes.LoadedDataSourceTypes;
  40. payload: Plugin[];
  41. }
  42. interface SetDataSourceTypeSearchQueryAction {
  43. type: ActionTypes.SetDataSourceTypeSearchQuery;
  44. payload: string;
  45. }
  46. interface LoadDataSourceAction {
  47. type: ActionTypes.LoadDataSource;
  48. payload: DataSourceSettings;
  49. }
  50. interface LoadDataSourceMetaAction {
  51. type: ActionTypes.LoadDataSourceMeta;
  52. payload: Plugin;
  53. }
  54. interface SetDataSourceNameAction {
  55. type: ActionTypes.SetDataSourceName;
  56. payload: string;
  57. }
  58. interface SetIsDefaultAction {
  59. type: ActionTypes.SetIsDefault;
  60. payload: boolean;
  61. }
  62. const dataSourcesLoaded = (dataSources: DataSourceSettings[]): LoadDataSourcesAction => ({
  63. type: ActionTypes.LoadDataSources,
  64. payload: dataSources,
  65. });
  66. const dataSourceLoaded = (dataSource: DataSourceSettings): LoadDataSourceAction => ({
  67. type: ActionTypes.LoadDataSource,
  68. payload: dataSource,
  69. });
  70. const dataSourceMetaLoaded = (dataSourceMeta: Plugin): LoadDataSourceMetaAction => ({
  71. type: ActionTypes.LoadDataSourceMeta,
  72. payload: dataSourceMeta,
  73. });
  74. const dataSourceTypesLoad = (): LoadDataSourceTypesAction => ({
  75. type: ActionTypes.LoadDataSourceTypes,
  76. });
  77. const dataSourceTypesLoaded = (dataSourceTypes: Plugin[]): LoadedDataSourceTypesAction => ({
  78. type: ActionTypes.LoadedDataSourceTypes,
  79. payload: dataSourceTypes,
  80. });
  81. export const setDataSourcesSearchQuery = (searchQuery: string): SetDataSourcesSearchQueryAction => ({
  82. type: ActionTypes.SetDataSourcesSearchQuery,
  83. payload: searchQuery,
  84. });
  85. export const setDataSourcesLayoutMode = (layoutMode: LayoutMode): SetDataSourcesLayoutModeAction => ({
  86. type: ActionTypes.SetDataSourcesLayoutMode,
  87. payload: layoutMode,
  88. });
  89. export const setDataSourceTypeSearchQuery = (query: string): SetDataSourceTypeSearchQueryAction => ({
  90. type: ActionTypes.SetDataSourceTypeSearchQuery,
  91. payload: query,
  92. });
  93. export const setDataSourceName = (name: string) => ({
  94. type: ActionTypes.SetDataSourceName,
  95. payload: name,
  96. });
  97. export const setIsDefault = (state: boolean) => ({
  98. type: ActionTypes.SetIsDefault,
  99. payload: state,
  100. });
  101. export type Action =
  102. | LoadDataSourcesAction
  103. | SetDataSourcesSearchQueryAction
  104. | SetDataSourcesLayoutModeAction
  105. | UpdateLocationAction
  106. | LoadDataSourceTypesAction
  107. | LoadedDataSourceTypesAction
  108. | SetDataSourceTypeSearchQueryAction
  109. | LoadDataSourceAction
  110. | UpdateNavIndexAction
  111. | LoadDataSourceMetaAction
  112. | SetDataSourceNameAction
  113. | SetIsDefaultAction;
  114. type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
  115. export function loadDataSources(): ThunkResult<void> {
  116. return async dispatch => {
  117. const response = await getBackendSrv().get('/api/datasources');
  118. dispatch(dataSourcesLoaded(response));
  119. };
  120. }
  121. export function loadDataSource(id: number): ThunkResult<void> {
  122. return async dispatch => {
  123. const dataSource = await getBackendSrv().get(`/api/datasources/${id}`);
  124. const pluginInfo = await getBackendSrv().get(`/api/plugins/${dataSource.type}/settings`);
  125. dispatch(dataSourceLoaded(dataSource));
  126. dispatch(dataSourceMetaLoaded(pluginInfo));
  127. dispatch(updateNavIndex(buildNavModel(dataSource, pluginInfo)));
  128. };
  129. }
  130. export function addDataSource(plugin: Plugin): ThunkResult<void> {
  131. return async (dispatch, getStore) => {
  132. await dispatch(loadDataSources());
  133. const dataSources = getStore().dataSources.dataSources;
  134. const newInstance = {
  135. name: plugin.name,
  136. type: plugin.id,
  137. access: 'proxy',
  138. isDefault: dataSources.length === 0,
  139. };
  140. if (nameExits(dataSources, newInstance.name)) {
  141. newInstance.name = findNewName(dataSources, newInstance.name);
  142. }
  143. const result = await getBackendSrv().post('/api/datasources', newInstance);
  144. dispatch(updateLocation({ path: `/datasources/edit/${result.id}` }));
  145. };
  146. }
  147. export function loadDataSourceTypes(): ThunkResult<void> {
  148. return async dispatch => {
  149. dispatch(dataSourceTypesLoad());
  150. const result = await getBackendSrv().get('/api/plugins', { enabled: 1, type: 'datasource' });
  151. dispatch(dataSourceTypesLoaded(result));
  152. };
  153. }
  154. export function updateDataSource(dataSource: DataSourceSettings): ThunkResult<void> {
  155. return async dispatch => {
  156. await getBackendSrv().put(`/api/datasources/${dataSource.id}`, dataSource);
  157. await updateFrontendSettings();
  158. return dispatch(loadDataSource(dataSource.id));
  159. };
  160. }
  161. export function deleteDataSource(): ThunkResult<void> {
  162. return async (dispatch, getStore) => {
  163. const dataSource = getStore().dataSources.dataSource;
  164. await getBackendSrv().delete(`/api/datasources/${dataSource.id}`);
  165. dispatch(updateLocation({ path: '/datasources' }));
  166. };
  167. }
  168. export function nameExits(dataSources, name) {
  169. return (
  170. dataSources.filter(dataSource => {
  171. return dataSource.name.toLowerCase() === name.toLowerCase();
  172. }).length > 0
  173. );
  174. }
  175. export function findNewName(dataSources, name) {
  176. // Need to loop through current data sources to make sure
  177. // the name doesn't exist
  178. while (nameExits(dataSources, name)) {
  179. // If there's a duplicate name that doesn't end with '-x'
  180. // we can add -1 to the name and be done.
  181. if (!nameHasSuffix(name)) {
  182. name = `${name}-1`;
  183. } else {
  184. // if there's a duplicate name that ends with '-x'
  185. // we can try to increment the last digit until the name is unique
  186. // remove the 'x' part and replace it with the new number
  187. name = `${getNewName(name)}${incrementLastDigit(getLastDigit(name))}`;
  188. }
  189. }
  190. return name;
  191. }
  192. function updateFrontendSettings() {
  193. return getBackendSrv()
  194. .get('/api/frontend/settings')
  195. .then(settings => {
  196. config.datasources = settings.datasources;
  197. config.defaultDatasource = settings.defaultDatasource;
  198. getDatasourceSrv().init();
  199. });
  200. }
  201. function nameHasSuffix(name) {
  202. return name.endsWith('-', name.length - 1);
  203. }
  204. function getLastDigit(name) {
  205. return parseInt(name.slice(-1), 10);
  206. }
  207. function incrementLastDigit(digit) {
  208. return isNaN(digit) ? 1 : digit + 1;
  209. }
  210. function getNewName(name) {
  211. return name.slice(0, name.length - 1);
  212. }