EVOLUTION-MANAGER
Edit File: panel_directive.ts
import angular from 'angular'; import $ from 'jquery'; // @ts-ignore import Drop from 'tether-drop'; // @ts-ignore import baron from 'baron'; import { PanelEvents } from '@grafana/data'; import { getLocationSrv } from '@grafana/runtime'; import { e2e } from '@grafana/e2e'; const module = angular.module('grafana.directives'); const panelTemplate = ` <div class="panel-container" ng-class="{'panel-container--no-title': !ctrl.panel.title.length}"> <div class="panel-header" ng-class="{'grid-drag-handle': !ctrl.panel.fullscreen}"> <span class="panel-info-corner"> <i class="fa"></i> <span class="panel-info-corner-inner"></span> </span> <span class="panel-loading" ng-show="ctrl.loading"> <i class="fa fa-spinner fa-spin"></i> </span> <panel-header class="panel-title-container" panel-ctrl="ctrl" aria-label={{ctrl.selectors.title(ctrl.panel.title)}}></panel-header> </div> <div class="panel-content"> <ng-transclude class="panel-height-helper"></ng-transclude> </div> </div> </div> `; module.directive('grafanaPanel', ($rootScope, $document, $timeout) => { return { restrict: 'E', template: panelTemplate, transclude: true, scope: { ctrl: '=' }, link: (scope: any, elem) => { const panelContainer = elem.find('.panel-container'); const panelContent = elem.find('.panel-content'); const cornerInfoElem = elem.find('.panel-info-corner'); const ctrl = scope.ctrl; ctrl.selectors = e2e.pages.Dashboard.Panels.Panel.selectors; let infoDrop: any; let panelScrollbar: any; // the reason for handling these classes this way is for performance // limit the watchers on panels etc let transparentLastState = false; let lastHasAlertRule = false; let lastAlertState: boolean; let hasAlertRule; function mouseEnter() { panelContainer.toggleClass('panel-hover-highlight', true); ctrl.dashboard.setPanelFocus(ctrl.panel.id); } function mouseLeave() { panelContainer.toggleClass('panel-hover-highlight', false); ctrl.dashboard.setPanelFocus(0); } function resizeScrollableContent() { if (panelScrollbar) { panelScrollbar.update(); } } function infoCornerClicked() { if (ctrl.error) { getLocationSrv().update({ partial: true, query: { inspect: ctrl.panel.id } }); } } // set initial transparency if (ctrl.panel.transparent) { transparentLastState = true; panelContainer.addClass('panel-container--transparent'); } // update scrollbar after mounting ctrl.events.on(PanelEvents.componentDidMount, () => { if (ctrl.__proto__.constructor.scrollable) { const scrollRootClass = 'baron baron__root baron__clipper panel-content--scrollable'; const scrollerClass = 'baron__scroller'; const scrollBarHTML = ` <div class="baron__track"> <div class="baron__bar"></div> </div> `; const scrollRoot = panelContent; const scroller = panelContent.find(':first').find(':first'); scrollRoot.addClass(scrollRootClass); $(scrollBarHTML).appendTo(scrollRoot); scroller.addClass(scrollerClass); panelScrollbar = baron({ root: scrollRoot[0], scroller: scroller[0], bar: '.baron__bar', barOnCls: '_scrollbar', scrollingCls: '_scrolling', }); panelScrollbar.scroll(); } }); ctrl.events.on(PanelEvents.panelSizeChanged, () => { ctrl.calculatePanelHeight(panelContainer[0].offsetHeight); $timeout(() => { resizeScrollableContent(); ctrl.render(); }); }); ctrl.events.on(PanelEvents.viewModeChanged, () => { // first wait one pass for dashboard fullscreen view mode to take effect (classses being applied) setTimeout(() => { // then recalc style ctrl.calculatePanelHeight(panelContainer[0].offsetHeight); // then wait another cycle (this might not be needed) $timeout(() => { ctrl.render(); resizeScrollableContent(); }); }, 10); }); ctrl.events.on(PanelEvents.render, () => { // set initial height if (!ctrl.height) { ctrl.calculatePanelHeight(panelContainer[0].offsetHeight); } if (transparentLastState !== ctrl.panel.transparent) { panelContainer.toggleClass('panel-container--transparent', ctrl.panel.transparent === true); transparentLastState = ctrl.panel.transparent; } hasAlertRule = ctrl.panel.alert !== undefined; if (lastHasAlertRule !== hasAlertRule) { panelContainer.toggleClass('panel-has-alert', hasAlertRule); lastHasAlertRule = hasAlertRule; } if (ctrl.alertState) { if (lastAlertState) { panelContainer.removeClass('panel-alert-state--' + lastAlertState); } if ( ctrl.alertState.state === 'ok' || ctrl.alertState.state === 'alerting' || ctrl.alertState.state === 'pending' ) { panelContainer.addClass('panel-alert-state--' + ctrl.alertState.state); } lastAlertState = ctrl.alertState.state; } else if (lastAlertState) { panelContainer.removeClass('panel-alert-state--' + lastAlertState); lastAlertState = null; } }); function updatePanelCornerInfo() { const cornerMode = ctrl.getInfoMode(); cornerInfoElem[0].className = 'panel-info-corner panel-info-corner--' + cornerMode; if (cornerMode) { if (infoDrop) { infoDrop.destroy(); } infoDrop = new Drop({ target: cornerInfoElem[0], content: () => { return ctrl.getInfoContent({ mode: 'tooltip' }); }, classes: ctrl.error ? 'drop-error' : 'drop-help', openOn: 'hover', hoverOpenDelay: 100, tetherOptions: { attachment: 'bottom left', targetAttachment: 'top left', constraints: [ { to: 'window', attachment: 'together', pin: true, }, ], }, }); } } scope.$watchGroup(['ctrl.error', 'ctrl.panel.description'], updatePanelCornerInfo); scope.$watchCollection('ctrl.panel.links', updatePanelCornerInfo); elem.on('mouseenter', mouseEnter); elem.on('mouseleave', mouseLeave); cornerInfoElem.on('click', infoCornerClicked); scope.$on('$destroy', () => { elem.off(); cornerInfoElem.off(); if (infoDrop) { infoDrop.destroy(); } if (panelScrollbar) { panelScrollbar.dispose(); } }); }, }; }); module.directive('panelHelpCorner', $rootScope => { return { restrict: 'E', template: ` <span class="alert-error panel-error small pointer" ng-if="ctrl.error" ng-click="ctrl.openInspector()"> <span data-placement="top" bs-tooltip="ctrl.error"> <i class="fa fa-exclamation"></i><span class="panel-error-arrow"></span> </span> </span> `, link: (scope, elem) => {}, }; });