GrafanaCtrl.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. // Libraries
  2. import _ from 'lodash';
  3. import $ from 'jquery';
  4. // @ts-ignore
  5. import Drop from 'tether-drop';
  6. // Utils and servies
  7. import { colors } from '@grafana/ui';
  8. import { setBackendSrv, BackendSrv, setDataSourceSrv } from '@grafana/runtime';
  9. import config from 'app/core/config';
  10. import coreModule from 'app/core/core_module';
  11. import { profiler } from 'app/core/profiler';
  12. import appEvents from 'app/core/app_events';
  13. import { TimeSrv, setTimeSrv } from 'app/features/dashboard/services/TimeSrv';
  14. import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
  15. import { KeybindingSrv, setKeybindingSrv } from 'app/core/services/keybindingSrv';
  16. import { AngularLoader, setAngularLoader } from 'app/core/services/AngularLoader';
  17. import { configureStore } from 'app/store/configureStore';
  18. import { LocationUpdate, setLocationSrv } from '@grafana/runtime';
  19. import { updateLocation } from 'app/core/actions';
  20. // Types
  21. import { KioskUrlValue } from 'app/types';
  22. import { setLinkSrv, LinkSrv } from 'app/features/panel/panellinks/link_srv';
  23. import { UtilSrv } from 'app/core/services/util_srv';
  24. import { ContextSrv } from 'app/core/services/context_srv';
  25. import { BridgeSrv } from 'app/core/services/bridge_srv';
  26. import { PlaylistSrv } from 'app/features/playlist/playlist_srv';
  27. import { ILocationService, ITimeoutService, IRootScopeService } from 'angular';
  28. export class GrafanaCtrl {
  29. /** @ngInject */
  30. constructor(
  31. $scope: any,
  32. utilSrv: UtilSrv,
  33. $rootScope: any,
  34. contextSrv: ContextSrv,
  35. bridgeSrv: BridgeSrv,
  36. backendSrv: BackendSrv,
  37. timeSrv: TimeSrv,
  38. linkSrv: LinkSrv,
  39. datasourceSrv: DatasourceSrv,
  40. keybindingSrv: KeybindingSrv,
  41. angularLoader: AngularLoader
  42. ) {
  43. // make angular loader service available to react components
  44. setAngularLoader(angularLoader);
  45. setBackendSrv(backendSrv);
  46. setDataSourceSrv(datasourceSrv);
  47. setTimeSrv(timeSrv);
  48. setLinkSrv(linkSrv);
  49. setKeybindingSrv(keybindingSrv);
  50. const store = configureStore();
  51. setLocationSrv({
  52. update: (opt: LocationUpdate) => {
  53. store.dispatch(updateLocation(opt));
  54. },
  55. });
  56. $scope.init = () => {
  57. $scope.contextSrv = contextSrv;
  58. $scope.appSubUrl = config.appSubUrl;
  59. $scope._ = _;
  60. profiler.init(config, $rootScope);
  61. utilSrv.init();
  62. bridgeSrv.init();
  63. };
  64. $rootScope.colors = colors;
  65. $rootScope.onAppEvent = function(name: string, callback: () => void, localScope: any) {
  66. const unbind = $rootScope.$on(name, callback);
  67. let callerScope = this;
  68. if (callerScope.$id === 1 && !localScope) {
  69. console.log('warning rootScope onAppEvent called without localscope');
  70. }
  71. if (localScope) {
  72. callerScope = localScope;
  73. }
  74. callerScope.$on('$destroy', unbind);
  75. };
  76. $rootScope.appEvent = (name: string, payload: any) => {
  77. $rootScope.$emit(name, payload);
  78. appEvents.emit(name, payload);
  79. };
  80. $scope.init();
  81. }
  82. }
  83. function setViewModeBodyClass(body: JQuery, mode: KioskUrlValue) {
  84. body.removeClass('view-mode--tv');
  85. body.removeClass('view-mode--kiosk');
  86. body.removeClass('view-mode--inactive');
  87. switch (mode) {
  88. case 'tv': {
  89. body.addClass('view-mode--tv');
  90. break;
  91. }
  92. // 1 & true for legacy states
  93. case '1':
  94. case true: {
  95. body.addClass('view-mode--kiosk');
  96. break;
  97. }
  98. }
  99. }
  100. /** @ngInject */
  101. export function grafanaAppDirective(
  102. playlistSrv: PlaylistSrv,
  103. contextSrv: ContextSrv,
  104. $timeout: ITimeoutService,
  105. $rootScope: IRootScopeService,
  106. $location: ILocationService
  107. ) {
  108. return {
  109. restrict: 'E',
  110. controller: GrafanaCtrl,
  111. link: (scope: any, elem: JQuery) => {
  112. const body = $('body');
  113. // see https://github.com/zenorocha/clipboard.js/issues/155
  114. $.fn.modal.Constructor.prototype.enforceFocus = () => {};
  115. $('.preloader').remove();
  116. appEvents.on('toggle-sidemenu-mobile', () => {
  117. body.toggleClass('sidemenu-open--xs');
  118. });
  119. appEvents.on('toggle-sidemenu-hidden', () => {
  120. body.toggleClass('sidemenu-hidden');
  121. });
  122. appEvents.on('playlist-started', () => {
  123. elem.toggleClass('view-mode--playlist', true);
  124. });
  125. appEvents.on('playlist-stopped', () => {
  126. elem.toggleClass('view-mode--playlist', false);
  127. });
  128. // check if we are in server side render
  129. if (document.cookie.indexOf('renderKey') !== -1) {
  130. body.addClass('body--phantomjs');
  131. }
  132. // tooltip removal fix
  133. // manage page classes
  134. let pageClass: string;
  135. scope.$on('$routeChangeSuccess', (evt: any, data: any) => {
  136. if (pageClass) {
  137. body.removeClass(pageClass);
  138. }
  139. if (data.$$route) {
  140. pageClass = data.$$route.pageClass;
  141. if (pageClass) {
  142. body.addClass(pageClass);
  143. }
  144. }
  145. // clear body class sidemenu states
  146. body.removeClass('sidemenu-open--xs');
  147. $('#tooltip, .tooltip').remove();
  148. // check for kiosk url param
  149. setViewModeBodyClass(body, data.params.kiosk);
  150. // close all drops
  151. for (const drop of Drop.drops) {
  152. drop.destroy();
  153. }
  154. appEvents.emit('hide-dash-search');
  155. });
  156. // handle kiosk mode
  157. appEvents.on('toggle-kiosk-mode', (options: { exit?: boolean }) => {
  158. const search: { kiosk?: KioskUrlValue } = $location.search();
  159. if (options && options.exit) {
  160. search.kiosk = '1';
  161. }
  162. switch (search.kiosk) {
  163. case 'tv': {
  164. search.kiosk = true;
  165. appEvents.emit('alert-success', ['Press ESC to exit Kiosk mode']);
  166. break;
  167. }
  168. case '1':
  169. case true: {
  170. delete search.kiosk;
  171. break;
  172. }
  173. default: {
  174. search.kiosk = 'tv';
  175. }
  176. }
  177. $timeout(() => $location.search(search));
  178. setViewModeBodyClass(body, search.kiosk!);
  179. });
  180. // handle in active view state class
  181. let lastActivity = new Date().getTime();
  182. let activeUser = true;
  183. const inActiveTimeLimit = 60 * 5000;
  184. function checkForInActiveUser() {
  185. if (!activeUser) {
  186. return;
  187. }
  188. // only go to activity low mode on dashboard page
  189. if (!body.hasClass('page-dashboard')) {
  190. return;
  191. }
  192. if (new Date().getTime() - lastActivity > inActiveTimeLimit) {
  193. activeUser = false;
  194. body.addClass('view-mode--inactive');
  195. }
  196. }
  197. function userActivityDetected() {
  198. lastActivity = new Date().getTime();
  199. if (!activeUser) {
  200. activeUser = true;
  201. body.removeClass('view-mode--inactive');
  202. }
  203. }
  204. // mouse and keyboard is user activity
  205. body.mousemove(userActivityDetected);
  206. body.keydown(userActivityDetected);
  207. // set useCapture = true to catch event here
  208. document.addEventListener('wheel', userActivityDetected, { capture: true, passive: true });
  209. // treat tab change as activity
  210. document.addEventListener('visibilitychange', userActivityDetected);
  211. // check every 2 seconds
  212. setInterval(checkForInActiveUser, 2000);
  213. appEvents.on('toggle-view-mode', () => {
  214. lastActivity = 0;
  215. checkForInActiveUser();
  216. });
  217. // handle document clicks that should hide things
  218. body.click(evt => {
  219. const target = $(evt.target);
  220. if (target.parents().length === 0) {
  221. return;
  222. }
  223. // ensure dropdown menu doesn't impact on z-index
  224. body.find('.dropdown-menu-open').removeClass('dropdown-menu-open');
  225. // for stuff that animates, slides out etc, clicking it needs to
  226. // hide it right away
  227. const clickAutoHide = target.closest('[data-click-hide]');
  228. if (clickAutoHide.length) {
  229. const clickAutoHideParent = clickAutoHide.parent();
  230. clickAutoHide.detach();
  231. setTimeout(() => {
  232. clickAutoHideParent.append(clickAutoHide);
  233. }, 100);
  234. }
  235. // hide search
  236. if (body.find('.search-container').length > 0) {
  237. if (target.parents('.search-results-container, .search-field-wrapper').length === 0) {
  238. scope.$apply(() => {
  239. scope.appEvent('hide-dash-search');
  240. });
  241. }
  242. }
  243. // hide popovers
  244. const popover = elem.find('.popover');
  245. if (popover.length > 0 && target.parents('.graph-legend').length === 0) {
  246. popover.hide();
  247. }
  248. });
  249. },
  250. };
  251. }
  252. coreModule.directive('grafanaApp', grafanaAppDirective);