plugin_component.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import angular from 'angular';
  2. import _ from 'lodash';
  3. import config from 'app/core/config';
  4. import coreModule from 'app/core/core_module';
  5. import {importPluginModule} from './plugin_loader';
  6. import {UnknownPanelCtrl} from 'app/plugins/panel/unknown/module';
  7. import {DashboardRowCtrl} from './row_ctrl';
  8. /** @ngInject **/
  9. function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $templateCache) {
  10. function getTemplate(component) {
  11. if (component.template) {
  12. return $q.when(component.template);
  13. }
  14. var cached = $templateCache.get(component.templateUrl);
  15. if (cached) {
  16. return $q.when(cached);
  17. }
  18. return $http.get(component.templateUrl).then(res => {
  19. return res.data;
  20. });
  21. }
  22. function relativeTemplateUrlToAbs(templateUrl, baseUrl) {
  23. if (!templateUrl) { return undefined; }
  24. if (templateUrl.indexOf('public') === 0) { return templateUrl; }
  25. return baseUrl + '/' + templateUrl;
  26. }
  27. function getPluginComponentDirective(options) {
  28. // handle relative template urls for plugin templates
  29. options.Component.templateUrl = relativeTemplateUrlToAbs(options.Component.templateUrl, options.baseUrl);
  30. return function() {
  31. return {
  32. templateUrl: options.Component.templateUrl,
  33. template: options.Component.template,
  34. restrict: 'E',
  35. controller: options.Component,
  36. controllerAs: 'ctrl',
  37. bindToController: true,
  38. scope: options.bindings,
  39. link: (scope, elem, attrs, ctrl) => {
  40. if (ctrl.link) {
  41. ctrl.link(scope, elem, attrs, ctrl);
  42. }
  43. if (ctrl.init) {
  44. ctrl.init();
  45. }
  46. }
  47. };
  48. };
  49. }
  50. function loadPanelComponentInfo(scope, attrs) {
  51. if (scope.panel.type === 'row') {
  52. return $q.when({
  53. name: 'dashboard-row',
  54. bindings: {dashboard: "=", panel: "="},
  55. attrs: {dashboard: "ctrl.dashboard", panel: "panel"},
  56. Component: DashboardRowCtrl,
  57. });
  58. }
  59. var componentInfo: any = {
  60. name: 'panel-plugin-' + scope.panel.type,
  61. bindings: {dashboard: "=", panel: "=", row: "="},
  62. attrs: {dashboard: "ctrl.dashboard", panel: "panel", row: "ctrl.row"},
  63. };
  64. let panelInfo = config.panels[scope.panel.type];
  65. var panelCtrlPromise = Promise.resolve(UnknownPanelCtrl);
  66. if (panelInfo) {
  67. panelCtrlPromise = importPluginModule(panelInfo.module).then(function(panelModule) {
  68. return panelModule.PanelCtrl;
  69. });
  70. }
  71. return panelCtrlPromise.then(function(PanelCtrl: any) {
  72. componentInfo.Component = PanelCtrl;
  73. if (!PanelCtrl || PanelCtrl.registered) {
  74. return componentInfo;
  75. }
  76. if (PanelCtrl.templatePromise) {
  77. return PanelCtrl.templatePromise.then(res => {
  78. return componentInfo;
  79. });
  80. }
  81. if (panelInfo) {
  82. PanelCtrl.templateUrl = relativeTemplateUrlToAbs(PanelCtrl.templateUrl, panelInfo.baseUrl);
  83. }
  84. PanelCtrl.templatePromise = getTemplate(PanelCtrl).then(template => {
  85. PanelCtrl.templateUrl = null;
  86. PanelCtrl.template = `<grafana-panel ctrl="ctrl">${template}</grafana-panel>`;
  87. return componentInfo;
  88. });
  89. return PanelCtrl.templatePromise;
  90. });
  91. }
  92. function getModule(scope, attrs) {
  93. switch (attrs.type) {
  94. // QueryCtrl
  95. case "query-ctrl": {
  96. let datasource = scope.target.datasource || scope.ctrl.panel.datasource;
  97. return datasourceSrv.get(datasource).then(ds => {
  98. scope.datasource = ds;
  99. return importPluginModule(ds.meta.module).then(dsModule => {
  100. return {
  101. baseUrl: ds.meta.baseUrl,
  102. name: 'query-ctrl-' + ds.meta.id,
  103. bindings: {target: "=", panelCtrl: "=", datasource: "="},
  104. attrs: {"target": "target", "panel-ctrl": "ctrl.panelCtrl", datasource: "datasource"},
  105. Component: dsModule.QueryCtrl
  106. };
  107. });
  108. });
  109. }
  110. // QueryOptionsCtrl
  111. case "query-options-ctrl": {
  112. return datasourceSrv.get(scope.ctrl.panel.datasource).then(ds => {
  113. return importPluginModule(ds.meta.module).then((dsModule): any => {
  114. if (!dsModule.QueryOptionsCtrl) {
  115. return {notFound: true};
  116. }
  117. return {
  118. baseUrl: ds.meta.baseUrl,
  119. name: 'query-options-ctrl-' + ds.meta.id,
  120. bindings: {panelCtrl: "="},
  121. attrs: {"panel-ctrl": "ctrl.panelCtrl"},
  122. Component: dsModule.QueryOptionsCtrl
  123. };
  124. });
  125. });
  126. }
  127. // Annotations
  128. case "annotations-query-ctrl": {
  129. return importPluginModule(scope.ctrl.currentDatasource.meta.module).then(function(dsModule) {
  130. return {
  131. baseUrl: scope.ctrl.currentDatasource.meta.baseUrl,
  132. name: 'annotations-query-ctrl-' + scope.ctrl.currentDatasource.meta.id,
  133. bindings: {annotation: "=", datasource: "="},
  134. attrs: {"annotation": "ctrl.currentAnnotation", datasource: "ctrl.currentDatasource"},
  135. Component: dsModule.AnnotationsQueryCtrl,
  136. };
  137. });
  138. }
  139. // Datasource ConfigCtrl
  140. case 'datasource-config-ctrl': {
  141. var dsMeta = scope.ctrl.datasourceMeta;
  142. return importPluginModule(dsMeta.module).then(function(dsModule): any {
  143. if (!dsModule.ConfigCtrl) {
  144. return {notFound: true};
  145. }
  146. return {
  147. baseUrl: dsMeta.baseUrl,
  148. name: 'ds-config-' + dsMeta.id,
  149. bindings: {meta: "=", current: "="},
  150. attrs: {meta: "ctrl.datasourceMeta", current: "ctrl.current"},
  151. Component: dsModule.ConfigCtrl,
  152. };
  153. });
  154. }
  155. // AppConfigCtrl
  156. case 'app-config-ctrl': {
  157. let model = scope.ctrl.model;
  158. return importPluginModule(model.module).then(function(appModule) {
  159. return {
  160. baseUrl: model.baseUrl,
  161. name: 'app-config-' + model.id,
  162. bindings: {appModel: "=", appEditCtrl: "="},
  163. attrs: {"app-model": "ctrl.model", "app-edit-ctrl": "ctrl"},
  164. Component: appModule.ConfigCtrl,
  165. };
  166. });
  167. }
  168. // App Page
  169. case 'app-page': {
  170. let appModel = scope.ctrl.appModel;
  171. return importPluginModule(appModel.module).then(function(appModule) {
  172. return {
  173. baseUrl: appModel.baseUrl,
  174. name: 'app-page-' + appModel.id + '-' + scope.ctrl.page.slug,
  175. bindings: {appModel: "="},
  176. attrs: {"app-model": "ctrl.appModel"},
  177. Component: appModule[scope.ctrl.page.component],
  178. };
  179. });
  180. }
  181. // Panel
  182. case 'panel': {
  183. return loadPanelComponentInfo(scope, attrs);
  184. }
  185. default: {
  186. return $q.reject({message: "Could not find component type: " + attrs.type });
  187. }
  188. }
  189. }
  190. function appendAndCompile(scope, elem, componentInfo) {
  191. var child = angular.element(document.createElement(componentInfo.name));
  192. _.each(componentInfo.attrs, (value, key) => {
  193. child.attr(key, value);
  194. });
  195. $compile(child)(scope);
  196. elem.empty();
  197. // let a binding digest cycle complete before adding to dom
  198. setTimeout(function() {
  199. elem.append(child);
  200. scope.$applyAsync(function() {
  201. scope.$broadcast('refresh');
  202. });
  203. });
  204. }
  205. function registerPluginComponent(scope, elem, attrs, componentInfo) {
  206. if (componentInfo.notFound) {
  207. elem.empty();
  208. return;
  209. }
  210. if (!componentInfo.Component) {
  211. throw {message: 'Failed to find exported plugin component for ' + componentInfo.name};
  212. }
  213. if (!componentInfo.Component.registered) {
  214. var directiveName = attrs.$normalize(componentInfo.name);
  215. var directiveFn = getPluginComponentDirective(componentInfo);
  216. coreModule.directive(directiveName, directiveFn);
  217. componentInfo.Component.registered = true;
  218. }
  219. appendAndCompile(scope, elem, componentInfo);
  220. }
  221. return {
  222. restrict: 'E',
  223. link: function(scope, elem, attrs) {
  224. getModule(scope, attrs).then(function (componentInfo) {
  225. registerPluginComponent(scope, elem, attrs, componentInfo);
  226. }).catch(err => {
  227. $rootScope.appEvent('alert-error', ['Plugin Error', err.message || err]);
  228. console.log('Plugin component error', err);
  229. });
  230. }
  231. };
  232. }
  233. coreModule.directive('pluginComponent', pluginDirectiveLoader);