plugin_component.ts 9.5 KB

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