merge sync with master

This commit is contained in:
Torkel Ödegaard 2016-01-31 19:51:23 +01:00
commit 3f945e886b
160 changed files with 2278 additions and 233742 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ awsconfig
/dist
/emails/dist
/public_gen
/public/vendor/npm
/tmp
vendor/phantomjs/phantomjs

View File

@ -59,7 +59,8 @@
},
"scripts": {
"test": "grunt test",
"coveralls": "grunt karma:coveralls && rm -rf ./coverage"
"coveralls": "grunt karma:coveralls && rm -rf ./coverage",
"postinstall": "grunt copy:node_modules"
},
"license": "Apache-2.0",
"dependencies": {

View File

@ -123,6 +123,8 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
panels[panel.Id] = map[string]interface{}{
"module": panel.Module,
"name": panel.Name,
"id": panel.Id,
"info": panel.Info,
}
}

View File

@ -5,7 +5,7 @@ import store from 'app/core/store';
import _ from 'lodash';
import angular from 'angular';
import $ from 'jquery';
import coreModule from '../core_module';
import coreModule from 'app/core/core_module';
export class GrafanaCtrl {

View File

@ -59,11 +59,14 @@ coreModule.filter('noXml', function() {
coreModule.filter('interpolateTemplateVars', function (templateSrv) {
var filterFunc: any = function(text, scope) {
if (scope.panel) {
return templateSrv.replaceWithText(text, scope.panel.scopedVars);
var scopedVars;
if (scope.ctrl && scope.ctrl.panel) {
scopedVars = scope.ctrl.panel.scopedVars;
} else {
return templateSrv.replaceWithText(text, scope.row.scopedVars);
scopedVars = scope.row.scopedVars;
}
return templateSrv.replaceWithText(text, scopedVars);
};
filterFunc.$stateful = true;

View File

@ -116,36 +116,7 @@ function (angular, _, config) {
$scope.$broadcast('render');
};
$scope.removePanel = function(panel) {
$scope.appEvent('confirm-modal', {
title: 'Are you sure you want to remove this panel?',
icon: 'fa-trash',
yesText: 'Delete',
onConfirm: function() {
$scope.row.panels = _.without($scope.row.panels, panel);
}
});
};
$scope.updatePanelSpan = function(panel, span) {
panel.span = Math.min(Math.max(Math.floor(panel.span + span), 1), 12);
};
$scope.replacePanel = function(newPanel, oldPanel) {
var row = $scope.row;
var index = _.indexOf(row.panels, oldPanel);
row.panels.splice(index, 1);
// adding it back needs to be done in next digest
$timeout(function() {
newPanel.id = oldPanel.id;
newPanel.span = oldPanel.span;
row.panels.splice(index, 0, newPanel);
});
};
$scope.init();
});
module.directive('rowHeight', function() {

View File

@ -26,7 +26,7 @@ function (angular, _, require, config) {
$scope.modalTitle = 'Share Dashboard';
}
if (!$scope.dashboardMeta.isSnapshot) {
if (!$scope.dashboard.meta.isSnapshot) {
$scope.tabs.push({title: 'Snapshot sharing', src: 'shareSnapshot.html'});
}

View File

@ -17,6 +17,7 @@ function (angular, _, $) {
self.state = {};
self.panelScopes = [];
self.$scope = $scope;
self.dashboard = $scope.dashboard;
$scope.exitFullscreen = function() {
if (self.state.fullscreen) {
@ -31,6 +32,14 @@ function (angular, _, $) {
}
});
$scope.onAppEvent('panel-change-view', function(evt, payload) {
self.update(payload);
});
$scope.onAppEvent('panel-instantiated', function(evt, payload) {
self.registerPanel(payload.scope);
});
this.update(this.getQueryStringState(), true);
this.expandRowForPanel();
}
@ -66,7 +75,7 @@ function (angular, _, $) {
DashboardViewState.prototype.update = function(state, skipUrlSync) {
_.extend(this.state, state);
this.fullscreen = this.state.fullscreen;
this.dashboard.meta.fullscreen = this.state.fullscreen;
if (!this.state.fullscreen) {
this.state.panelId = null;
@ -84,7 +93,7 @@ function (angular, _, $) {
DashboardViewState.prototype.syncState = function() {
if (this.panelScopes.length === 0) { return; }
if (this.fullscreen) {
if (this.dashboard.meta.fullscreen) {
if (this.fullscreenPanel) {
this.leaveFullscreen(false);
}
@ -105,23 +114,24 @@ function (angular, _, $) {
DashboardViewState.prototype.getPanelScope = function(id) {
return _.find(this.panelScopes, function(panelScope) {
return panelScope.panel.id === id;
return panelScope.ctrl.panel.id === id;
});
};
DashboardViewState.prototype.leaveFullscreen = function(render) {
var self = this;
var ctrl = self.fullscreenPanel.ctrl;
self.fullscreenPanel.editMode = false;
self.fullscreenPanel.fullscreen = false;
delete self.fullscreenPanel.height;
ctrl.editMode = false;
ctrl.fullscreen = false;
delete ctrl.height;
this.$scope.appEvent('panel-fullscreen-exit', {panelId: this.fullscreenPanel.panel.id});
this.$scope.appEvent('panel-fullscreen-exit', {panelId: ctrl.panel.id});
if (!render) { return false;}
$timeout(function() {
if (self.oldTimeRange !== self.fullscreenPanel.range) {
if (self.oldTimeRange !== ctrl.range) {
self.$scope.broadcastRefresh();
}
else {
@ -135,17 +145,18 @@ function (angular, _, $) {
var docHeight = $(window).height();
var editHeight = Math.floor(docHeight * 0.3);
var fullscreenHeight = Math.floor(docHeight * 0.7);
var ctrl = panelScope.ctrl;
panelScope.editMode = this.state.edit && this.$scope.dashboardMeta.canEdit;
panelScope.height = panelScope.editMode ? editHeight : fullscreenHeight;
ctrl.editMode = this.state.edit && this.$scope.dashboardMeta.canEdit;
ctrl.height = ctrl.editMode ? editHeight : fullscreenHeight;
ctrl.fullscreen = true;
this.oldTimeRange = panelScope.range;
this.oldTimeRange = ctrl.range;
this.fullscreenPanel = panelScope;
$(window).scrollTop(0);
panelScope.fullscreen = true;
this.$scope.appEvent('panel-fullscreen-enter', {panelId: panelScope.panel.id});
this.$scope.appEvent('panel-fullscreen-enter', {panelId: ctrl.panel.id});
$timeout(function() {
panelScope.$broadcast('render');
@ -156,8 +167,12 @@ function (angular, _, $) {
var self = this;
self.panelScopes.push(panelScope);
if (self.state.panelId === panelScope.panel.id) {
self.enterFullscreen(panelScope);
if (self.state.panelId === panelScope.ctrl.panel.id) {
if (self.state.edit) {
panelScope.ctrl.editPanel();
} else {
panelScope.ctrl.viewPanel();
}
}
panelScope.$on('$destroy', function() {

View File

@ -6,4 +6,5 @@ define([
'./solo_panel_ctrl',
'./panel_loader',
'./query_editor',
'./panel_editor_tab',
], function () {});

View File

@ -0,0 +1,234 @@
///<reference path="../../headers/common.d.ts" />
import config from 'app/core/config';
import $ from 'jquery';
import _ from 'lodash';
import kbn from 'app/core/utils/kbn';
import {PanelCtrl} from './panel_ctrl';
import * as rangeUtil from '../../core/utils/rangeutil';
import * as dateMath from '../../core/utils/datemath';
class MetricsPanelCtrl extends PanelCtrl {
error: boolean;
loading: boolean;
datasource: any;
$q: any;
$timeout: any;
datasourceSrv: any;
timeSrv: any;
timing: any;
range: any;
rangeRaw: any;
interval: any;
resolution: any;
timeInfo: any;
skipDataOnInit: boolean;
datasources: any[];
constructor($scope, $injector) {
super($scope, $injector);
// make metrics tab the default
this.editorTabIndex = 1;
this.$q = $injector.get('$q');
this.datasourceSrv = $injector.get('datasourceSrv');
this.timeSrv = $injector.get('timeSrv');
if (!this.panel.targets) {
this.panel.targets = [{}];
}
// hookup initial data fetch
this.$timeout(() => {
if (!this.skipDataOnInit) {
this.refresh();
}
}, 30);;
}
initEditMode() {
this.addEditorTab('Metrics', 'public/app/partials/metrics.html');
this.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
this.datasources = this.datasourceSrv.getMetricSources();
}
refresh() {
this.getData();
}
refreshData(data) {
// null op
return this.$q.when(data);
}
loadSnapshot(data) {
// null op
return data;
}
getData() {
// ignore fetching data if another panel is in fullscreen
if (this.otherPanelInFullscreenMode()) { return; }
// if we have snapshot data use that
if (this.panel.snapshotData) {
if (this.loadSnapshot) {
this.loadSnapshot(this.panel.snapshotData);
}
return;
}
// clear loading/error state
delete this.error;
this.loading = true;
// load datasource service
this.datasourceSrv.get(this.panel.datasource).then(datasource => {
this.datasource = datasource;
return this.refreshData(this.datasource);
}).then(() => {
this.loading = false;
}).catch(err => {
console.log('Panel data error:', err);
this.loading = false;
this.error = err.message || "Timeseries data request error";
this.inspector = {error: err};
});
}
setTimeQueryStart() {
this.timing = {};
this.timing.queryStart = new Date().getTime();
}
setTimeQueryEnd() {
this.timing.queryEnd = new Date().getTime();
}
updateTimeRange() {
this.range = this.timeSrv.timeRange();
this.rangeRaw = this.timeSrv.timeRange(false);
this.applyPanelTimeOverrides();
if (this.panel.maxDataPoints) {
this.resolution = this.panel.maxDataPoints;
} else {
this.resolution = Math.ceil($(window).width() * (this.panel.span / 12));
}
var panelInterval = this.panel.interval;
var datasourceInterval = (this.datasource || {}).interval;
this.interval = kbn.calculateInterval(this.range, this.resolution, panelInterval || datasourceInterval);
};
applyPanelTimeOverrides() {
this.timeInfo = '';
// check panel time overrrides
if (this.panel.timeFrom) {
var timeFromInfo = rangeUtil.describeTextRange(this.panel.timeFrom);
if (timeFromInfo.invalid) {
this.timeInfo = 'invalid time override';
return;
}
if (_.isString(this.rangeRaw.from)) {
var timeFromDate = dateMath.parse(timeFromInfo.from);
this.timeInfo = timeFromInfo.display;
this.rangeRaw.from = timeFromInfo.from;
this.rangeRaw.to = timeFromInfo.to;
this.range.from = timeFromDate;
}
}
if (this.panel.timeShift) {
var timeShiftInfo = rangeUtil.describeTextRange(this.panel.timeShift);
if (timeShiftInfo.invalid) {
this.timeInfo = 'invalid timeshift';
return;
}
var timeShift = '-' + this.panel.timeShift;
this.timeInfo += ' timeshift ' + timeShift;
this.range.from = dateMath.parseDateMath(timeShift, this.range.from, false);
this.range.to = dateMath.parseDateMath(timeShift, this.range.to, true);
this.rangeRaw = this.range;
}
if (this.panel.hideTimeOverride) {
this.timeInfo = '';
}
};
issueQueries(datasource) {
if (!this.panel.targets || this.panel.targets.length === 0) {
return this.$q.when([]);
}
this.updateTimeRange();
var metricsQuery = {
range: this.range,
rangeRaw: this.rangeRaw,
interval: this.interval,
targets: this.panel.targets,
format: this.panel.renderer === 'png' ? 'png' : 'json',
maxDataPoints: this.resolution,
scopedVars: this.panel.scopedVars,
cacheTimeout: this.panel.cacheTimeout
};
this.setTimeQueryStart();
return datasource.query(metricsQuery).then(results => {
this.setTimeQueryEnd();
if (this.dashboard.snapshot) {
this.panel.snapshotData = results;
}
return results;
});
}
addDataQuery(datasource) {
this.dashboard.addDataQueryTo(this.panel, datasource);
}
removeDataQuery(query) {
this.dashboard.removeDataQuery(this.panel, query);
this.refresh();
};
duplicateDataQuery(query) {
this.dashboard.duplicateDataQuery(this.panel, query);
}
moveDataQuery(fromIndex, toIndex) {
this.dashboard.moveDataQuery(this.panel, fromIndex, toIndex);
}
setDatasource(datasource) {
// switching to mixed
if (datasource.meta.mixed) {
_.each(this.panel.targets, target => {
target.datasource = this.panel.datasource;
if (target.datasource === null) {
target.datasource = config.defaultDatasource;
}
});
} else if (this.datasource && this.datasource.meta.mixed) {
_.each(this.panel.targets, target => {
delete target.datasource;
});
}
this.panel.datasource = datasource.value;
this.datasource = null;
this.refresh();
}
}
export {MetricsPanelCtrl};

View File

@ -0,0 +1,50 @@
///<reference path="../../headers/common.d.ts" />
import config from 'app/core/config';
import {PanelCtrl} from './panel_ctrl';
import {MetricsPanelCtrl} from './metrics_panel_ctrl';
export class DefaultPanelCtrl extends PanelCtrl {
constructor($scope, $injector) {
super($scope, $injector);
}
}
class PanelDirective {
template: string;
templateUrl: string;
bindToController: boolean;
scope: any;
controller: any;
controllerAs: string;
getDirective() {
if (!this.controller) {
this.controller = DefaultPanelCtrl;
}
return {
template: this.template,
templateUrl: this.templateUrl,
controller: this.controller,
controllerAs: 'ctrl',
bindToController: true,
scope: {dashboard: "=", panel: "=", row: "="},
link: (scope, elem, attrs, ctrl) => {
ctrl.init();
this.link(scope, elem, attrs, ctrl);
}
};
}
link(scope, elem, attrs, ctrl) {
return null;
}
}
export {
PanelCtrl,
MetricsPanelCtrl,
PanelDirective,
}

View File

@ -0,0 +1,179 @@
///<reference path="../../headers/common.d.ts" />
import config from 'app/core/config';
import _ from 'lodash';
export class PanelCtrl {
panel: any;
row: any;
dashboard: any;
editorTabIndex: number;
pluginName: string;
pluginId: string;
icon: string;
editorTabs: any;
$scope: any;
$injector: any;
$timeout: any;
fullscreen: boolean;
inspector: any;
editModeInitiated: boolean;
editorHelpIndex: number;
constructor($scope, $injector) {
this.$injector = $injector;
this.$scope = $scope;
this.$timeout = $injector.get('$timeout');
this.editorTabIndex = 0;
var plugin = config.panels[this.panel.type];
if (plugin) {
this.pluginId = plugin.id;
this.pluginName = plugin.name;
}
$scope.$on("refresh", () => this.refresh());
}
init() {
this.publishAppEvent('panel-instantiated', {scope: this.$scope});
this.refresh();
}
renderingCompleted() {
this.$scope.$root.performance.panelsRendered++;
}
refresh() {
return;
}
publishAppEvent(evtName, evt) {
this.$scope.$root.appEvent(evtName, evt);
}
changeView(fullscreen, edit) {
this.publishAppEvent('panel-change-view', {
fullscreen: fullscreen, edit: edit, panelId: this.panel.id
});
}
viewPanel() {
this.changeView(true, false);
}
editPanel() {
if (!this.editModeInitiated) {
this.editorTabs = [];
this.addEditorTab('General', 'public/app/partials/panelgeneral.html');
this.initEditMode();
}
this.changeView(true, true);
}
exitFullscreen() {
this.changeView(false, false);
}
initEditMode() {
return;
}
addEditorTab(title, directiveFn, index?) {
var editorTab = {title, directiveFn};
if (_.isString(directiveFn)) {
editorTab.directiveFn = function() {
return {templateUrl: directiveFn};
};
}
if (index) {
this.editorTabs.splice(index, 0, editorTab);
} else {
this.editorTabs.push(editorTab);
}
}
getMenu() {
let menu = [];
menu.push({text: 'View', click: 'ctrl.viewPanel(); dismiss();'});
menu.push({text: 'Edit', click: 'ctrl.editPanel(); dismiss();', role: 'Editor'});
menu.push({text: 'Duplicate', click: 'ctrl.duplicate()', role: 'Editor' });
menu.push({text: 'Share', click: 'ctrl.sharePanel(); dismiss();'});
return menu;
}
getExtendedMenu() {
return [{text: 'Panel JSON', click: 'ctrl.editPanelJson(); dismiss();'}];
}
otherPanelInFullscreenMode() {
return this.dashboard.meta.fullscreen && !this.fullscreen;
}
broadcastRender(arg1?, arg2?) {
this.$scope.$broadcast('render', arg1, arg2);
}
toggleEditorHelp(index) {
if (this.editorHelpIndex === index) {
this.editorHelpIndex = null;
return;
}
this.editorHelpIndex = index;
}
duplicate() {
this.dashboard.duplicatePanel(this.panel, this.row);
}
updateColumnSpan(span) {
this.panel.span = Math.min(Math.max(Math.floor(this.panel.span + span), 1), 12);
this.$timeout(() => {
this.broadcastRender();
});
}
removePanel() {
this.publishAppEvent('confirm-modal', {
title: 'Are you sure you want to remove this panel?',
icon: 'fa-trash',
yesText: 'Delete',
onConfirm: () => {
this.row.panels = _.without(this.row.panels, this.panel);
}
});
}
editPanelJson() {
this.publishAppEvent('show-json-editor', {
object: this.panel,
updateHandler: this.replacePanel.bind(this)
});
}
replacePanel(newPanel, oldPanel) {
var row = this.row;
var index = _.indexOf(this.row.panels, oldPanel);
this.row.panels.splice(index, 1);
// adding it back needs to be done in next digest
this.$timeout(() => {
newPanel.id = oldPanel.id;
newPanel.span = oldPanel.span;
this.row.panels.splice(index, 0, newPanel);
});
}
sharePanel() {
var shareScope = this.$scope.$new();
shareScope.panel = this.panel;
shareScope.dashboard = this.dashboard;
this.publishAppEvent('show-modal', {
src: './app/features/dashboard/partials/shareModal.html',
scope: shareScope
});
}
}

View File

@ -12,12 +12,13 @@ function (angular, $) {
restrict: 'E',
templateUrl: 'app/features/panel/partials/panel.html',
transclude: true,
scope: { ctrl: "=" },
link: function(scope, elem) {
var panelContainer = elem.find('.panel-container');
scope.$watchGroup(['fullscreen', 'height', 'panel.height', 'row.height'], function() {
panelContainer.css({ minHeight: scope.height || scope.panel.height || scope.row.height, display: 'block' });
elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false);
var ctrl = scope.ctrl;
scope.$watchGroup(['ctrl.fullscreen', 'ctrl.height', 'ctrl.panel.height', 'ctrl.row.height'], function() {
panelContainer.css({ minHeight: ctrl.height || ctrl.panel.height || ctrl.row.height, display: 'block' });
elem.toggleClass('panel-fullscreen', ctrl.fullscreen ? true : false);
});
}
};
@ -30,6 +31,7 @@ function (angular, $) {
link: function(scope, elem) {
var resizing = false;
var lastPanel = false;
var ctrl = scope.ctrl;
var handleOffset;
var originalHeight;
var originalWidth;
@ -40,31 +42,31 @@ function (angular, $) {
resizing = true;
handleOffset = $(e.target).offset();
originalHeight = parseInt(scope.row.height);
originalWidth = scope.panel.span;
originalHeight = parseInt(ctrl.row.height);
originalWidth = ctrl.panel.span;
maxWidth = $(document).width();
lastPanel = scope.row.panels[scope.row.panels.length - 1];
lastPanel = ctrl.row.panels[ctrl.row.panels.length - 1];
$('body').on('mousemove', moveHandler);
$('body').on('mouseup', dragEndHandler);
}
function moveHandler(e) {
scope.row.height = originalHeight + (e.pageY - handleOffset.top);
scope.panel.span = originalWidth + (((e.pageX - handleOffset.left) / maxWidth) * 12);
scope.panel.span = Math.min(Math.max(scope.panel.span, 1), 12);
ctrl.row.height = originalHeight + (e.pageY - handleOffset.top);
ctrl.panel.span = originalWidth + (((e.pageX - handleOffset.left) / maxWidth) * 12);
ctrl.panel.span = Math.min(Math.max(ctrl.panel.span, 1), 12);
var rowSpan = scope.dashboard.rowSpan(scope.row);
var rowSpan = ctrl.dashboard.rowSpan(ctrl.row);
// auto adjust other panels
if (Math.floor(rowSpan) < 14) {
// last panel should not push row down
if (lastPanel === scope.panel && rowSpan > 12) {
if (lastPanel === ctrl.panel && rowSpan > 12) {
lastPanel.span -= rowSpan - 12;
}
// reduce width of last panel so total in row is 12
else if (lastPanel !== scope.panel) {
else if (lastPanel !== ctrl.panel) {
lastPanel.span = lastPanel.span - (rowSpan - 12);
lastPanel.span = Math.min(Math.max(lastPanel.span, 1), 12);
}
@ -77,7 +79,7 @@ function (angular, $) {
function dragEndHandler() {
// if close to 12
var rowSpan = scope.dashboard.rowSpan(scope.row);
var rowSpan = ctrl.dashboard.rowSpan(ctrl.row);
if (rowSpan < 12 && rowSpan > 11) {
lastPanel.span += 12 - rowSpan;
}

View File

@ -0,0 +1,28 @@
///<reference path="../../headers/common.d.ts" />
import angular from 'angular';
import config from 'app/core/config';
var directiveModule = angular.module('grafana.directives');
/** @ngInject */
function panelEditorTab(dynamicDirectiveSrv) {
return dynamicDirectiveSrv.create({
scope: {
ctrl: "=",
editorTab: "=",
index: "=",
},
directive: scope => {
var pluginId = scope.ctrl.pluginId;
var tabIndex = scope.index;
return Promise.resolve({
name: `panel-editor-tab-${pluginId}${tabIndex}`,
fn: scope.editorTab.directiveFn,
});
}
});
}
directiveModule.directive('panelEditorTab', panelEditorTab);

View File

@ -3,28 +3,82 @@
import angular from 'angular';
import config from 'app/core/config';
import {unknownPanelDirective} from '../../plugins/panel/unknown/module';
import {UnknownPanel} from '../../plugins/panel/unknown/module';
var directiveModule = angular.module('grafana.directives');
/** @ngInject */
function panelLoader($parse, dynamicDirectiveSrv) {
return dynamicDirectiveSrv.create({
directive: scope => {
let panelInfo = config.panels[scope.panel.type];
if (!panelInfo) {
return Promise.resolve({
name: 'panel-directive-' + scope.panel.type,
fn: unknownPanelDirective
function panelLoader($compile, dynamicDirectiveSrv, $http, $q, $injector) {
return {
restrict: 'E',
scope: {
dashboard: "=",
row: "=",
panel: "="
},
link: function(scope, elem, attrs) {
function getTemplate(directive) {
if (directive.template) {
return $q.when(directive.template);
}
return $http.get(directive.templateUrl).then(res => {
return res.data;
});
}
return System.import(panelInfo.module).then(function(panelModule) {
return {
name: 'panel-directive-' + scope.panel.type,
fn: panelModule.panel,
};
function addPanelAndCompile(name) {
var child = angular.element(document.createElement(name));
child.attr('dashboard', 'dashboard');
child.attr('panel', 'panel');
child.attr('row', 'row');
$compile(child)(scope);
elem.empty();
elem.append(child);
}
function addPanel(name, Panel) {
if (Panel.registered) {
addPanelAndCompile(name);
return;
}
if (Panel.promise) {
Panel.promise.then(() => {
addPanelAndCompile(name);
});
return;
}
var panelInstance = $injector.instantiate(Panel);
var directive = panelInstance.getDirective();
Panel.promise = getTemplate(directive).then(template => {
directive.templateUrl = null;
directive.template = `<grafana-panel ctrl="ctrl">${template}</grafana-panel>`;
directiveModule.directive(attrs.$normalize(name), function() {
return directive;
});
Panel.registered = true;
addPanelAndCompile(name);
});
}
var panelElemName = 'panel-directive-' + scope.panel.type;
let panelInfo = config.panels[scope.panel.type];
if (!panelInfo) {
addPanel(panelElemName, UnknownPanel);
return;
}
System.import(panelInfo.module).then(function(panelModule) {
addPanel(panelElemName, panelModule.Panel);
}).catch(err => {
console.log('Panel err: ', err);
});
},
});
}
};
}
angular.module('grafana.directives').directive('panelLoader', panelLoader);
directiveModule.directive('panelLoader', panelLoader);

View File

@ -11,32 +11,32 @@ function (angular, $, _) {
.directive('panelMenu', function($compile, linkSrv) {
var linkTemplate =
'<span class="panel-title drag-handle pointer">' +
'<span class="panel-title-text drag-handle">{{panel.title | interpolateTemplateVars:this}}</span>' +
'<span class="panel-links-btn"><i class="fa fa-external-link"></i></span>' +
'<span class="panel-time-info" ng-show="panelMeta.timeInfo"><i class="fa fa-clock-o"></i> {{panelMeta.timeInfo}}</span>' +
'<span class="panel-title-text drag-handle">{{ctrl.panel.title | interpolateTemplateVars:this}}</span>' +
'<span class="panel-links-btn"><i class="fa fa-link"></i></span>' +
'<span class="panel-time-info" ng-show="ctrl.timeInfo"><i class="fa fa-clock-o"></i> {{ctrl.timeInfo}}</span>' +
'</span>';
function createExternalLinkMenu($scope) {
function createExternalLinkMenu(ctrl) {
var template = '<div class="panel-menu small">';
template += '<div class="panel-menu-row">';
if ($scope.panel.links) {
_.each($scope.panel.links, function(link) {
var info = linkSrv.getPanelLinkAnchorInfo(link, $scope.panel.scopedVars);
if (ctrl.panel.links) {
_.each(ctrl.panel.links, function(link) {
var info = linkSrv.getPanelLinkAnchorInfo(link, ctrl.panel.scopedVars);
template += '<a class="panel-menu-link" href="' + info.href + '" target="' + info.target + '">' + info.title + '</a>';
});
}
return template;
}
function createMenuTemplate($scope) {
function createMenuTemplate(ctrl) {
var template = '<div class="panel-menu small">';
if ($scope.dashboardMeta.canEdit) {
if (ctrl.dashboard.meta.canEdit) {
template += '<div class="panel-menu-inner">';
template += '<div class="panel-menu-row">';
template += '<a class="panel-menu-icon pull-left" ng-click="updateColumnSpan(-1)"><i class="fa fa-minus"></i></a>';
template += '<a class="panel-menu-icon pull-left" ng-click="updateColumnSpan(1)"><i class="fa fa-plus"></i></a>';
template += '<a class="panel-menu-icon pull-right" ng-click="removePanel(panel)"><i class="fa fa-remove"></i></a>';
template += '<a class="panel-menu-icon pull-left" ng-click="ctrl.updateColumnSpan(-1)"><i class="fa fa-minus"></i></a>';
template += '<a class="panel-menu-icon pull-left" ng-click="ctrl.updateColumnSpan(1)"><i class="fa fa-plus"></i></a>';
template += '<a class="panel-menu-icon pull-right" ng-click="ctrl.removePanel()"><i class="fa fa-remove"></i></a>';
template += '<div class="clearfix"></div>';
template += '</div>';
}
@ -44,9 +44,9 @@ function (angular, $, _) {
template += '<div class="panel-menu-row">';
template += '<a class="panel-menu-link" gf-dropdown="extendedMenu"><i class="fa fa-bars"></i></a>';
_.each($scope.panelMeta.menu, function(item) {
_.each(ctrl.getMenu(), function(item) {
// skip edit actions if not editor
if (item.role === 'Editor' && !$scope.dashboardMeta.canEdit) {
if (item.role === 'Editor' && !ctrl.dashboard.meta.canEdit) {
return;
}
@ -63,8 +63,8 @@ function (angular, $, _) {
return template;
}
function getExtendedMenu($scope) {
return angular.copy($scope.panelMeta.extendedMenu);
function getExtendedMenu(ctrl) {
return ctrl.getExtendedMenu();
}
return {
@ -74,13 +74,14 @@ function (angular, $, _) {
var $panelLinksBtn = $link.find(".panel-links-btn");
var $panelContainer = elem.parents(".panel-container");
var menuScope = null;
var ctrl = $scope.ctrl;
var timeout = null;
var $menu = null;
elem.append($link);
$scope.$watchCollection('panel.links', function(newValue) {
var showIcon = (newValue ? newValue.length > 0 : false) && $scope.panel.title !== '';
$scope.$watchCollection('ctrl.panel.links', function(newValue) {
var showIcon = (newValue ? newValue.length > 0 : false) && ctrl.panel.title !== '';
$panelLinksBtn.toggle(showIcon);
});
@ -95,7 +96,7 @@ function (angular, $, _) {
// if hovering or draging pospone close
if (force !== true) {
if ($menu.is(':hover') || $scope.dashboard.$$panelDragging) {
if ($menu.is(':hover') || $scope.ctrl.dashboard.$$panelDragging) {
dismiss(2200);
return;
}
@ -126,7 +127,7 @@ function (angular, $, _) {
if ($(e.target).hasClass('fa-link')) {
menuTemplate = createExternalLinkMenu($scope);
} else {
menuTemplate = createMenuTemplate($scope);
menuTemplate = createMenuTemplate(ctrl);
}
$menu = $(menuTemplate);
@ -135,7 +136,7 @@ function (angular, $, _) {
});
menuScope = $scope.$new();
menuScope.extendedMenu = getExtendedMenu($scope);
menuScope.extendedMenu = getExtendedMenu(ctrl);
menuScope.dismiss = function() {
dismiss(null, true);
};

View File

@ -1,7 +0,0 @@
define([
'./panel_meta2',
],
function (panelMeta) {
'use strict';
return panelMeta.default;
});

View File

@ -1,47 +0,0 @@
export default class PanelMeta {
description: any;
fullscreen: any;
editIcon: any;
panelName: any;
menu: any;
editorTabs: any;
extendedMenu: any;
constructor(options: any) {
this.description = options.description;
this.fullscreen = options.fullscreen;
this.editIcon = options.editIcon;
this.panelName = options.panelName;
this.menu = [];
this.editorTabs = [];
this.extendedMenu = [];
if (options.fullscreen) {
this.addMenuItem('View', 'icon-eye-open', 'toggleFullscreen(false); dismiss();');
}
this.addMenuItem('Edit', 'icon-cog', 'editPanel(); dismiss();', 'Editor');
this.addMenuItem('Duplicate', 'icon-copy', 'duplicatePanel()', 'Editor');
this.addMenuItem('Share', 'icon-share', 'sharePanel(); dismiss();');
this.addEditorTab('General', 'app/partials/panelgeneral.html');
if (options.metricsEditor) {
this.addEditorTab('Metrics', 'app/partials/metrics.html');
}
this.addExtendedMenuItem('Panel JSON', '', 'editPanelJson(); dismiss();');
}
addMenuItem (text, icon, click, role?) {
this.menu.push({text: text, icon: icon, click: click, role: role});
}
addExtendedMenuItem (text, icon, click, role?) {
this.extendedMenu.push({text: text, icon: icon, click: click, role: role});
}
addEditorTab (title, src) {
this.editorTabs.push({title: title, src: src});
}
}

View File

@ -1,12 +1,12 @@
<div class="panel-container" ng-class="{'panel-transparent': panel.transparent}">
<div class="panel-container" ng-class="{'panel-transparent': ctrl.panel.transparent}">
<div class="panel-header">
<span class="alert-error panel-error small pointer" config-modal="app/partials/inspector.html" ng-if="panelMeta.error">
<span data-placement="top" bs-tooltip="panelMeta.error">
<span class="alert-error panel-error small pointer" config-modal="app/partials/inspector.html" ng-if="ctrl.error">
<span data-placement="top" bs-tooltip="ctrl.error">
<i class="fa fa-exclamation"></i><span class="panel-error-arrow"></span>
</span>
</span>
<span class="panel-loading" ng-show="panelMeta.loading">
<span class="panel-loading" ng-show="ctrl.loading">
<i class="fa fa-spinner fa-spin"></i>
</span>
@ -19,27 +19,27 @@
<panel-resizer></panel-resizer>
</div>
<div class="panel-full-edit" ng-if="editMode">
<div class="panel-full-edit" ng-if="ctrl.editMode">
<div class="gf-box">
<div class="gf-box-header">
<div class="gf-box-title">
<i ng-class="panelMeta.editIcon"></i>
{{panelMeta.panelName}}
<i ng-class="ctrl.icon"></i>
{{ctrl.name}}
</div>
<div ng-model="editor.index" bs-tabs>
<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
<div ng-model="ctrl.editorTabIndex" bs-tabs>
<div ng-repeat="tab in ctrl.editorTabs" data-title="{{tab.title}}">
</div>
</div>
<button class="gf-box-header-close-btn" ng-click="exitFullscreen();">
<button class="gf-box-header-close-btn" ng-click="ctrl.exitFullscreen();">
Back to dashboard
</button>
</div>
<div class="gf-box-body">
<div ng-repeat="tab in panelMeta.editorTabs" ng-if="editor.index === $index">
<div ng-include src="tab.src"></div>
<div ng-repeat="tab in ctrl.editorTabs" ng-if="ctrl.editorTabIndex === $index">
<panel-editor-tab editor-tab="tab" ctrl="ctrl" index="$index"></panel-editor-tab>
</div>
</div>
</div>

View File

@ -13,8 +13,8 @@
</li>
<li>
<input type="text" class="input-small tight-form-input last" placeholder="1h"
empty-to-null ng-model="panel.timeFrom" valid-time-span
ng-change="get_data()" ng-model-onblur>
empty-to-null ng-model="ctrl.panel.timeFrom" valid-time-span
ng-change="ctrl.refresh()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@ -32,8 +32,8 @@
</li>
<li>
<input type="text" class="input-small tight-form-input last" placeholder="1h"
empty-to-null ng-model="panel.timeShift" valid-time-span
ng-change="get_data()" ng-model-onblur>
empty-to-null ng-model="ctrl.panel.timeShift" valid-time-span
ng-change="ctrl.refresh()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@ -47,9 +47,9 @@
<strong>Hide time override info</strong>
</li>
<li class="tight-form-item last">
<input class="cr1" id="panel.hideTimeOverride" type="checkbox"
ng-model="panel.hideTimeOverride" ng-checked="panel.hideTimeOverride" ng-change="get_data()">
<label for="panel.hideTimeOverride" class="cr1"></label>
<input class="cr1" id="ctrl.panel.hideTimeOverride" type="checkbox"
ng-model="ctrl.panel.hideTimeOverride" ng-checked="ctrl.panel.hideTimeOverride" ng-change="ctrl.refresh()">
<label for="ctrl.panel.hideTimeOverride" class="cr1"></label>
</li>
</ul>
<div class="clearfix"></div>

View File

@ -2,7 +2,8 @@
<div class="row-fluid">
<div class="span12">
<div class="panel nospace" ng-if="panel" style="width: 100%">
<panel-loader type="panel.type" ng-cloak></panel-loader>
<panel-loader dashboard="dashboard" row="row" panel="panel">
</panel-loader>
</div>
</div>
</div>

View File

@ -5,11 +5,11 @@ import angular from 'angular';
/** @ngInject */
function metricsQueryEditor(dynamicDirectiveSrv, datasourceSrv) {
return dynamicDirectiveSrv.create({
watchPath: "panel.datasource",
watchPath: "ctrl.panel.datasource",
directive: scope => {
let datasource = scope.target.datasource || scope.panel.datasource;
let datasource = scope.target.datasource || scope.ctrl.panel.datasource;
return datasourceSrv.get(datasource).then(ds => {
scope.datasource = ds;
scope.ctrl.datasource = ds;
if (!scope.target.refId) {
scope.target.refId = 'A';
@ -29,9 +29,9 @@ function metricsQueryEditor(dynamicDirectiveSrv, datasourceSrv) {
/** @ngInject */
function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) {
return dynamicDirectiveSrv.create({
watchPath: "panel.datasource",
watchPath: "ctrl.panel.datasource",
directive: scope => {
return datasourceSrv.get(scope.panel.datasource).then(ds => {
return datasourceSrv.get(scope.ctrl.panel.datasource).then(ds => {
return System.import(ds.meta.module).then(dsModule => {
return {
name: 'metrics-query-options-' + ds.meta.id,

View File

@ -39,13 +39,8 @@ function (angular, $) {
}
$scope.panel.span = 12;
$scope.dashboardViewState = {registerPanel: function() { }, state: {}};
};
if (!$scope.skipAutoInit) {
$scope.init();
}
$scope.init();
});
});

View File

@ -5,7 +5,7 @@ declare var System: any;
// dummy modules
declare module 'app/core/config' {
var config : any;
var config: any;
export default config;
}
@ -35,7 +35,7 @@ declare module 'app/core/utils/kbn' {
}
declare module 'app/core/store' {
var store : any;
var store: any;
export default store;
}

View File

@ -23,7 +23,7 @@
<div class="row-text pointer" ng-click="toggleRow(row)" ng-bind="row.title | interpolateTemplateVars:this"></div>
</div>
<div class="row-open" ng-show="!row.collapse">
<div class='row-tab bgSuccess dropdown' ng-show="dashboardMeta.canEdit" ng-hide="dashboardViewState.fullscreen">
<div class='row-tab bgSuccess dropdown' ng-show="dashboardMeta.canEdit" ng-hide="dashboard.meta.fullscreen">
<span class="row-tab-button dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-bars"></i>
</span>
@ -79,9 +79,10 @@
<div class="row-text pointer" ng-click="toggleRow(row)" ng-if="row.showTitle" ng-bind="row.title | interpolateTemplateVars:this">
</div>
<div ng-repeat="(name, panel) in row.panels track by panel.id" class="panel" ui-draggable="!dashboardViewState.fullscreen" drag="panel.id"
ui-on-Drop="onDrop($data, row, panel)" drag-handle-class="drag-handle" panel-width>
<panel-loader class="panel-margin"></panel-loader>
<div ng-repeat="panel in row.panels track by panel.id" class="panel" ui-draggable="!dashboard.meta.fullscreen" drag="panel.id"
ui-on-drop="onDrop($data, row, panel)" drag-handle-class="drag-handle" panel-width>
<panel-loader class="panel-margin" dashboard="dashboard" row="row" panel="panel">
</panel-loader>
</div>
<div panel-drop-zone class="panel panel-drop-zone" ui-on-drop="onDrop($data, row)" data-drop="true">
@ -97,7 +98,7 @@
</div>
</div>
<div ng-show='dashboardMeta.canEdit' class="row-fluid add-row-panel-hint" ng-hide="dashboardViewState.fullscreen">
<div ng-show='dashboardMeta.canEdit' class="row-fluid add-row-panel-hint" ng-hide="dashboard.meta.fullscreen">
<div class="span12" style="text-align:right;">
<span style="margin-right: 10px;" ng-click="addRowDefault()" class="pointer btn btn-info btn-small">
<span><i class="fa fa-plus"></i> ADD ROW</span>

View File

@ -1,25 +1,25 @@
<div class="editor-row">
<div class="tight-form-container">
<metrics-query-editor ng-repeat="target in panel.targets" ng-class="{'tight-form-disabled': target.hide}" >
<metrics-query-editor ng-repeat="target in ctrl.panel.targets" ng-class="{'tight-form-disabled': target.hide}" >
</metrics-query-editor>
</div>
<div style="margin: 20px 0 0 0">
<button class="btn btn-inverse" ng-click="addDataQuery()" ng-hide="datasource.meta.mixed">
<button class="btn btn-inverse" ng-click="ctrl.addDataQuery()" ng-hide="ctrl.datasource.meta.mixed">
<i class="fa fa-plus"></i>&nbsp;
Query
</button>
<div class="dropdown" ng-if="datasource.meta.mixed">
<div class="dropdown" ng-if="ctrl.datasource.meta.mixed">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-plus"></i>&nbsp;
Query &nbsp; <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
<a ng-click="addDataQuery(datasource);">{{datasource.name}}</a>
<li ng-repeat="datasource in ctrl.datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
<a ng-click="ctrl.addDataQuery(datasource);">{{datasource.name}}</a>
</li>
</ul>
</div>
@ -34,12 +34,12 @@
<div class="pull-right dropdown" style="margin-right: 10px;">
<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown" bs-tooltip="'Datasource'">
<i class="fa fa-database"></i>&nbsp;
{{datasource.name}} &nbsp; <span class="caret"></span>
{{ctrl.datasource.name}} &nbsp; <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="datasource in datasources" role="menuitem">
<a ng-click="setDatasource(datasource);">{{datasource.name}}</a>
<li ng-repeat="datasource in ctrl.datasources" role="menuitem">
<a ng-click="ctrl.setDatasource(datasource);">{{datasource.name}}</a>
</li>
</ul>
</div>

View File

@ -7,23 +7,23 @@
Title
</li>
<li>
<input type="text" class="input-xlarge tight-form-input" ng-model='panel.title'></input>
<input type="text" class="input-xlarge tight-form-input" ng-model='ctrl.panel.title'></input>
</li>
<li class="tight-form-item">
Span
</li>
<li>
<select class="input-mini tight-form-input" ng-model="panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select>
<select class="input-mini tight-form-input" ng-model="ctrl.panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select>
</li>
<li class="tight-form-item">
Height
</li>
<li>
<input type="text" class="input-small tight-form-input" ng-model='panel.height'></input>
<input type="text" class="input-small tight-form-input" ng-model='ctrl.panel.height'></input>
</li>
<li class="tight-form-item">
<label class="checkbox-label" for="panel.transparent">Transparent</label>
<input class="cr1" id="panel.transparent" type="checkbox" ng-model="panel.transparent" ng-checked="panel.transparent">
<input class="cr1" id="panel.transparent" type="checkbox" ng-model="ctrl.panel.transparent" ng-checked="ctrl.panel.transparent">
<label for="panel.transparent" class="cr1"></label>
</li>
</ul>
@ -38,7 +38,7 @@
Repeat Panel
</li>
<li>
<select class="input-small tight-form-input last" ng-model="panel.repeat" ng-options="f.name as f.name for f in dashboard.templating.list">
<select class="input-small tight-form-input last" ng-model="ctrl.panel.repeat" ng-options="f.name as f.name for f in ctrl.dashboard.templating.list">
<option value=""></option>
</select>
</li>
@ -46,7 +46,7 @@
Min span
</li>
<li>
<select class="input-small tight-form-input last" ng-model="panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12]">
<select class="input-small tight-form-input last" ng-model="ctrl.panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12]">
<option value=""></option>
</select>
</li>
@ -56,6 +56,6 @@
</div>
</div>
<panel-links-editor panel="panel"></panel-links-editor>
<panel-links-editor panel="ctrl.panel"></panel-links-editor>

View File

@ -5,7 +5,10 @@ function (GraphiteDatasource) {
'use strict';
function metricsQueryEditor() {
return {controller: 'GraphiteQueryCtrl', templateUrl: 'app/plugins/datasource/graphite/partials/query.editor.html'};
return {
controller: 'GraphiteQueryCtrl',
templateUrl: 'app/plugins/datasource/graphite/partials/query.editor.html'
};
}
function metricsQueryOptions() {

View File

@ -25,19 +25,19 @@
</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="duplicateDataQuery(target)">Duplicate</a>
<a tabindex="1" ng-click="ctrl.duplicateDataQuery(target)">Duplicate</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="moveDataQuery($index, $index-1)">Move up</a>
<a tabindex="1" ng-click="ctrl.moveDataQuery($index, $index-1)">Move up</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="moveDataQuery($index, $index+1)">Move down</a>
<a tabindex="1" ng-click="ctrl.moveDataQuery($index, $index+1)">Move down</a>
</li>
</ul>
</div>
</li>
<li class="tight-form-item last">
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<a class="pointer" tabindex="1" ng-click="ctrl.removeDataQuery(target)">
<i class="fa fa-remove"></i>
</a>
</li>
@ -48,14 +48,14 @@
{{target.refId}}
</li>
<li>
<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
<a class="tight-form-item" ng-click="target.hide = !target.hide; panelCtrl.refresh();" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
</ul>
<span style="display: block; overflow: hidden;">
<input type="text" class="tight-form-clear-input" style="width: 100%;" ng-model="target.target" give-focus="target.textEditor" spellcheck='false' ng-model-onblur ng-change="get_data()" ng-show="target.textEditor"></input>
<input type="text" class="tight-form-clear-input" style="width: 100%;" ng-model="target.target" give-focus="target.textEditor" spellcheck='false' ng-model-onblur ng-change="panelCtrl.getData()" ng-show="target.textEditor"></input>
</span>
<ul class="tight-form-list" role="menu" ng-hide="target.textEditor">

View File

@ -11,7 +11,7 @@
<li>
<input type="text"
class="input-mini tight-form-input"
ng-model="panel.cacheTimeout"
ng-model="ctrl.panel.cacheTimeout"
bs-tooltip="'Graphite parameter to override memcache default timeout (unit is seconds)'"
data-placement="right"
spellcheck='false'
@ -23,10 +23,10 @@
<li>
<input type="text"
class="input-mini tight-form-input"
ng-model="panel.maxDataPoints"
ng-model="ctrl.panel.maxDataPoints"
bs-tooltip="'Override max data points, automatically set to graph width in pixels.'"
data-placement="right"
ng-model-onblur ng-change="get_data()"
ng-model-onblur ng-change="ctrl.refresh()"
spellcheck='false'
placeholder="auto"></input>
</li>
@ -39,27 +39,27 @@
<i class="fa fa-info-circle"></i>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
shorter legend names
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(2);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(2);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
series as parameters
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
stacking
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(4)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(4)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
templating
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(5)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(5)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
max data points
</a>
</li>
@ -71,7 +71,7 @@
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<div class="grafana-info-box span8" ng-if="editorHelpIndex === 1">
<div class="grafana-info-box span8" ng-if="ctrl.editorHelpIndex === 1">
<h5>Shorter legend names</h5>
<ul>
<li>alias() function to specify a custom series name</li>
@ -81,7 +81,7 @@
</ul>
</div>
<div class="grafana-info-box span8" ng-if="editorHelpIndex === 2">
<div class="grafana-info-box span8" ng-if="ctrl.editorHelpIndex === 2">
<h5>Series as parameter</h5>
<ul>
<li>Some graphite functions allow you to have many series arguments</li>
@ -99,7 +99,7 @@
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 3">
<h5>Stacking</h5>
<ul>
<li>You find the stacking option under Display Styles tab</li>
@ -107,7 +107,7 @@
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 4">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 4">
<h5>Templating</h5>
<ul>
<li>You can use a template variable in place of metric names</li>
@ -116,7 +116,7 @@
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 5">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 5">
<h5>Max data points</h5>
<ul>
<li>Every graphite request is issued with a maxDataPoints parameter</li>

View File

@ -11,6 +11,7 @@ function (angular, _, config, gfunc, Parser) {
var module = angular.module('grafana.controllers');
module.controller('GraphiteQueryCtrl', function($scope, uiSegmentSrv, templateSrv) {
var panelCtrl = $scope.panelCtrl = $scope.ctrl;
$scope.init = function() {
if ($scope.target) {
@ -125,7 +126,7 @@ function (angular, _, config, gfunc, Parser) {
}
var path = getSegmentPathUpTo(fromIndex + 1);
return $scope.datasource.metricFindQuery(path)
return panelCtrl.datasource.metricFindQuery(path)
.then(function(segments) {
if (segments.length === 0) {
if (path !== '') {
@ -159,7 +160,7 @@ function (angular, _, config, gfunc, Parser) {
$scope.getAltSegments = function (index) {
var query = index === 0 ? '*' : getSegmentPathUpTo(index) + '.*';
return $scope.datasource.metricFindQuery(query).then(function(segments) {
return panelCtrl.datasource.metricFindQuery(query).then(function(segments) {
var altSegments = _.map(segments, function(segment) {
return uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable });
});
@ -208,7 +209,7 @@ function (angular, _, config, gfunc, Parser) {
$scope.targetTextChanged = function() {
parseTarget();
$scope.get_data();
panelCtrl.refresh();
};
$scope.targetChanged = function() {
@ -222,7 +223,7 @@ function (angular, _, config, gfunc, Parser) {
if ($scope.target.target !== oldTarget) {
if ($scope.segments[$scope.segments.length - 1].value !== 'select metric') {
$scope.$parent.get_data();
panelCtrl.refresh();
}
}
};

View File

@ -14,13 +14,19 @@ describe('GraphiteQueryCtrl', function() {
beforeEach(angularMocks.module('grafana.services'));
beforeEach(ctx.providePhase());
beforeEach(ctx.createControllerPhase('GraphiteQueryCtrl'));
beforeEach(angularMocks.inject(($rootScope, $controller, $q) => {
ctx.$q = $q;
ctx.scope = $rootScope.$new();
ctx.scope.ctrl = {panel: ctx.panel};
ctx.panelCtrl = ctx.scope.ctrl;
ctx.controller = $controller('GraphiteQueryCtrl', {$scope: ctx.scope});
}));
beforeEach(function() {
ctx.scope.target = {target: 'aliasByNode(scaleToSeconds(test.prod.*,1),2)'};
ctx.scope.datasource = ctx.datasource;
ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
ctx.panelCtrl.datasource = ctx.datasource;
ctx.panelCtrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
});
describe('init', function() {
@ -30,7 +36,7 @@ describe('GraphiteQueryCtrl', function() {
});
it('should validate metric key exists', function() {
expect(ctx.scope.datasource.metricFindQuery.getCall(0).args[0]).to.be('test.prod.*');
expect(ctx.panelCtrl.datasource.metricFindQuery.getCall(0).args[0]).to.be('test.prod.*');
});
it('should delete last segment if no metrics are found', function() {
@ -45,11 +51,11 @@ describe('GraphiteQueryCtrl', function() {
describe('when adding function', function() {
beforeEach(function() {
ctx.scope.target.target = 'test.prod.*.count';
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: false}]));
ctx.panelCtrl.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: false}]));
ctx.scope.init();
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
ctx.panelCtrl.refresh = sinon.spy();
ctx.scope.addFunction(gfunc.getFuncDef('aliasByNode'));
});
@ -61,19 +67,17 @@ describe('GraphiteQueryCtrl', function() {
expect(ctx.scope.target.target).to.be('aliasByNode(test.prod.*.count, 2)');
});
it('should call get_data', function() {
expect(ctx.scope.$parent.get_data.called).to.be(true);
it('should call refresh', function() {
expect(ctx.panelCtrl.refresh.called).to.be(true);
});
});
describe('when adding function before any metric segment', function() {
beforeEach(function() {
ctx.scope.target.target = '';
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: true}]));
ctx.panelCtrl.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: true}]));
ctx.scope.init();
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
ctx.scope.addFunction(gfunc.getFuncDef('asPercent'));
});
@ -85,10 +89,9 @@ describe('GraphiteQueryCtrl', function() {
describe('when initalizing target without metric expression and only function', function() {
beforeEach(function() {
ctx.scope.target.target = 'asPercent(#A, #B)';
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([]));
ctx.panelCtrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
ctx.scope.init();
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
});
it('should not add select metric segment', function() {
@ -104,10 +107,9 @@ describe('GraphiteQueryCtrl', function() {
describe('when initializing a target with single param func using variable', function() {
beforeEach(function() {
ctx.scope.target.target = 'movingAverage(prod.count, $var)';
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([]));
ctx.panelCtrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
ctx.scope.init();
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
});
it('should add 2 segments', function() {
@ -123,7 +125,7 @@ describe('GraphiteQueryCtrl', function() {
describe('when initalizing target without metric expression and function with series-ref', function() {
beforeEach(function() {
ctx.scope.target.target = 'asPercent(metric.node.count, #A)';
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([]));
ctx.panelCtrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
ctx.scope.init();
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
@ -141,13 +143,12 @@ describe('GraphiteQueryCtrl', function() {
describe('when getting altSegments and metricFindQuery retuns empty array', function() {
beforeEach(function() {
ctx.scope.target.target = 'test.count';
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([]));
ctx.panelCtrl.datasource.metricFindQuery.returns(ctx.$q.when([]));
ctx.scope.init();
ctx.scope.getAltSegments(1).then(function(results) {
ctx.altSegments = results;
});
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
});
it('should have no segments', function() {
@ -158,11 +159,11 @@ describe('GraphiteQueryCtrl', function() {
describe('targetChanged', function() {
beforeEach(function() {
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: false}]));
ctx.panelCtrl.datasource.metricFindQuery.returns(ctx.$q.when([{expandable: false}]));
ctx.scope.init();
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
ctx.panelCtrl.refresh = sinon.spy();
ctx.scope.target.target = '';
ctx.scope.targetChanged();
});
@ -171,8 +172,8 @@ describe('GraphiteQueryCtrl', function() {
expect(ctx.scope.target.target).to.be('aliasByNode(scaleToSeconds(test.prod.*, 1), 2)');
});
it('should call get_data', function() {
expect(ctx.scope.$parent.get_data.called).to.be(true);
it('should call panelCtrl.refresh', function() {
expect(ctx.panelCtrl.refresh.called).to.be(true);
});
});
});

View File

@ -35,7 +35,7 @@
{{target.refId}}
</li>
<li>
<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
<a class="tight-form-item" ng-click="target.hide = !target.hide; panelCtrl.refresh();" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</li>
@ -60,7 +60,7 @@
</ul>
<div class="tight-form-flex-wrapper" ng-show="target.rawQuery">
<input type="text" class="tight-form-clear-input" ng-model="target.query" spellcheck="false" style="width: 100%;" ng-blur="get_data()"></input>
<input type="text" class="tight-form-clear-input" ng-model="target.query" spellcheck="false" style="width: 100%;" ng-blur="panelCtrl.refresh()"></input>
</div>
<div class="clearfix"></div>
@ -88,7 +88,7 @@
<span>GROUP BY</span>
</li>
<li ng-repeat="part in queryModel.groupByParts">
<influx-query-part-editor part="part" class="tight-form-item tight-form-func" remove-action="removeGroupByPart(part, $index)" part-updated="get_data();" get-options="getPartOptions(part)"></influx-query-part-editor>
<influx-query-part-editor part="part" class="tight-form-item tight-form-func" remove-action="removeGroupByPart(part, $index)" part-updated="panelCtrl.refresh();" get-options="getPartOptions(part)"></influx-query-part-editor>
</li>
<li>
<metric-segment segment="groupBySegment" get-options="getGroupByOptions()" on-change="groupByAction(part, $index)"></metric-segment>
@ -104,13 +104,13 @@
ALIAS BY
</li>
<li>
<input type="text" class="tight-form-clear-input input-xlarge" ng-model="target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="get_data()">
<input type="text" class="tight-form-clear-input input-xlarge" ng-model="target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="panelCtrl.refresh()">
</li>
<li class="tight-form-item">
Format as
</li>
<li>
<select class="input-small tight-form-input" style="width: 104px" ng-model="target.resultFormat" ng-options="f.value as f.text for f in resultFormats" ng-change="get_data()"></select>
<select class="input-small tight-form-input" style="width: 104px" ng-model="target.resultFormat" ng-options="f.value as f.text for f in resultFormats" ng-change="panelCtrl.refresh()"></select>
</li>
</ul>
<div class="clearfix"></div>

View File

@ -8,7 +8,7 @@
Group by time interval
</li>
<li>
<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
<input type="text" class="input-medium tight-form-input" ng-model="ctrl.panel.interval" ng-blur="ctrl.refresh();"
spellcheck='false' placeholder="example: >10s">
</li>
<li class="tight-form-item">
@ -24,17 +24,17 @@
<i class="fa fa-info-circle"></i>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
alias patterns
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
stacking &amp; and fill
</a>
</li>
<li class="tight-form-item">
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
<a ng-click="ctrl.toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
group by time
</a>
</li>
@ -46,7 +46,7 @@
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 1">
<h5>Alias patterns</h5>
<ul>
<li>$m = replaced with measurement name</li>
@ -58,7 +58,7 @@
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 2">
<h5>Stacking and fill</h5>
<ul>
<li>When stacking is enabled it important that points align</li>
@ -69,7 +69,7 @@
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
<div class="grafana-info-box span6" ng-if="ctrl.editorHelpIndex === 3">
<h5>Group by time</h5>
<ul>
<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
@ -80,8 +80,6 @@
<li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
</ul>
</div>
</div>
</div>

View File

@ -15,13 +15,15 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
queryPart = queryPart.default;
module.controller('InfluxQueryCtrl', function($scope, templateSrv, $q, uiSegmentSrv) {
var panelCtrl = $scope.ctrl;
$scope.panelCtrl = panelCtrl;
$scope.init = function() {
if (!$scope.target) { return; }
$scope.target = $scope.target;
$scope.queryModel = new InfluxQuery($scope.target);
$scope.queryBuilder = new InfluxQueryBuilder($scope.target, $scope.datasource.database);
$scope.queryBuilder = new InfluxQueryBuilder($scope.target, panelCtrl.datasource.database);
$scope.groupBySegment = uiSegmentSrv.newPlusButton();
$scope.resultFormats = [
{text: 'Time series', value: 'time_series'},
@ -75,7 +77,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
$scope.getGroupByOptions = function() {
var query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS');
return $scope.datasource.metricFindQuery(query)
return panelCtrl.datasource.metricFindQuery(query)
.then(function(tags) {
var options = [];
if (!$scope.queryModel.hasFill()) {
@ -97,26 +99,26 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
var plusButton = uiSegmentSrv.newPlusButton();
$scope.groupBySegment.value = plusButton.value;
$scope.groupBySegment.html = plusButton.html;
$scope.get_data();
panelCtrl.refresh();
};
$scope.removeGroupByPart = function(part, index) {
$scope.queryModel.removeGroupByPart(part, index);
$scope.get_data();
panelCtrl.refresh();
};
$scope.addSelectPart = function(selectParts, cat, subitem) {
$scope.queryModel.addSelectPart(selectParts, subitem.value);
$scope.get_data();
panelCtrl.refresh();
};
$scope.removeSelectPart = function(selectParts, part) {
$scope.queryModel.removeSelectPart(selectParts, part);
$scope.get_data();
panelCtrl.refresh();
};
$scope.selectPartUpdated = function() {
$scope.get_data();
panelCtrl.refresh();
};
$scope.fixTagSegments = function() {
@ -130,19 +132,19 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
$scope.measurementChanged = function() {
$scope.target.measurement = $scope.measurementSegment.value;
$scope.get_data();
panelCtrl.refresh();
};
$scope.getPolicySegments = function() {
var policiesQuery = $scope.queryBuilder.buildExploreQuery('RETENTION POLICIES');
return $scope.datasource.metricFindQuery(policiesQuery)
return panelCtrl.datasource.metricFindQuery(policiesQuery)
.then($scope.transformToSegments(false))
.then(null, $scope.handleQueryError);
};
$scope.policyChanged = function() {
$scope.target.policy = $scope.policySegment.value;
$scope.get_data();
panelCtrl.refresh();
};
$scope.toggleQueryMode = function () {
@ -151,19 +153,19 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
$scope.getMeasurements = function () {
var query = $scope.queryBuilder.buildExploreQuery('MEASUREMENTS');
return $scope.datasource.metricFindQuery(query)
return panelCtrl.datasource.metricFindQuery(query)
.then($scope.transformToSegments(true), $scope.handleQueryError);
};
$scope.getPartOptions = function(part) {
if (part.def.type === 'field') {
var fieldsQuery = $scope.queryBuilder.buildExploreQuery('FIELDS');
return $scope.datasource.metricFindQuery(fieldsQuery)
return panelCtrl.datasource.metricFindQuery(fieldsQuery)
.then($scope.transformToSegments(true), $scope.handleQueryError);
}
if (part.def.type === 'tag') {
var tagsQuery = $scope.queryBuilder.buildExploreQuery('TAG_KEYS');
return $scope.datasource.metricFindQuery(tagsQuery)
return panelCtrl.datasource.metricFindQuery(tagsQuery)
.then($scope.transformToSegments(true), $scope.handleQueryError);
}
};
@ -211,7 +213,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
addTemplateVars = true;
}
return $scope.datasource.metricFindQuery(query)
return panelCtrl.datasource.metricFindQuery(query)
.then($scope.transformToSegments(addTemplateVars))
.then(function(results) {
if (segment.type === 'key') {
@ -224,7 +226,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
$scope.getFieldSegments = function() {
var fieldsQuery = $scope.queryBuilder.buildExploreQuery('FIELDS');
return $scope.datasource.metricFindQuery(fieldsQuery)
return panelCtrl.datasource.metricFindQuery(fieldsQuery)
.then($scope.transformToSegments(false))
.then(null, $scope.handleQueryError);
};
@ -234,7 +236,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
$scope.setFill = function(fill) {
$scope.target.fill = fill;
$scope.get_data();
panelCtrl.refresh();
};
$scope.tagSegmentUpdated = function(segment, index) {
@ -300,7 +302,7 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) {
});
$scope.target.tags = tags;
$scope.$parent.get_data();
panelCtrl.refresh();
};
$scope.getTagValueOperator = function(tagValue, tagOperator) {

View File

@ -10,14 +10,20 @@ describe('InfluxDBQueryCtrl', function() {
beforeEach(angularMocks.module('grafana.controllers'));
beforeEach(angularMocks.module('grafana.services'));
beforeEach(ctx.providePhase());
beforeEach(ctx.createControllerPhase('InfluxQueryCtrl'));
beforeEach(angularMocks.inject(($rootScope, $controller, $q) => {
ctx.$q = $q;
ctx.scope = $rootScope.$new();
ctx.scope.ctrl = {panel: ctx.panel};
ctx.panelCtrl = ctx.scope.ctrl;
ctx.controller = $controller('InfluxQueryCtrl', {$scope: ctx.scope});
}));
beforeEach(function() {
ctx.scope.target = {};
ctx.scope.$parent = { get_data: sinon.spy() };
ctx.scope.datasource = ctx.datasource;
ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
ctx.panelCtrl.refresh = sinon.spy();
ctx.panelCtrl.datasource = ctx.datasource;
ctx.panelCtrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
});
describe('init', function() {

View File

@ -7,13 +7,13 @@
<strong>Mode</strong>
</li>
<li>
<select class="input-small tight-form-input last" ng-model="panel.mode" ng-options="f for f in modes" ng-change="get_data()"></select>
<select class="input-small tight-form-input last" ng-model="ctrl.panel.mode" ng-options="f for f in ctrl.modes" ng-change="ctrl.refresh()"></select>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<div class="section" style="margin-bottom: 20px" ng-if="panel.mode === 'search'">
<div class="section" style="margin-bottom: 20px" ng-if="ctrl.panel.mode === 'search'">
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 110px">
@ -24,13 +24,13 @@
</li>
<li>
<input type="text" class="input-medium tight-form-input" placeholder="title query"
ng-model="panel.query" ng-change="get_data()" ng-model-onblur>
ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur>
</li>
<li class="tight-form-item">
Tags
</li>
<li>
<bootstrap-tagsinput ng-model="panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="get_data()">
<bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()">
</bootstrap-tagsinput>
</li>
</ul>
@ -47,7 +47,7 @@
<strong>Limit number to</strong>
</li>
<li>
<input class="input-small tight-form-input last" type="number" ng-model="panel.limit" ng-model-onblur ng-change="get_data()">
<input class="input-small tight-form-input last" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()">
</li>
</ul>
<div class="clearfix"></div>

View File

@ -1,6 +1,5 @@
<grafana-panel>
<div class="dashlist">
<div class="dashlist-item" ng-repeat="dash in dashList">
<div class="dashlist">
<div class="dashlist-item" ng-repeat="dash in ctrl.dashList">
<a class="dashlist-link dashlist-link-{{dash.type}}" href="dashboard/{{dash.uri}}">
<span class="dashlist-title">
{{dash.title}}
@ -10,4 +9,4 @@
</span>
</a>
</div>
</grafana-panel>
</div>

View File

@ -1,82 +0,0 @@
define([
'angular',
'app/app',
'lodash',
'app/core/config',
'app/features/panel/panel_meta',
],
function (angular, app, _, config, PanelMeta) {
'use strict';
var module = angular.module('grafana.panels.dashlist', []);
app.useModule(module);
/** @ngInject */
function DashListPanelCtrl($scope, panelSrv, backendSrv) {
$scope.panelMeta = new PanelMeta({
panelName: 'Dashboard list',
editIcon: "fa fa-star",
fullscreen: true,
});
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/dashlist/editor.html');
var defaults = {
mode: 'starred',
query: '',
limit: 10,
tags: []
};
$scope.modes = ['starred', 'search'];
_.defaults($scope.panel, defaults);
$scope.dashList = [];
$scope.init = function() {
panelSrv.init($scope);
if ($scope.panel.tag) {
$scope.panel.tags = [$scope.panel.tag];
delete $scope.panel.tag;
}
if ($scope.isNewPanel()) {
$scope.panel.title = "Starred Dashboards";
}
};
$scope.refreshData = function() {
var params = {
limit: $scope.panel.limit
};
if ($scope.panel.mode === 'starred') {
params.starred = "true";
} else {
params.query = $scope.panel.query;
params.tag = $scope.panel.tags;
}
return backendSrv.search(params).then(function(result) {
$scope.dashList = result;
$scope.panelRenderingComplete();
});
};
$scope.init();
}
function dashListPanelDirective() {
return {
controller: DashListPanelCtrl,
templateUrl: 'app/plugins/panel/dashlist/module.html',
};
}
return {
panel: dashListPanelDirective
};
});

View File

@ -0,0 +1,63 @@
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
import config from 'app/core/config';
import {PanelDirective, PanelCtrl} from '../../../features/panel/panel';
// Set and populate defaults
var panelDefaults = {
mode: 'starred',
query: '',
limit: 10,
tags: []
};
class DashListCtrl extends PanelCtrl {
dashList: any[];
modes: any[];
/** @ngInject */
constructor($scope, $injector, private backendSrv) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
if (this.panel.tag) {
this.panel.tags = [$scope.panel.tag];
delete this.panel.tag;
}
}
initEditMode() {
this.modes = ['starred', 'search'];
this.icon = "fa fa-star";
this.addEditorTab('Options', () => {
return {templateUrl: 'app/plugins/panel/dashlist/editor.html'};
});
}
refresh() {
var params: any = {limit: this.panel.limit};
if (this.panel.mode === 'starred') {
params.starred = "true";
} else {
params.query = this.panel.query;
params.tag = this.panel.tags;
}
return this.backendSrv.search(params).then(result => {
this.dashList = result;
this.renderingCompleted();
});
}
}
class DashListPanel extends PanelDirective {
controller = DashListCtrl;
templateUrl = 'public/app/plugins/panel/dashlist/module.html';
}
export {
DashListCtrl,
DashListPanel as Panel
}

View File

@ -10,22 +10,22 @@
Unit
</li>
<li class="dropdown" style="width: 140px;"
ng-model="panel.y_formats[0]"
dropdown-typeahead="unitFormats"
dropdown-typeahead-on-select="setUnitFormat(0, $subItem)">
ng-model="ctrl.panel.y_formats[0]"
dropdown-typeahead="ctrl.unitFormats"
dropdown-typeahead-on-select="ctrl.setUnitFormat(0, $subItem)">
</li>
<li class="tight-form-item">
Scale type
</li>
<li>
<select class="input-small tight-form-input" style="width: 113px" ng-model="panel.grid.leftLogBase" ng-options="v as k for (k, v) in logScales" ng-change="render()"></select>
<select class="input-small tight-form-input" style="width: 113px" ng-model="ctrl.panel.grid.leftLogBase" ng-options="v as k for (k, v) in ctrl.logScales" ng-change="ctrl.render()"></select>
</li>
<li class="tight-form-item">
Label
</li>
<li>
<input type="text" class="input-small tight-form-input last"
ng-model="panel.leftYAxisLabel" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.leftYAxisLabel" ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@ -40,16 +40,16 @@
</li>
<li>
<input type="number" class="input-small tight-form-input" placeholder="auto"
empty-to-null ng-model="panel.grid.leftMax"
ng-change="render()" ng-model-onblur>
empty-to-null ng-model="ctrl.panel.grid.leftMax"
ng-change="ctrl.render()" ng-model-onblur>
</li>
<li class="tight-form-item" style="width: 115px; text-align: right;">
Y-Min
</li>
<li>
<input type="number" class="input-small tight-form-input" placeholder="auto" style="width: 113px;"
empty-to-null ng-model="panel.grid.leftMin"
ng-change="render()" ng-model-onblur>
empty-to-null ng-model="ctrl.panel.grid.leftMin"
ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@ -63,22 +63,22 @@
Unit
</li>
<li class="dropdown" style="width: 140px"
ng-model="panel.y_formats[1]"
dropdown-typeahead="unitFormats"
dropdown-typeahead-on-select="setUnitFormat(1, $subItem)">
ng-model="ctrl.panel.y_formats[1]"
dropdown-typeahead="ctrl.unitFormats"
dropdown-typeahead-on-select="ctrl.setUnitFormat(1, $subItem)">
</li>
<li class="tight-form-item">
Scale type
</li>
<li>
<select class="input-small tight-form-input" style="width: 113px" ng-model="panel.grid.rightLogBase" ng-options="v as k for (k, v) in logScales" ng-change="render()"></select>
<select class="input-small tight-form-input" style="width: 113px" ng-model="ctrl.panel.grid.rightLogBase" ng-options="v as k for (k, v) in ctrl.logScales" ng-change="ctrl.render()"></select>
</li>
<li class="tight-form-item">
Label
</li>
<li>
<input type="text" class="input-small tight-form-input last"
ng-model="panel.rightYAxisLabel" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.rightYAxisLabel" ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@ -93,16 +93,16 @@
</li>
<li>
<input type="number" class="input-small tight-form-input" placeholder="auto"
empty-to-null ng-model="panel.grid.rightMax"
ng-change="render()" ng-model-onblur>
empty-to-null ng-model="ctrl.panel.grid.rightMax"
ng-change="ctrl.render()" ng-model-onblur>
</li>
<li class="tight-form-item" style="width: 115px; text-align: right;">
Y-Min
</li>
<li>
<input type="number" class="input-small tight-form-input" placeholder="auto" style="width: 113px;"
empty-to-null ng-model="panel.grid.rightMin"
ng-change="render()" ng-model-onblur>
empty-to-null ng-model="ctrl.panel.grid.rightMin"
ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@ -119,13 +119,13 @@
<li class="tight-form-item">
X-Axis&nbsp;
<input class="cr1" id="hideXAxis" type="checkbox"
ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
ng-model="ctrl.panel['x-axis']" ng-checked="ctrl.panel['x-axis']" ng-change="ctrl.render()">
<label for="hideXAxis" class="cr1"></label>
</li>
<li class="tight-form-item last">
Y-Axis&nbsp;
<input class="cr1" id="hideYAxis" type="checkbox"
ng-model="panel['y-axis']" ng-checked="panel['y-axis']" ng-change="render()">
ng-model="ctrl.panel['y-axis']" ng-checked="ctrl.panel['y-axis']" ng-change="ctrl.render()">
<label for="hideYAxis" class="cr1"></label>
</li>
</ul>
@ -141,23 +141,23 @@
</li>
<li>
<input type="number" class="input-small tight-form-input"
ng-model="panel.grid.threshold1" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.grid.threshold1" ng-change="ctrl.render()" ng-model-onblur>
</li>
<li class="tight-form-item">
<spectrum-picker ng-model="panel.grid.threshold1Color" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.grid.threshold1Color" ng-change="ctrl.render()" ></spectrum-picker>
</li>
<li class="tight-form-item">
Level 2
</li>
<li>
<input type="number" class="input-small tight-form-input"
ng-model="panel.grid.threshold2" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.grid.threshold2" ng-change="ctrl.render()" ng-model-onblur>
</li>
<li class="tight-form-item">
<spectrum-picker ng-model="panel.grid.threshold2Color" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.grid.threshold2Color" ng-change="ctrl.render()" ></spectrum-picker>
</li>
<li class="tight-form-item last">
<editor-checkbox text="Line mode" model="panel.grid.thresholdLine" change="render()"></editor-checkbox>
<editor-checkbox text="Line mode" model="ctrl.panel.grid.thresholdLine" change="ctrl.render()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
@ -173,20 +173,20 @@
Legend
</li>
<li class="tight-form-item">
<editor-checkbox text="Show" model="panel.legend.show" change="get_data()"></editor-checkbox>
<editor-checkbox text="Show" model="ctrl.panel.legend.show" change="ctrl.refresh()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Table" model="panel.legend.alignAsTable" change="render()"></editor-checkbox>
<editor-checkbox text="Table" model="ctrl.panel.legend.alignAsTable" change="ctrl.render()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Right side" model="panel.legend.rightSide" change="render()"></editor-checkbox>
<editor-checkbox text="Right side" model="ctrl.panel.legend.rightSide" change="ctrl.render()"></editor-checkbox>
</li>
<li ng-if="panel.legend.rightSide" class="tight-form-item">
Side width
</li>
<li ng-if="panel.legend.rightSide" style="width: 105px">
<input type="number" class="input-small tight-form-input" placeholder="250" bs-tooltip="'Set a min-width for the legend side table/block'" data-placement="right"
ng-model="panel.legend.sideWidth" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.legend.sideWidth" ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@ -197,10 +197,10 @@
Hide series
</li>
<li class="tight-form-item">
<editor-checkbox text="With only nulls" model="panel.legend.hideEmpty" change="render()"></editor-checkbox>
<editor-checkbox text="With only nulls" model="ctrl.panel.legend.hideEmpty" change="ctrl.render()"></editor-checkbox>
</li>
<li class="tight-form-item last">
<editor-checkbox text="With only zeroes" model="panel.legend.hideZero" change="render()"></editor-checkbox>
<editor-checkbox text="With only zeroes" model="ctrl.panel.legend.hideZero" change="ctrl.render()"></editor-checkbox>
</li>
</ul>
<div class="clearfix"></div>
@ -212,26 +212,26 @@
Values
</li>
<li class="tight-form-item">
<editor-checkbox text="Min" model="panel.legend.min" change="legendValuesOptionChanged()"></editor-checkbox>
<editor-checkbox text="Min" model="ctrl.panel.legend.min" change="ctrl.legendValuesOptionChanged()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Max" model="panel.legend.max" change="legendValuesOptionChanged()"></editor-checkbox>
<editor-checkbox text="Max" model="ctrl.panel.legend.max" change="ctrl.legendValuesOptionChanged()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Avg" model="panel.legend.avg" change="legendValuesOptionChanged()"></editor-checkbox>
<editor-checkbox text="Avg" model="ctrl.panel.legend.avg" change="ctrl.legendValuesOptionChanged()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Current" model="panel.legend.current" change="legendValuesOptionChanged()"></editor-checkbox>
<editor-checkbox text="Current" model="ctrl.panel.legend.current" change="ctrl.legendValuesOptionChanged()"></editor-checkbox>
</li>
<li class="tight-form-item">
<editor-checkbox text="Total" model="panel.legend.total" change="legendValuesOptionChanged()"></editor-checkbox>
<editor-checkbox text="Total" model="ctrl.panel.legend.total" change="ctrl.legendValuesOptionChanged()"></editor-checkbox>
</li>
<li class="tight-form-item">
Decimals
</li>
<li style="width: 105px">
<input type="number" class="input-small tight-form-input" placeholder="auto" bs-tooltip="'Override automatic decimal precision for legend and tooltips'" data-placement="right"
ng-model="panel.decimals" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.decimals" ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>

View File

@ -24,14 +24,16 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
restrict: 'A',
template: '<div> </div>',
link: function(scope, elem) {
var dashboard = scope.dashboard;
var ctrl = scope.ctrl;
var dashboard = ctrl.dashboard;
var panel = ctrl.panel;
var data, annotations;
var sortedSeries;
var graphHeight;
var legendSideLastValue = null;
scope.crosshairEmiter = false;
var rootScope = scope.$root;
scope.onAppEvent('setCrosshair', function(event, info) {
rootScope.onAppEvent('setCrosshair', function(event, info) {
// do not need to to this if event is from this panel
if (info.scope === scope) {
return;
@ -43,20 +45,20 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
plot.setCrosshair({ x: info.pos.x, y: info.pos.y });
}
}
});
}, scope);
scope.onAppEvent('clearCrosshair', function() {
rootScope.onAppEvent('clearCrosshair', function() {
var plot = elem.data().plot;
if (plot) {
plot.clearCrosshair();
}
});
}, scope);
// Receive render events
scope.$on('render',function(event, renderData) {
data = renderData || data;
if (!data) {
scope.get_data();
ctrl.refresh();
return;
}
annotations = data.annotations || annotations;
@ -64,10 +66,10 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
});
function getLegendHeight(panelHeight) {
if (!scope.panel.legend.show || scope.panel.legend.rightSide) {
if (!panel.legend.show || panel.legend.rightSide) {
return 0;
}
if (scope.panel.legend.alignAsTable) {
if (panel.legend.alignAsTable) {
var total = 30 + (25 * data.length);
return Math.min(total, Math.floor(panelHeight/2));
} else {
@ -77,14 +79,13 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
function setElementHeight() {
try {
graphHeight = scope.height || scope.panel.height || scope.row.height;
graphHeight = ctrl.height || panel.height || ctrl.row.height;
if (_.isString(graphHeight)) {
graphHeight = parseInt(graphHeight.replace('px', ''), 10);
}
graphHeight -= 5; // padding
graphHeight -= scope.panel.title ? 24 : 9; // subtract panel title bar
graphHeight -= panel.title ? 24 : 9; // subtract panel title bar
graphHeight = graphHeight - getLegendHeight(graphHeight); // subtract one line legend
elem.css('height', graphHeight + 'px');
@ -100,7 +101,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
return true;
}
if ($rootScope.fullscreen && !scope.fullscreen) {
if (ctrl.otherPanelInFullscreenMode()) {
return true;
}
@ -122,11 +123,11 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
for (var i = 0; i < data.length; i++) {
var series = data[i];
var axis = yaxis[series.yaxis - 1];
var formater = kbn.valueFormats[scope.panel.y_formats[series.yaxis - 1]];
var formater = kbn.valueFormats[panel.y_formats[series.yaxis - 1]];
// decimal override
if (_.isNumber(scope.panel.decimals)) {
series.updateLegendValues(formater, scope.panel.decimals, null);
if (_.isNumber(panel.decimals)) {
series.updateLegendValues(formater, panel.decimals, null);
} else {
// auto decimals
// legend and tooltip gets one more decimal precision
@ -135,22 +136,22 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
series.updateLegendValues(formater, tickDecimals, axis.scaledDecimals + 2);
}
if(!scope.$$phase) { scope.$digest(); }
if(!rootScope.$$phase) { scope.$digest(); }
}
// add left axis labels
if (scope.panel.leftYAxisLabel) {
if (panel.leftYAxisLabel) {
var yaxisLabel = $("<div class='axisLabel left-yaxis-label'></div>")
.text(scope.panel.leftYAxisLabel)
.text(panel.leftYAxisLabel)
.appendTo(elem);
yaxisLabel.css("margin-top", yaxisLabel.width() / 2);
}
// add right axis labels
if (scope.panel.rightYAxisLabel) {
if (panel.rightYAxisLabel) {
var rightLabel = $("<div class='axisLabel right-yaxis-label'></div>")
.text(scope.panel.rightYAxisLabel)
.text(panel.rightYAxisLabel)
.appendTo(elem);
rightLabel.css("margin-top", rightLabel.width() / 2);
@ -158,8 +159,8 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
}
function processOffsetHook(plot, gridMargin) {
if (scope.panel.leftYAxisLabel) { gridMargin.left = 20; }
if (scope.panel.rightYAxisLabel) { gridMargin.right = 20; }
if (panel.leftYAxisLabel) { gridMargin.left = 20; }
if (panel.rightYAxisLabel) { gridMargin.right = 20; }
}
// Function for rendering panel
@ -168,7 +169,6 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
return;
}
var panel = scope.panel;
var stack = panel.stack ? true : null;
// Populate element
@ -230,7 +230,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode, panel.y_formats);
// if hidden remove points and disable stack
if (scope.hiddenSeries[series.alias]) {
if (ctrl.hiddenSeries[series.alias]) {
series.data = [];
series.stack = false;
}
@ -255,7 +255,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
}
if (incrementRenderCounter) {
scope.panelRenderingComplete();
ctrl.renderingCompleted();
}
}
@ -285,12 +285,12 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
function addTimeAxis(options) {
var ticks = elem.width() / 100;
var min = _.isUndefined(scope.range.from) ? null : scope.range.from.valueOf();
var max = _.isUndefined(scope.range.to) ? null : scope.range.to.valueOf();
var min = _.isUndefined(ctrl.range.from) ? null : ctrl.range.from.valueOf();
var max = _.isUndefined(ctrl.range.to) ? null : ctrl.range.to.valueOf();
options.xaxis = {
timezone: dashboard.timezone,
show: scope.panel['x-axis'],
show: panel['x-axis'],
mode: "time",
min: min,
max: max,
@ -361,11 +361,11 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
function configureAxisOptions(data, options) {
var defaults = {
position: 'left',
show: scope.panel['y-axis'],
min: scope.panel.grid.leftMin,
show: panel['y-axis'],
min: panel.grid.leftMin,
index: 1,
logBase: scope.panel.grid.leftLogBase || 1,
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.leftMax,
logBase: panel.grid.leftLogBase || 1,
max: panel.percentage && panel.stack ? 100 : panel.grid.leftMax,
};
options.yaxes.push(defaults);
@ -373,18 +373,18 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
if (_.findWhere(data, {yaxis: 2})) {
var secondY = _.clone(defaults);
secondY.index = 2,
secondY.logBase = scope.panel.grid.rightLogBase || 1,
secondY.logBase = panel.grid.rightLogBase || 1,
secondY.position = 'right';
secondY.min = scope.panel.grid.rightMin;
secondY.max = scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.rightMax;
secondY.min = panel.grid.rightMin;
secondY.max = panel.percentage && panel.stack ? 100 : panel.grid.rightMax;
options.yaxes.push(secondY);
applyLogScale(options.yaxes[1], data);
configureAxisMode(options.yaxes[1], scope.panel.percentage && scope.panel.stack ? "percent" : scope.panel.y_formats[1]);
configureAxisMode(options.yaxes[1], panel.percentage && panel.stack ? "percent" : panel.y_formats[1]);
}
applyLogScale(options.yaxes[0], data);
configureAxisMode(options.yaxes[0], scope.panel.percentage && scope.panel.stack ? "percent" : scope.panel.y_formats[0]);
configureAxisMode(options.yaxes[0], panel.percentage && panel.stack ? "percent" : panel.y_formats[0]);
}
function applyLogScale(axis, data) {
@ -466,18 +466,18 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
url += '&height=' + elem.css('height').replace('px', '');
url += '&bgcolor=1f1f1f'; // @grayDarker & @grafanaPanelBackground
url += '&fgcolor=BBBFC2'; // @textColor & @grayLighter
url += scope.panel.stack ? '&areaMode=stacked' : '';
url += scope.panel.fill !== 0 ? ('&areaAlpha=' + (scope.panel.fill/10).toFixed(1)) : '';
url += scope.panel.linewidth !== 0 ? '&lineWidth=' + scope.panel.linewidth : '';
url += scope.panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
url += scope.panel.grid.leftMin !== null ? '&yMin=' + scope.panel.grid.leftMin : '';
url += scope.panel.grid.leftMax !== null ? '&yMax=' + scope.panel.grid.leftMax : '';
url += scope.panel.grid.rightMin !== null ? '&yMin=' + scope.panel.grid.rightMin : '';
url += scope.panel.grid.rightMax !== null ? '&yMax=' + scope.panel.grid.rightMax : '';
url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
url += panel.stack ? '&areaMode=stacked' : '';
url += panel.fill !== 0 ? ('&areaAlpha=' + (panel.fill/10).toFixed(1)) : '';
url += panel.linewidth !== 0 ? '&lineWidth=' + panel.linewidth : '';
url += panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
url += panel.grid.leftMin !== null ? '&yMin=' + panel.grid.leftMin : '';
url += panel.grid.leftMax !== null ? '&yMax=' + panel.grid.leftMax : '';
url += panel.grid.rightMin !== null ? '&yMin=' + panel.grid.rightMin : '';
url += panel.grid.rightMax !== null ? '&yMax=' + panel.grid.rightMax : '';
url += panel['x-axis'] ? '' : '&hideAxes=true';
url += panel['y-axis'] ? '' : '&hideYAxis=true';
switch(scope.panel.y_formats[0]) {
switch(panel.y_formats[0]) {
case 'bytes':
url += '&yUnitSystem=binary';
break;
@ -510,7 +510,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
break;
}
switch(scope.panel.nullPointMode) {
switch(panel.nullPointMode) {
case 'connected':
url += '&lineMode=connected';
break;
@ -521,7 +521,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
break;
}
url += scope.panel.steppedLine ? '&lineMode=staircase' : '';
url += panel.steppedLine ? '&lineMode=staircase' : '';
elem.html('<img src="' + url + '"></img>');
}

View File

@ -0,0 +1,295 @@
///<reference path="../../../headers/common.d.ts" />
import moment from 'moment';
import kbn from 'app/core/utils/kbn';
import _ from 'lodash';
import TimeSeries from '../../../core/time_series2';
import * as fileExport from '../../../core/utils/file_export';
import {MetricsPanelCtrl} from '../../../features/panel/panel';
var panelDefaults = {
// datasource name, null = default datasource
datasource: null,
// sets client side (flot) or native graphite png renderer (png)
renderer: 'flot',
// Show/hide the x-axis
'x-axis' : true,
// Show/hide y-axis
'y-axis' : true,
// y axis formats, [left axis,right axis]
y_formats : ['short', 'short'],
// grid options
grid : {
leftLogBase: 1,
leftMax: null,
rightMax: null,
leftMin: null,
rightMin: null,
rightLogBase: 1,
threshold1: null,
threshold2: null,
threshold1Color: 'rgba(216, 200, 27, 0.27)',
threshold2Color: 'rgba(234, 112, 112, 0.22)'
},
// show/hide lines
lines : true,
// fill factor
fill : 1,
// line width in pixels
linewidth : 2,
// show hide points
points : false,
// point radius in pixels
pointradius : 5,
// show hide bars
bars : false,
// enable/disable stacking
stack : false,
// stack percentage mode
percentage : false,
// legend options
legend: {
show: true, // disable/enable legend
values: false, // disable/enable legend values
min: false,
max: false,
current: false,
total: false,
avg: false
},
// how null points should be handled
nullPointMode : 'connected',
// staircase line mode
steppedLine: false,
// tooltip options
tooltip : {
value_type: 'cumulative',
shared: true,
},
// time overrides
timeFrom: null,
timeShift: null,
// metric queries
targets: [{}],
// series color overrides
aliasColors: {},
// other style overrides
seriesOverrides: [],
};
class GraphCtrl extends MetricsPanelCtrl {
hiddenSeries: any = {};
seriesList: any = [];
logScales: any;
unitFormats: any;
annotationsPromise: any;
datapointsCount: number;
datapointsOutside: boolean;
datapointsWarning: boolean;
colors: any = [];
/** @ngInject */
constructor($scope, $injector, private annotationsSrv) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
_.defaults(this.panel.tooltip, panelDefaults.tooltip);
_.defaults(this.panel.grid, panelDefaults.grid);
_.defaults(this.panel.legend, panelDefaults.legend);
this.colors = $scope.$root.colors;
}
initEditMode() {
super.initEditMode();
this.icon = "fa fa-bar-chart";
this.addEditorTab('Axes & Grid', 'public/app/plugins/panel/graph/axisEditor.html', 2);
this.addEditorTab('Display Styles', 'public/app/plugins/panel/graph/styleEditor.html', 3);
this.logScales = {
'linear': 1,
'log (base 2)': 2,
'log (base 10)': 10,
'log (base 32)': 32,
'log (base 1024)': 1024
};
this.unitFormats = kbn.getUnitFormats();
}
getExtendedMenu() {
var menu = super.getExtendedMenu();
menu.push({text: 'Export CSV', click: 'ctrl.exportCsv()'});
menu.push({text: 'Toggle legend', click: 'ctrl.toggleLegend()'});
return menu;
}
setUnitFormat(axis, subItem) {
this.panel.y_formats[axis] = subItem.value;
this.render();
}
refreshData(datasource) {
this.annotationsPromise = this.annotationsSrv.getAnnotations(this.dashboard);
return this.issueQueries(datasource)
.then(res => this.dataHandler(res))
.catch(err => {
this.seriesList = [];
this.render([]);
throw err;
});
}
zoomOut(evt) {
this.publishAppEvent('zoom-out', evt);
}
loadSnapshot(snapshotData) {
this.updateTimeRange();
this.annotationsPromise = this.annotationsSrv.getAnnotations(this.dashboard);
this.dataHandler(snapshotData);
}
dataHandler(results) {
// png renderer returns just a url
if (_.isString(results)) {
this.render(results);
return;
}
this.datapointsWarning = false;
this.datapointsCount = 0;
this.datapointsOutside = false;
this.seriesList = _.map(results.data, (series, i) => this.seriesHandler(series, i));
this.datapointsWarning = this.datapointsCount === 0 || this.datapointsOutside;
this.annotationsPromise.then(annotations => {
this.loading = false;
this.seriesList.annotations = annotations;
this.render(this.seriesList);
}, () => {
this.loading = false;
this.render(this.seriesList);
});
};
seriesHandler(seriesData, index) {
var datapoints = seriesData.datapoints;
var alias = seriesData.target;
var colorIndex = index % this.colors.length;
var color = this.panel.aliasColors[alias] || this.colors[colorIndex];
var series = new TimeSeries({
datapoints: datapoints,
alias: alias,
color: color,
});
if (datapoints && datapoints.length > 0) {
var last = moment.utc(datapoints[datapoints.length - 1][1]);
var from = moment.utc(this.range.from);
if (last - from < -10000) {
this.datapointsOutside = true;
}
this.datapointsCount += datapoints.length;
}
return series;
}
render(data?: any) {
this.broadcastRender(data);
}
changeSeriesColor(series, color) {
series.color = color;
this.panel.aliasColors[series.alias] = series.color;
this.render();
}
toggleSeries(serie, event) {
if (event.ctrlKey || event.metaKey || event.shiftKey) {
if (this.hiddenSeries[serie.alias]) {
delete this.hiddenSeries[serie.alias];
} else {
this.hiddenSeries[serie.alias] = true;
}
} else {
this.toggleSeriesExclusiveMode(serie);
}
this.render();
}
toggleSeriesExclusiveMode (serie) {
var hidden = this.hiddenSeries;
if (hidden[serie.alias]) {
delete hidden[serie.alias];
}
// check if every other series is hidden
var alreadyExclusive = _.every(this.seriesList, value => {
if (value.alias === serie.alias) {
return true;
}
return hidden[value.alias];
});
if (alreadyExclusive) {
// remove all hidden series
_.each(this.seriesList, value => {
delete this.hiddenSeries[value.alias];
});
} else {
// hide all but this serie
_.each(this.seriesList, value => {
if (value.alias === serie.alias) {
return;
}
this.hiddenSeries[value.alias] = true;
});
}
}
toggleYAxis(info) {
var override = _.findWhere(this.panel.seriesOverrides, { alias: info.alias });
if (!override) {
override = { alias: info.alias };
this.panel.seriesOverrides.push(override);
}
override.yaxis = info.yaxis === 2 ? 1 : 2;
this.render();
};
addSeriesOverride(override) {
this.panel.seriesOverrides.push(override || {});
}
removeSeriesOverride(override) {
this.panel.seriesOverrides = _.without(this.panel.seriesOverrides, override);
this.render();
}
// Called from panel menu
toggleLegend() {
this.panel.legend.show = !this.panel.legend.show;
this.refresh();
}
legendValuesOptionChanged() {
var legend = this.panel.legend;
legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total;
this.render();
}
exportCsv() {
fileExport.exportSeriesListToCsv(this.seriesList);
}
}
export {GraphCtrl}

View File

@ -6,6 +6,8 @@ function ($) {
function GraphTooltip(elem, dashboard, scope, getSeriesFn) {
var self = this;
var ctrl = scope.ctrl;
var panel = ctrl.panel;
var $tooltip = $('<div id="tooltip">');
@ -47,12 +49,12 @@ function ($) {
for (i = 0; i < seriesList.length; i++) {
series = seriesList[i];
if (!series.data.length || (scope.panel.legend.hideEmpty && series.allIsNull)) {
if (!series.data.length || (panel.legend.hideEmpty && series.allIsNull)) {
results.push({ hidden: true });
continue;
}
if (!series.data.length || (scope.panel.legend.hideZero && series.allIsZero)) {
if (!series.data.length || (panel.legend.hideZero && series.allIsZero)) {
results.push({ hidden: true });
continue;
}
@ -61,7 +63,7 @@ function ($) {
results.time = series.data[hoverIndex][0];
if (series.stack) {
if (scope.panel.tooltip.value_type === 'individual') {
if (panel.tooltip.value_type === 'individual') {
value = series.data[hoverIndex][1];
} else if (!series.stack) {
value = series.data[hoverIndex][1];
@ -89,7 +91,7 @@ function ($) {
};
elem.mouseleave(function () {
if (scope.panel.tooltip.shared) {
if (panel.tooltip.shared) {
var plot = elem.data().plot;
if (plot) {
$tooltip.detach();
@ -98,7 +100,7 @@ function ($) {
}
if (dashboard.sharedCrosshair) {
scope.appEvent('clearCrosshair');
ctrl.publishAppEvent('clearCrosshair');
}
});
@ -108,15 +110,15 @@ function ($) {
var seriesList = getSeriesFn();
var group, value, absoluteTime, relativeTime, hoverInfo, i, series, seriesHtml;
if(dashboard.sharedCrosshair){
scope.appEvent('setCrosshair', { pos: pos, scope: scope });
if (dashboard.sharedCrosshair) {
ctrl.publishAppEvent('setCrosshair', { pos: pos, scope: scope });
}
if (seriesList.length === 0) {
return;
}
if (scope.panel.tooltip.shared) {
if (panel.tooltip.shared) {
plot.unhighlight();
var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(plotData, pos);
@ -151,7 +153,7 @@ function ($) {
group = '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
group += '<i class="fa fa-minus" style="color:' + item.series.color +';"></i> ' + series.label + ':</div>';
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
if (panel.stack && panel.tooltip.value_type === 'individual') {
value = item.datapoint[1] - item.datapoint[2];
}
else {

View File

@ -8,7 +8,7 @@ define([
function (angular, _, $) {
'use strict';
var module = angular.module('grafana.panels.graph');
var module = angular.module('grafana.directives');
module.directive('graphLegend', function(popoverSrv) {
@ -16,13 +16,14 @@ function (angular, _, $) {
link: function(scope, elem) {
var $container = $('<section class="graph-legend"></section>');
var firstRender = true;
var panel = scope.panel;
var ctrl = scope.ctrl;
var panel = ctrl.panel;
var data;
var seriesList;
var i;
scope.$on('render', function() {
data = scope.seriesList;
data = ctrl.seriesList;
if (data) {
render();
}
@ -54,7 +55,7 @@ function (angular, _, $) {
var el = $(e.currentTarget);
var index = getSeriesIndexForElement(el);
var seriesInfo = seriesList[index];
scope.toggleSeries(seriesInfo, e);
ctrl.toggleSeries(seriesInfo, e);
}
function sortLegend(e) {
@ -148,7 +149,7 @@ function (angular, _, $) {
var html = '<div class="graph-legend-series';
if (series.yaxis === 2) { html += ' pull-right'; }
if (scope.hiddenSeries[series.alias]) { html += ' graph-legend-series-hidden'; }
if (ctrl.hiddenSeries[series.alias]) { html += ' graph-legend-series-hidden'; }
html += '" data-series-index="' + i + '">';
html += '<div class="graph-legend-icon">';
html += '<i class="fa fa-minus pointer" style="color:' + series.color + '"></i>';

View File

@ -3,12 +3,12 @@
<div class="editor-row small" style="padding-bottom: 0;">
<label>Axis:</label>
<button ng-click="toggleYAxis(series);dismiss();"
<button ng-click="ctrl.toggleYAxis(series);dismiss();"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 1 }">
Left
</button>
<button ng-click="toggleYAxis(series);dismiss();"
<button ng-click="ctrl.toggleYAxis(series);dismiss();"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 2 }">
Right
@ -16,10 +16,10 @@
</div>
<div class="editor-row">
<i ng-repeat="color in colors" class="pointer"
<i ng-repeat="color in ctrl.colors" class="pointer"
ng-class="{'fa fa-circle-o': color === series.color,'fa fa-circle': color !== series.color}"
ng-style="{color:color}"
ng-click="changeSeriesColor(series, color);dismiss();">&nbsp;</i>
ng-click="ctrl.changeSeriesColor(series, color);dismiss();">&nbsp;</i>
</div>
</div>

View File

@ -1,3 +0,0 @@
declare var panel: any;
declare var GraphCtrl: any;
export {panel, GraphCtrl};

View File

@ -1,25 +1,22 @@
<grafana-panel>
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
<div class="graph-canvas-wrapper">
<div ng-if="datapointsWarning" class="datapoints-warning">
<span class="small" ng-show="!datapointsCount">
No datapoints <tip>No datapoints returned from metric query</tip>
</span>
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
</div>
<div grafana-graph class="histogram-chart" ng-dblclick="zoomOut()">
</div>
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': ctrl.panel.legend.rightSide}">
<div class="graph-canvas-wrapper">
<div ng-if="datapointsWarning" class="datapoints-warning">
<span class="small" ng-show="!datapointsCount">
No datapoints <tip>No datapoints returned from metric query</tip>
</span>
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
</div>
<div grafana-graph class="histogram-chart" ng-dblclick="ctrl.zoomOut()">
</div>
<div class="graph-legend-wrapper" ng-if="panel.legend.show" graph-legend></div>
</div>
<div class="clearfix"></div>
<div class="graph-legend-wrapper" ng-if="ctrl.panel.legend.show" graph-legend></div>
</div>
<div class="clearfix"></div>
</grafana-panel>

View File

@ -1,303 +0,0 @@
define([
'angular',
'lodash',
'moment',
'app/core/utils/kbn',
'app/core/utils/file_export',
'app/core/time_series',
'app/features/panel/panel_meta',
'./seriesOverridesCtrl',
'./graph',
'./legend',
],
function (angular, _, moment, kbn, fileExport, TimeSeries, PanelMeta) {
'use strict';
/** @ngInject */
function GraphCtrl($scope, $rootScope, panelSrv, annotationsSrv, panelHelper) {
$scope.panelMeta = new PanelMeta({
panelName: 'Graph',
editIcon: "fa fa-bar-chart",
fullscreen: true,
metricsEditor: true,
});
$scope.panelMeta.addEditorTab('Axes & Grid', 'app/plugins/panel/graph/axisEditor.html');
$scope.panelMeta.addEditorTab('Display Styles', 'app/plugins/panel/graph/styleEditor.html');
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
$scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()');
$scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()');
// Set and populate defaults
var _d = {
// datasource name, null = default datasource
datasource: null,
// sets client side (flot) or native graphite png renderer (png)
renderer: 'flot',
// Show/hide the x-axis
'x-axis' : true,
// Show/hide y-axis
'y-axis' : true,
// y axis formats, [left axis,right axis]
y_formats : ['short', 'short'],
// grid options
grid : {
leftLogBase: 1,
leftMax: null,
rightMax: null,
leftMin: null,
rightMin: null,
rightLogBase: 1,
threshold1: null,
threshold2: null,
threshold1Color: 'rgba(216, 200, 27, 0.27)',
threshold2Color: 'rgba(234, 112, 112, 0.22)'
},
// show/hide lines
lines : true,
// fill factor
fill : 1,
// line width in pixels
linewidth : 2,
// show hide points
points : false,
// point radius in pixels
pointradius : 5,
// show hide bars
bars : false,
// enable/disable stacking
stack : false,
// stack percentage mode
percentage : false,
// legend options
legend: {
show: true, // disable/enable legend
values: false, // disable/enable legend values
min: false,
max: false,
current: false,
total: false,
avg: false
},
// how null points should be handled
nullPointMode : 'connected',
// staircase line mode
steppedLine: false,
// tooltip options
tooltip : {
value_type: 'cumulative',
shared: true,
},
// time overrides
timeFrom: null,
timeShift: null,
// metric queries
targets: [{}],
// series color overrides
aliasColors: {},
// other style overrides
seriesOverrides: [],
};
_.defaults($scope.panel,_d);
_.defaults($scope.panel.tooltip, _d.tooltip);
_.defaults($scope.panel.annotate, _d.annotate);
_.defaults($scope.panel.grid, _d.grid);
_.defaults($scope.panel.legend, _d.legend);
$scope.logScales = {'linear': 1, 'log (base 2)': 2, 'log (base 10)': 10, 'log (base 32)': 32, 'log (base 1024)': 1024};
$scope.hiddenSeries = {};
$scope.seriesList = [];
$scope.unitFormats = kbn.getUnitFormats();
$scope.setUnitFormat = function(axis, subItem) {
$scope.panel.y_formats[axis] = subItem.value;
$scope.render();
};
$scope.refreshData = function(datasource) {
panelHelper.updateTimeRange($scope);
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.dashboard);
return panelHelper.issueMetricQuery($scope, datasource)
.then($scope.dataHandler, function(err) {
$scope.seriesList = [];
$scope.render([]);
throw err;
});
};
$scope.zoomOut = function(evt) {
$scope.appEvent('zoom-out', evt);
};
$scope.loadSnapshot = function(snapshotData) {
panelHelper.updateTimeRange($scope);
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.dashboard);
$scope.dataHandler(snapshotData);
};
$scope.dataHandler = function(results) {
// png renderer returns just a url
if (_.isString(results)) {
$scope.render(results);
return;
}
$scope.datapointsWarning = false;
$scope.datapointsCount = 0;
$scope.datapointsOutside = false;
$scope.seriesList = _.map(results.data, $scope.seriesHandler);
$scope.datapointsWarning = $scope.datapointsCount === 0 || $scope.datapointsOutside;
$scope.annotationsPromise
.then(function(annotations) {
$scope.panelMeta.loading = false;
$scope.seriesList.annotations = annotations;
$scope.render($scope.seriesList);
}, function() {
$scope.panelMeta.loading = false;
$scope.render($scope.seriesList);
});
};
$scope.seriesHandler = function(seriesData, index) {
var datapoints = seriesData.datapoints;
var alias = seriesData.target;
var colorIndex = index % $rootScope.colors.length;
var color = $scope.panel.aliasColors[alias] || $rootScope.colors[colorIndex];
var series = new TimeSeries({
datapoints: datapoints,
alias: alias,
color: color,
});
if (datapoints && datapoints.length > 0) {
var last = moment.utc(datapoints[datapoints.length - 1][1]);
var from = moment.utc($scope.range.from);
if (last - from < -10000) {
$scope.datapointsOutside = true;
}
$scope.datapointsCount += datapoints.length;
}
return series;
};
$scope.render = function(data) {
panelHelper.broadcastRender($scope, data);
};
$scope.changeSeriesColor = function(series, color) {
series.color = color;
$scope.panel.aliasColors[series.alias] = series.color;
$scope.render();
};
$scope.toggleSeries = function(serie, event) {
if (event.ctrlKey || event.metaKey || event.shiftKey) {
if ($scope.hiddenSeries[serie.alias]) {
delete $scope.hiddenSeries[serie.alias];
}
else {
$scope.hiddenSeries[serie.alias] = true;
}
} else {
$scope.toggleSeriesExclusiveMode(serie);
}
$scope.render();
};
$scope.toggleSeriesExclusiveMode = function(serie) {
var hidden = $scope.hiddenSeries;
if (hidden[serie.alias]) {
delete hidden[serie.alias];
}
// check if every other series is hidden
var alreadyExclusive = _.every($scope.seriesList, function(value) {
if (value.alias === serie.alias) {
return true;
}
return hidden[value.alias];
});
if (alreadyExclusive) {
// remove all hidden series
_.each($scope.seriesList, function(value) {
delete $scope.hiddenSeries[value.alias];
});
}
else {
// hide all but this serie
_.each($scope.seriesList, function(value) {
if (value.alias === serie.alias) {
return;
}
$scope.hiddenSeries[value.alias] = true;
});
}
};
$scope.toggleYAxis = function(info) {
var override = _.findWhere($scope.panel.seriesOverrides, { alias: info.alias });
if (!override) {
override = { alias: info.alias };
$scope.panel.seriesOverrides.push(override);
}
override.yaxis = info.yaxis === 2 ? 1 : 2;
$scope.render();
};
$scope.addSeriesOverride = function(override) {
$scope.panel.seriesOverrides.push(override || {});
};
$scope.removeSeriesOverride = function(override) {
$scope.panel.seriesOverrides = _.without($scope.panel.seriesOverrides, override);
$scope.render();
};
// Called from panel menu
$scope.toggleLegend = function() {
$scope.panel.legend.show = !$scope.panel.legend.show;
$scope.get_data();
};
$scope.legendValuesOptionChanged = function() {
var legend = $scope.panel.legend;
legend.values = legend.min || legend.max || legend.avg || legend.current || legend.total;
$scope.render();
};
$scope.exportCsv = function() {
fileExport.exportSeriesListToCsv($scope.seriesList);
};
panelSrv.init($scope);
}
function graphPanelDirective() {
return {
controller: GraphCtrl,
templateUrl: 'app/plugins/panel/graph/module.html',
};
}
return {
GraphCtrl: GraphCtrl,
panel: graphPanelDirective,
};
});

View File

@ -0,0 +1,17 @@
import {PanelDirective} from '../../../features/panel/panel';
import {GraphCtrl} from './graph_ctrl';
import './graph';
import './legend';
import './seriesOverridesCtrl';
class GraphPanel extends PanelDirective {
controller = GraphCtrl;
templateUrl = 'public/app/plugins/panel/graph/module.html';
}
export {
GraphPanel,
GraphPanel as Panel
}

View File

@ -1,13 +1,11 @@
define([
'angular',
'jquery',
'app/app',
'lodash',
], function(angular, jquery, app, _) {
], function(angular, jquery, _) {
'use strict';
var module = angular.module('grafana.panels.graph', []);
app.useModule(module);
var module = angular.module('grafana.controllers');
module.controller('SeriesOverridesCtrl', function($scope, $element, popoverSrv) {
$scope.overrideMenu = [];
@ -45,18 +43,19 @@ define([
}
$scope.updateCurrentOverrides();
$scope.render();
$scope.ctrl.render();
};
$scope.colorSelected = function(color) {
$scope.override['color'] = color;
$scope.updateCurrentOverrides();
$scope.render();
$scope.ctrl.render();
};
$scope.openColorSelector = function() {
var popoverScope = $scope.$new();
popoverScope.colorSelected = $scope.colorSelected;
popoverScope.colors = $scope.ctrl.colors;
popoverSrv.show({
element: $element.find(".dropdown"),
@ -69,11 +68,11 @@ define([
$scope.removeOverride = function(option) {
delete $scope.override[option.propertyName];
$scope.updateCurrentOverrides();
$scope.render();
$scope.ctrl.render();
};
$scope.getSeriesNames = function() {
return _.map($scope.seriesList, function(series) {
return _.map($scope.ctrl.seriesList, function(series) {
return series.alias;
});
};
@ -107,7 +106,5 @@ define([
$scope.addOverrideOption('Transform', 'transform', ['negative-Y']);
$scope.addOverrideOption('Legend', 'legend', [true, false]);
$scope.updateCurrentOverrides();
});
});

View File

@ -2,15 +2,10 @@
import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
import 'app/features/panel/panel_srv';
import 'app/features/panel/panel_helper';
import angular from 'angular';
import {GraphCtrl} from '../module';
import {GraphCtrl} from '../graph_ctrl';
import helpers from '../../../../../test/specs/helpers';
angular.module('grafana.controllers').controller('GraphCtrl', GraphCtrl);
describe('GraphCtrl', function() {
var ctx = new helpers.ControllerTestContext();
@ -18,7 +13,7 @@ describe('GraphCtrl', function() {
beforeEach(angularMocks.module('grafana.controllers'));
beforeEach(ctx.providePhase());
beforeEach(ctx.createControllerPhase('GraphCtrl'));
beforeEach(ctx.createPanelController(GraphCtrl));
describe('get_data with 2 series', function() {
beforeEach(function() {
@ -29,25 +24,23 @@ describe('GraphCtrl', function() {
{ target: 'test.cpu2', datapoints: [[1, 10]]}
]
}));
ctx.scope.render = sinon.spy();
ctx.scope.refreshData(ctx.datasource);
ctx.ctrl.render = sinon.spy();
ctx.ctrl.refreshData(ctx.datasource);
ctx.scope.$digest();
});
it('should send time series to render', function() {
var data = ctx.scope.render.getCall(0).args[0];
var data = ctx.ctrl.render.getCall(0).args[0];
expect(data.length).to.be(2);
});
describe('get_data failure following success', function() {
beforeEach(function() {
ctx.datasource.query = sinon.stub().returns(ctx.$q.reject('Datasource Error'));
ctx.scope.refreshData(ctx.datasource);
ctx.ctrl.refreshData(ctx.datasource);
ctx.scope.$digest();
});
});
});
});

View File

@ -24,11 +24,13 @@ describe('grafanaGraph', function() {
}));
beforeEach(angularMocks.inject(function($rootScope, $compile) {
var ctrl: any = {};
var scope = $rootScope.$new();
scope.ctrl = ctrl;
var element = angular.element("<div style='width:" + elementWidth + "px' grafana-graph><div>");
scope.height = '200px';
scope.panel = {
ctrl.height = '200px';
ctrl.panel = {
legend: {},
grid: { },
y_formats: [],
@ -38,14 +40,14 @@ describe('grafanaGraph', function() {
}
};
scope.panelRenderingComplete = sinon.spy();
scope.appEvent = sinon.spy();
scope.onAppEvent = sinon.spy();
scope.hiddenSeries = {};
scope.dashboard = { timezone: 'browser' };
scope.range = {
$rootScope.onAppEvent = sinon.spy();
ctrl.otherPanelInFullscreenMode = sinon.spy();
ctrl.renderingCompleted = sinon.spy();
ctrl.hiddenSeries = {};
ctrl.dashboard = { timezone: 'browser' };
ctrl.range = {
from: moment([2015, 1, 1, 10]),
to: moment([2015, 1, 1, 22])
to: moment([2015, 1, 1, 22]),
};
ctx.data = [];
ctx.data.push(new TimeSeries({
@ -57,7 +59,7 @@ describe('grafanaGraph', function() {
alias: 'series2'
}));
setupFunc(scope, ctx.data);
setupFunc(ctrl, ctx.data);
$compile(element)(scope);
scope.$digest();
@ -74,11 +76,11 @@ describe('grafanaGraph', function() {
}
graphScenario('simple lines options', function(ctx) {
ctx.setup(function(scope) {
scope.panel.lines = true;
scope.panel.fill = 5;
scope.panel.linewidth = 3;
scope.panel.steppedLine = true;
ctx.setup(function(ctrl) {
ctrl.panel.lines = true;
ctrl.panel.fill = 5;
ctrl.panel.linewidth = 3;
ctrl.panel.steppedLine = true;
});
it('should configure plot with correct options', function() {
@ -90,8 +92,8 @@ describe('grafanaGraph', function() {
});
graphScenario('grid thresholds 100, 200', function(ctx) {
ctx.setup(function(scope) {
scope.panel.grid = {
ctx.setup(function(ctrl) {
ctrl.panel.grid = {
threshold1: 100,
threshold1Color: "#111",
threshold2: 200,
@ -110,8 +112,8 @@ describe('grafanaGraph', function() {
});
graphScenario('inverted grid thresholds 200, 100', function(ctx) {
ctx.setup(function(scope) {
scope.panel.grid = {
ctx.setup(function(ctrl) {
ctrl.panel.grid = {
threshold1: 200,
threshold1Color: "#111",
threshold2: 100,
@ -130,8 +132,8 @@ describe('grafanaGraph', function() {
});
graphScenario('grid thresholds from zero', function(ctx) {
ctx.setup(function(scope) {
scope.panel.grid = {
ctx.setup(function(ctrl) {
ctrl.panel.grid = {
threshold1: 0,
threshold1Color: "#111",
};
@ -144,8 +146,8 @@ describe('grafanaGraph', function() {
});
graphScenario('when logBase is log 10', function(ctx) {
ctx.setup(function(scope) {
scope.panel.grid = {
ctx.setup(function(ctrl) {
ctrl.panel.grid = {
leftMax: null,
rightMax: null,
leftMin: null,
@ -163,8 +165,8 @@ describe('grafanaGraph', function() {
});
graphScenario('should use timeStep for barWidth', function(ctx) {
ctx.setup(function(scope, data) {
scope.panel.bars = true;
ctx.setup(function(ctrl, data) {
ctrl.panel.bars = true;
data[0] = new TimeSeries({
datapoints: [[1,10],[2,20]],
alias: 'series1',
@ -177,10 +179,10 @@ describe('grafanaGraph', function() {
});
graphScenario('series option overrides, fill & points', function(ctx) {
ctx.setup(function(scope, data) {
scope.panel.lines = true;
scope.panel.fill = 5;
scope.panel.seriesOverrides = [
ctx.setup(function(ctrl, data) {
ctrl.panel.lines = true;
ctrl.panel.fill = 5;
ctrl.panel.seriesOverrides = [
{ alias: 'test', fill: 0, points: true }
];
@ -195,8 +197,8 @@ describe('grafanaGraph', function() {
});
graphScenario('should order series order according to zindex', function(ctx) {
ctx.setup(function(scope) {
scope.panel.seriesOverrides = [{ alias: 'series1', zindex: 2 }];
ctx.setup(function(ctrl) {
ctrl.panel.seriesOverrides = [{ alias: 'series1', zindex: 2 }];
});
it('should move zindex 2 last', function() {
@ -206,8 +208,8 @@ describe('grafanaGraph', function() {
});
graphScenario('when series is hidden', function(ctx) {
ctx.setup(function(scope) {
scope.hiddenSeries = {'series2': true};
ctx.setup(function(ctrl) {
ctrl.hiddenSeries = {'series2': true};
});
it('should remove datapoints and disable stack', function() {
@ -218,9 +220,9 @@ describe('grafanaGraph', function() {
});
graphScenario('when stack and percent', function(ctx) {
ctx.setup(function(scope) {
scope.panel.percentage = true;
scope.panel.stack = true;
ctx.setup(function(ctrl) {
ctrl.panel.percentage = true;
ctrl.panel.stack = true;
});
it('should show percentage', function() {
@ -231,9 +233,9 @@ describe('grafanaGraph', function() {
graphScenario('when panel too narrow to show x-axis dates in same granularity as wide panels', function(ctx) {
describe('and the range is less than 24 hours', function() {
ctx.setup(function(scope) {
scope.range.from = moment([2015, 1, 1, 10]);
scope.range.to = moment([2015, 1, 1, 22]);
ctx.setup(function(ctrl) {
ctrl.range.from = moment([2015, 1, 1, 10]);
ctrl.range.to = moment([2015, 1, 1, 22]);
});
it('should format dates as hours minutes', function() {

View File

@ -8,6 +8,7 @@ import GraphTooltip from '../graph_tooltip';
var scope = {
appEvent: sinon.spy(),
onAppEvent: sinon.spy(),
ctrl: {}
};
var elem = $('<div></div>');
@ -15,8 +16,8 @@ var dashboard = { };
function describeSharedTooltip(desc, fn) {
var ctx: any = {};
ctx.scope = scope;
ctx.scope.panel = {
ctx.ctrl = scope.ctrl;
ctx.ctrl.panel = {
tooltip: {
shared: true
},
@ -51,9 +52,11 @@ describeSharedTooltip("steppedLine false, stack false", function(ctx) {
it('should return 2 series', function() {
expect(ctx.results.length).to.be(2);
});
it('should add time to results array', function() {
expect(ctx.results.time).to.be(10);
});
it('should set value and hoverIndex', function() {
expect(ctx.results[0].value).to.be(15);
expect(ctx.results[1].value).to.be(2);
@ -93,7 +96,7 @@ describeSharedTooltip("steppedLine false, stack true, individual false", functio
stack: true
}
];
ctx.scope.panel.stack = true;
ctx.ctrl.panel.stack = true;
ctx.pos = { x: 11 };
});
@ -124,7 +127,7 @@ describeSharedTooltip("steppedLine false, stack true, individual false, series s
stack: false
}
];
ctx.scope.panel.stack = true;
ctx.ctrl.panel.stack = true;
ctx.pos = { x: 11 };
});
@ -156,15 +159,14 @@ describeSharedTooltip("steppedLine false, stack true, individual true", function
stack: false
}
];
ctx.scope.panel.stack = true;
ctx.scope.panel.tooltip.value_type = 'individual';
ctx.ctrl.panel.stack = true;
ctx.ctrl.panel.tooltip.value_type = 'individual';
ctx.pos = { x: 11 };
});
it('should not show stacked value', function() {
expect(ctx.results[1].value).to.be(2);
});
});

View File

@ -1,60 +1,60 @@
<div class="editor-row">
<div class="section">
<h5>Chart Options</h5>
<editor-opt-bool text="Bars" model="panel.bars" change="render()"></editor-opt-bool>
<editor-opt-bool text="Lines" model="panel.lines" change="render()"></editor-opt-bool>
<editor-opt-bool text="Points" model="panel.points" change="render()"></editor-opt-bool>
<editor-opt-bool text="Bars" model="ctrl.panel.bars" change="ctrl.render()"></editor-opt-bool>
<editor-opt-bool text="Lines" model="ctrl.panel.lines" change="ctrl.render()"></editor-opt-bool>
<editor-opt-bool text="Points" model="ctrl.panel.points" change="ctrl.render()"></editor-opt-bool>
</div>
<div class="section">
<h5>Line options</h5>
<div class="editor-option" ng-show="panel.lines">
<div class="editor-option" ng-show="ctrl.panel.lines">
<label class="small">Line Fill</label>
<select class="input-mini" ng-model="panel.fill" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
<select class="input-mini" ng-model="ctrl.panel.fill" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select>
</div>
<div class="editor-option" ng-show="panel.lines">
<div class="editor-option" ng-show="ctrl.panel.lines">
<label class="small">Line Width</label>
<select class="input-mini" ng-model="panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
<select class="input-mini" ng-model="ctrl.panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select>
</div>
<div class="editor-option" ng-show="panel.points">
<div class="editor-option" ng-show="ctrl.panel.points">
<label class="small">Point Radius</label>
<select class="input-mini" ng-model="panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
<select class="input-mini" ng-model="ctrl.panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select>
</div>
<div class="editor-option">
<label class="small">Null point mode<tip>Define how null values should be drawn</tip></label>
<select class="input-medium" ng-model="panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="render()"></select>
<select class="input-medium" ng-model="ctrl.panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="ctrl.render()"></select>
</div>
<editor-opt-bool text="Staircase line" model="panel.steppedLine" change="render()"></editor-opt-bool>
<editor-opt-bool text="Staircase line" model="ctrl.panel.steppedLine" change="ctrl.render()"></editor-opt-bool>
</div>
<div class="section">
<h5>Multiple Series</h5>
<editor-opt-bool text="Stack" model="panel.stack" change="render()"></editor-opt-bool>
<editor-opt-bool text="Percent" model="panel.percentage" change="render()" tip="Stack as a percentage of total"></editor-opt-bool>
<editor-opt-bool text="Stack" model="ctrl.panel.stack" change="ctrl.render()"></editor-opt-bool>
<editor-opt-bool text="Percent" model="ctrl.panel.percentage" change="ctrl.render()" tip="Stack as a percentage of total"></editor-opt-bool>
</div>
<div class="section">
<h5>Rendering</h5>
<div class="editor-option">
<label class="small">Flot <tip>client side</tip></label>
<input type="radio" class="input-small" ng-model="panel.renderer" value="flot" ng-change="get_data()" />
<input type="radio" class="input-small" ng-model="ctrl.panel.renderer" value="flot" ng-change="ctrl.refresh()" />
</div>
<div class="editor-option">
<label class="small">Graphite PNG <tip>server side</tip></label>
<input type="radio" class="input-small" ng-model="panel.renderer" value="png" ng-change="get_data()" />
<input type="radio" class="input-small" ng-model="ctrl.panel.renderer" value="png" ng-change="ctr.refresh()" />
</div>
</div>
<div class="section">
<h5>Tooltip</h5>
<editor-opt-bool
text="All series" model="panel.tooltip.shared" change="render()"
text="All series" model="ctrl.panel.tooltip.shared" change="ctrl.render()"
tip="Show all series on same tooltip and a x croshair to help follow all series">
</editor-opt-bool>
<div class="editor-option" ng-show="panel.stack">
<div class="editor-option" ng-show="ctrl.panel.stack">
<label class="small">Stacked Values <tip>How should the values in stacked charts to be calculated?</tip></label>
<select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="render()"></select>
<select class="input-small" ng-model="ctrl.panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="ctrl.render()"></select>
</div>
</div>
</div>
@ -64,10 +64,10 @@
<div class="section">
<h5>Series specific overrides <tip>Regex match example: /server[0-3]/i </tip></h5>
<div class="tight-form-container">
<div class="tight-form" ng-repeat="override in panel.seriesOverrides" ng-controller="SeriesOverridesCtrl">
<div class="tight-form" ng-repeat="override in ctrl.panel.seriesOverrides" ng-controller="SeriesOverridesCtrl">
<ul class="tight-form-list">
<li class="tight-form-item">
<i class="fa fa-remove pointer" ng-click="removeSeriesOverride(override)"></i>
<i class="fa fa-remove pointer" ng-click="ctrl.removeSeriesOverride(override)"></i>
</li>
<li class="tight-form-item">
@ -75,7 +75,7 @@
</li>
<li>
<input type="text" ng-model="override.alias" bs-typeahead="getSeriesNames" ng-blur="render()" data-min-length=0 data-items=100 class="input-medium tight-form-input" >
<input type="text" ng-model="override.alias" bs-typeahead="getSeriesNames" ng-blur="ctrl.render()" data-min-length=0 data-items=100 class="input-medium tight-form-input" >
</li>
<li class="tight-form-item" ng-repeat="option in currentOverrides">
@ -95,7 +95,7 @@
</div>
</div>
<button class="btn btn-inverse" style="margin-top: 20px" ng-click="addSeriesOverride()">
<button class="btn btn-inverse" style="margin-top: 20px" ng-click="ctrl.addSeriesOverride()">
Add series specific option
</button>
</div>

View File

@ -3,240 +3,231 @@
import angular from 'angular';
import _ from 'lodash';
import kbn from 'app/core/utils/kbn';
import PanelMeta from 'app/features/panel/panel_meta2';
import TimeSeries from '../../../core/time_series2';
import {MetricsPanelCtrl} from '../../../features/panel/panel';
export class SingleStatCtrl {
// Set and populate defaults
var panelDefaults = {
links: [],
datasource: null,
maxDataPoints: 100,
interval: null,
targets: [{}],
cacheTimeout: null,
format: 'none',
prefix: '',
postfix: '',
nullText: null,
valueMaps: [
{ value: 'null', op: '=', text: 'N/A' }
],
nullPointMode: 'connected',
valueName: 'avg',
prefixFontSize: '50%',
valueFontSize: '80%',
postfixFontSize: '50%',
thresholds: '',
colorBackground: false,
colorValue: false,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
sparkline: {
show: false,
full: false,
lineColor: 'rgb(31, 120, 193)',
fillColor: 'rgba(31, 118, 189, 0.18)',
}
};
export class SingleStatCtrl extends MetricsPanelCtrl {
series: any[];
data: any[];
fontSizes: any[];
unitFormats: any[];
/** @ngInject */
constructor($scope, panelSrv, panelHelper) {
$scope.panelMeta = new PanelMeta({
panelName: 'Singlestat',
editIcon: "fa fa-dashboard",
fullscreen: true,
metricsEditor: true
constructor($scope, $injector) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
}
initEditMode() {
super.initEditMode();
this.icon = "fa fa-dashboard";
this.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
this.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html', 2);
this.unitFormats = kbn.getUnitFormats();
}
setUnitFormat(subItem) {
this.panel.format = subItem.value;
this.render();
}
refreshData(datasource) {
return this.issueQueries(datasource)
.then(this.dataHandler.bind(this))
.catch(err => {
this.series = [];
this.render();
throw err;
});
}
loadSnapshot(snapshotData) {
this.updateTimeRange();
this.dataHandler(snapshotData);
}
dataHandler(results) {
this.series = _.map(results.data, this.seriesHandler.bind(this));
this.render();
}
seriesHandler(seriesData) {
var series = new TimeSeries({
datapoints: seriesData.datapoints,
alias: seriesData.target,
});
$scope.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
series.flotpairs = series.getFlotPairs(this.panel.nullPointMode);
return series;
}
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html');
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
setColoring(options) {
if (options.background) {
this.panel.colorValue = false;
this.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)'];
} else {
this.panel.colorBackground = false;
this.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)'];
}
this.render();
}
// Set and populate defaults
var _d = {
links: [],
datasource: null,
maxDataPoints: 100,
interval: null,
targets: [{}],
cacheTimeout: null,
format: 'none',
prefix: '',
postfix: '',
nullText: null,
valueMaps: [
{ value: 'null', op: '=', text: 'N/A' }
],
nullPointMode: 'connected',
valueName: 'avg',
prefixFontSize: '50%',
valueFontSize: '80%',
postfixFontSize: '50%',
thresholds: '',
colorBackground: false,
colorValue: false,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
sparkline: {
show: false,
full: false,
lineColor: 'rgb(31, 120, 193)',
fillColor: 'rgba(31, 118, 189, 0.18)',
invertColorOrder() {
var tmp = this.panel.colors[0];
this.panel.colors[0] = this.panel.colors[2];
this.panel.colors[2] = tmp;
this.render();
}
getDecimalsForValue(value) {
if (_.isNumber(this.panel.decimals)) {
return {decimals: this.panel.decimals, scaledDecimals: null};
}
var delta = value / 2;
var dec = -Math.floor(Math.log(delta) / Math.LN10);
var magn = Math.pow(10, -dec),
norm = delta / magn, // norm is between 1.0 and 10.0
size;
if (norm < 1.5) {
size = 1;
} else if (norm < 3) {
size = 2;
// special case for 2.5, requires an extra decimal
if (norm > 2.25) {
size = 2.5;
++dec;
}
};
} else if (norm < 7.5) {
size = 5;
} else {
size = 10;
}
_.defaults($scope.panel, _d);
size *= magn;
$scope.unitFormats = kbn.getUnitFormats();
// reduce starting decimals if not needed
if (Math.floor(value) === value) { dec = 0; }
$scope.setUnitFormat = function(subItem) {
$scope.panel.format = subItem.value;
$scope.render();
};
var result: any = {};
result.decimals = Math.max(0, dec);
result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
$scope.init = function() {
panelSrv.init($scope);
};
return result;
}
$scope.refreshData = function(datasource) {
panelHelper.updateTimeRange($scope);
render() {
var data: any = {};
this.setValues(data);
return panelHelper.issueMetricQuery($scope, datasource)
.then($scope.dataHandler, function(err) {
$scope.series = [];
$scope.render();
throw err;
});
};
data.thresholds = this.panel.thresholds.split(',').map(function(strVale) {
return Number(strVale.trim());
});
$scope.loadSnapshot = function(snapshotData) {
panelHelper.updateTimeRange($scope);
$scope.dataHandler(snapshotData);
};
data.colorMap = this.panel.colors;
$scope.dataHandler = function(results) {
$scope.series = _.map(results.data, $scope.seriesHandler);
$scope.render();
};
this.data = data;
this.broadcastRender();
}
$scope.seriesHandler = function(seriesData) {
var series = new TimeSeries({
datapoints: seriesData.datapoints,
alias: seriesData.target,
});
setValues(data) {
data.flotpairs = [];
series.flotpairs = series.getFlotPairs($scope.panel.nullPointMode);
if (this.series.length > 1) {
this.inspector.error = new Error();
this.inspector.error.message = 'Multiple Series Error';
this.inspector.error.data = 'Metric query returns ' + this.series.length +
' series. Single Stat Panel expects a single series.\n\nResponse:\n'+JSON.stringify(this.series);
throw this.inspector.error;
}
return series;
};
if (this.series && this.series.length > 0) {
var lastPoint = _.last(this.series[0].datapoints);
var lastValue = _.isArray(lastPoint) ? lastPoint[0] : null;
$scope.setColoring = function(options) {
if (options.background) {
$scope.panel.colorValue = false;
$scope.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)'];
if (_.isString(lastValue)) {
data.value = 0;
data.valueFormated = lastValue;
data.valueRounded = 0;
} else {
$scope.panel.colorBackground = false;
$scope.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)'];
data.value = this.series[0].stats[this.panel.valueName];
data.flotpairs = this.series[0].flotpairs;
var decimalInfo = this.getDecimalsForValue(data.value);
var formatFunc = kbn.valueFormats[this.panel.format];
data.valueFormated = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals);
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
}
$scope.render();
};
}
$scope.invertColorOrder = function() {
var tmp = $scope.panel.colors[0];
$scope.panel.colors[0] = $scope.panel.colors[2];
$scope.panel.colors[2] = tmp;
$scope.render();
};
$scope.getDecimalsForValue = function(value) {
if (_.isNumber($scope.panel.decimals)) {
return { decimals: $scope.panel.decimals, scaledDecimals: null };
}
var delta = value / 2;
var dec = -Math.floor(Math.log(delta) / Math.LN10);
var magn = Math.pow(10, -dec),
norm = delta / magn, // norm is between 1.0 and 10.0
size;
if (norm < 1.5) {
size = 1;
} else if (norm < 3) {
size = 2;
// special case for 2.5, requires an extra decimal
if (norm > 2.25) {
size = 2.5;
++dec;
}
} else if (norm < 7.5) {
size = 5;
} else {
size = 10;
}
size *= magn;
// reduce starting decimals if not needed
if (Math.floor(value) === value) { dec = 0; }
var result: any = {};
result.decimals = Math.max(0, dec);
result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
return result;
};
$scope.render = function() {
var data: any = {};
$scope.setValues(data);
data.thresholds = $scope.panel.thresholds.split(',').map(function(strVale) {
return Number(strVale.trim());
});
data.colorMap = $scope.panel.colors;
$scope.data = data;
$scope.$broadcast('render');
};
$scope.setValues = function(data) {
data.flotpairs = [];
if ($scope.series.length > 1) {
$scope.inspector.error = new Error();
$scope.inspector.error.message = 'Multiple Series Error';
$scope.inspector.error.data = 'Metric query returns ' + $scope.series.length +
' series. Single Stat Panel expects a single series.\n\nResponse:\n'+JSON.stringify($scope.series);
throw $scope.inspector.error;
}
if ($scope.series && $scope.series.length > 0) {
var lastPoint = _.last($scope.series[0].datapoints);
var lastValue = _.isArray(lastPoint) ? lastPoint[0] : null;
if (_.isString(lastValue)) {
data.value = 0;
data.valueFormated = lastValue;
data.valueRounded = 0;
} else {
data.value = $scope.series[0].stats[$scope.panel.valueName];
data.flotpairs = $scope.series[0].flotpairs;
var decimalInfo = $scope.getDecimalsForValue(data.value);
var formatFunc = kbn.valueFormats[$scope.panel.format];
data.valueFormated = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals);
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
}
}
// check value to text mappings
for (var i = 0; i < $scope.panel.valueMaps.length; i++) {
var map = $scope.panel.valueMaps[i];
// special null case
if (map.value === 'null') {
if (data.value === null || data.value === void 0) {
data.valueFormated = map.text;
return;
}
continue;
}
// value/number to text mapping
var value = parseFloat(map.value);
if (value === data.value) {
// check value to text mappings
for (var i = 0; i < this.panel.valueMaps.length; i++) {
var map = this.panel.valueMaps[i];
// special null case
if (map.value === 'null') {
if (data.value === null || data.value === void 0) {
data.valueFormated = map.text;
return;
}
continue;
}
if (data.value === null || data.value === void 0) {
data.valueFormated = "no value";
// value/number to text mapping
var value = parseFloat(map.value);
if (value === data.value) {
data.valueFormated = map.text;
return;
}
};
}
$scope.removeValueMap = function(map) {
var index = _.indexOf($scope.panel.valueMaps, map);
$scope.panel.valueMaps.splice(index, 1);
$scope.render();
};
if (data.value === null || data.value === void 0) {
data.valueFormated = "no value";
}
};
$scope.addValueMap = function() {
$scope.panel.valueMaps.push({value: '', op: '=', text: '' });
};
$scope.init();
removeValueMap(map) {
var index = _.indexOf(this.panel.valueMaps, map);
this.panel.valueMaps.splice(index, 1);
this.render();
};
addValueMap() {
this.panel.valueMaps.push({value: '', op: '=', text: '' });
}
}

View File

@ -10,20 +10,20 @@
</li>
<li>
<input type="text" class="input-small tight-form-input"
ng-model="panel.prefix" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.prefix" ng-change="ctrl.render()" ng-model-onblur>
</li>
<li class="tight-form-item">
Value
</li>
<li>
<select class="input-small tight-form-input" ng-model="panel.valueName" ng-options="f for f in ['min','max','avg', 'current', 'total']" ng-change="render()"></select>
<select class="input-small tight-form-input" ng-model="ctrl.panel.valueName" ng-options="f for f in ['min','max','avg', 'current', 'total']" ng-change="ctrl.render()"></select>
</li>
<li class="tight-form-item">
Postfix
</li>
<li>
<input type="text" class="input-small tight-form-input last"
ng-model="panel.postfix" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.postfix" ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@ -37,19 +37,19 @@
Prefix
</li>
<li>
<select class="input-small tight-form-input" ng-model="panel.prefixFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
<select class="input-small tight-form-input" ng-model="ctrl.panel.prefixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>
</li>
<li class="tight-form-item">
Value
</li>
<li>
<select class="input-small tight-form-input" ng-model="panel.valueFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
<select class="input-small tight-form-input" ng-model="ctrl.panel.valueFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>
</li>
<li class="tight-form-item">
Postfix
</li>
<li>
<select class="input-small tight-form-input last" ng-model="panel.postfixFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
<select class="input-small tight-form-input last" ng-model="ctrl.panel.postfixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>
</li>
</ul>
<div class="clearfix"></div>
@ -60,16 +60,16 @@
<strong>Unit</strong>
</li>
<li class="dropdown" style="width: 266px;"
ng-model="panel.format"
dropdown-typeahead="unitFormats"
dropdown-typeahead-on-select="setUnitFormat($subItem)">
ng-model="ctrl.panel.format"
dropdown-typeahead="ctrl.unitFormats"
dropdown-typeahead-on-select="ctrl.setUnitFormat($subItem)">
</li>
<li class="tight-form-item">
Decimals
</li>
<li>
<input type="number" class="input-small tight-form-input last" placeholder="auto" bs-tooltip="'Override automatic decimal precision for legend and tooltips'" data-placement="right"
ng-model="panel.decimals" ng-change="render()" ng-model-onblur>
ng-model="ctrl.panel.decimals" ng-change="ctrl.render()" ng-model-onblur>
</li>
</ul>
<div class="clearfix"></div>
@ -86,32 +86,32 @@
</li>
<li class="tight-form-item">
Background&nbsp;
<input class="cr1" id="panel.colorBackground" type="checkbox"
ng-model="panel.colorBackground" ng-checked="panel.colorBackground" ng-change="render()">
<label for="panel.colorBackground" class="cr1"></label>
<input class="cr1" id="ctrl.panel.colorBackground" type="checkbox"
ng-model="ctrl.panel.colorBackground" ng-checked="ctrl.panel.colorBackground" ng-change="ctrl.render()">
<label for="ctrl.panel.colorBackground" class="cr1"></label>
</li>
<li class="tight-form-item">
Value&nbsp;
<input class="cr1" id="panel.colorValue" type="checkbox"
ng-model="panel.colorValue" ng-checked="panel.colorValue" ng-change="render()">
<label for="panel.colorValue" class="cr1"></label>
<input class="cr1" id="ctrl.panel.colorValue" type="checkbox"
ng-model="ctrl.panel.colorValue" ng-checked="ctrl.panel.colorValue" ng-change="ctrl.render()">
<label for="ctrl.panel.colorValue" class="cr1"></label>
</li>
<li class="tight-form-item">
Thresholds<tip>Define two threshold values&lt;br /&gt; 50,80 will produce: &lt;50 = Green, 50:80 = Yellow, &gt;80 = Red</tip>
</li>
<li>
<input type="text" class="input-large tight-form-input" ng-model="panel.thresholds" ng-blur="render()" placeholder="50,80"></input>
<input type="text" class="input-large tight-form-input" ng-model="ctrl.panel.thresholds" ng-blur="ctrl.render()" placeholder="50,80"></input>
</li>
<li class="tight-form-item">
Colors
</li>
<li class="tight-form-item">
<spectrum-picker ng-model="panel.colors[0]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="panel.colors[1]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="panel.colors[2]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.colors[0]" ng-change="ctrl.render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.colors[1]" ng-change="ctrl.render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.colors[2]" ng-change="ctrl.render()" ></spectrum-picker>
</li>
<li class="tight-form-item last">
<a class="pointer" ng-click="invertColorOrder()">invert order</a>
<a class="pointer" ng-click="ctrl.invertColorOrder()">invert order</a>
</li>
</ul>
<div class="clearfix"></div>
@ -128,27 +128,27 @@
</li>
<li class="tight-form-item">
Show&nbsp;
<input class="cr1" id="panel.sparkline.show" type="checkbox"
ng-model="panel.sparkline.show" ng-checked="panel.sparkline.show" ng-change="render()">
<label for="panel.sparkline.show" class="cr1"></label>
<input class="cr1" id="ctrl.panel.sparkline.show" type="checkbox"
ng-model="ctrl.panel.sparkline.show" ng-checked="ctrl.panel.sparkline.show" ng-change="ctrl.render()">
<label for="ctrl.panel.sparkline.show" class="cr1"></label>
</li>
<li class="tight-form-item">
Background mode&nbsp;
<input class="cr1" id="panel.sparkline.full" type="checkbox"
ng-model="panel.sparkline.full" ng-checked="panel.sparkline.full" ng-change="render()">
<label for="panel.sparkline.full" class="cr1"></label>
<input class="cr1" id="ctrl.panel.sparkline.full" type="checkbox"
ng-model="ctrl.panel.sparkline.full" ng-checked="ctrl.panel.sparkline.full" ng-change="ctrl.render()">
<label for="ctrl.panel.sparkline.full" class="cr1"></label>
</li>
<li class="tight-form-item">
Line Color
</li>
<li class="tight-form-item">
<spectrum-picker ng-model="panel.sparkline.lineColor" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.sparkline.lineColor" ng-change="ctrl.render()" ></spectrum-picker>
</li>
<li class="tight-form-item">
Fill Color
</li>
<li class="tight-form-item last">
<spectrum-picker ng-model="panel.sparkline.fillColor" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="ctrl.panel.sparkline.fillColor" ng-change="ctrl.render()" ></spectrum-picker>
</li>
</ul>
<div class="clearfix"></div>
@ -163,21 +163,21 @@
<li class="tight-form-item">
<strong>Value to text mapping</strong>
</li>
<li class="tight-form-item" ng-repeat-start="map in panel.valueMaps">
<i class="fa fa-remove pointer" ng-click="removeValueMap(map)"></i>
<li class="tight-form-item" ng-repeat-start="map in ctrl.panel.valueMaps">
<i class="fa fa-remove pointer" ng-click="ctrl.removeValueMap(map)"></i>
</li>
<li>
<input type="text" ng-model="map.value" placeholder="value" class="input-mini tight-form-input" ng-blur="render()">
<input type="text" ng-model="ctrl.map.value" placeholder="value" class="input-mini tight-form-input" ng-blur="ctrl.render()">
</li>
<li class="tight-form-item">
<i class="fa fa-arrow-right"></i>
</li>
<li ng-repeat-end>
<input type="text" placeholder="text" ng-model="map.text" class="input-mini tight-form-input" ng-blur="render()">
<input type="text" placeholder="text" ng-model="ctrl.map.text" class="input-mini tight-form-input" ng-blur="ctrl.render()">
</li>
<li>
<a class="pointer tight-form-item last" ng-click="addValueMap();">
<a class="pointer tight-form-item last" ng-click="ctrl.addValueMap();">
<i class="fa fa-plus"></i>
</a>
</li>

View File

@ -1,4 +1,4 @@
<grafana-panel>
<div class="singlestat-panel"></div>
<div class="clearfix"></div>
</grafana-panel>
<div class="singlestat-panel">
</div>
<div class="clearfix"></div>

View File

@ -2,222 +2,225 @@
import _ from 'lodash';
import $ from 'jquery';
import angular from 'angular';
import 'jquery.flot';
import {SingleStatCtrl} from './controller';
import {PanelDirective} from '../../../features/panel/panel';
angular.module('grafana.directives').directive('singleStatPanel', singleStatPanel);
class SingleStatPanel extends PanelDirective {
templateUrl = 'app/plugins/panel/singlestat/module.html';
controller = SingleStatCtrl;
/** @ngInject */
function singleStatPanel($location, linkSrv, $timeout, templateSrv) {
'use strict';
return {
controller: SingleStatCtrl,
templateUrl: 'app/plugins/panel/singlestat/module.html',
link: function(scope, elem) {
var data, panel, linkInfo, $panelContainer;
var firstRender = true;
/** @ngInject */
constructor(private $location, private linkSrv, private $timeout, private templateSrv) {
super();
}
scope.$on('render', function() {
if (firstRender) {
var inner = elem.find('.singlestat-panel');
if (inner.length) {
elem = inner;
$panelContainer = elem.parents('.panel-container');
firstRender = false;
hookupDrilldownLinkTooltip();
}
}
link(scope, elem, attrs, ctrl) {
var $location = this.$location;
var linkSrv = this.linkSrv;
var $timeout = this.$timeout;
var panel = ctrl.panel;
var templateSrv = this.templateSrv;
var data, linkInfo, $panelContainer;
var firstRender = true;
render();
scope.panelRenderingComplete();
});
function setElementHeight() {
try {
var height = scope.height || panel.height || scope.row.height;
if (_.isString(height)) {
height = parseInt(height.replace('px', ''), 10);
}
height -= 5; // padding
height -= panel.title ? 24 : 9; // subtract panel title bar
elem.css('height', height + 'px');
return true;
} catch (e) { // IE throws errors sometimes
return false;
scope.$on('render', function() {
if (firstRender) {
var inner = elem.find('.singlestat-panel');
if (inner.length) {
elem = inner;
$panelContainer = elem.parents('.panel-container');
firstRender = false;
hookupDrilldownLinkTooltip();
}
}
function applyColoringThresholds(value, valueString) {
if (!panel.colorValue) {
return valueString;
render();
ctrl.renderingCompleted();
});
function setElementHeight() {
try {
var height = scope.height || panel.height || ctrl.row.height;
if (_.isString(height)) {
height = parseInt(height.replace('px', ''), 10);
}
var color = getColorForValue(data, value);
if (color) {
return '<span style="color:' + color + '">'+ valueString + '</span>';
}
height -= 5; // padding
height -= panel.title ? 24 : 9; // subtract panel title bar
elem.css('height', height + 'px');
return true;
} catch (e) { // IE throws errors sometimes
return false;
}
}
function applyColoringThresholds(value, valueString) {
if (!panel.colorValue) {
return valueString;
}
function getSpan(className, fontSize, value) {
value = templateSrv.replace(value);
return '<span class="' + className + '" style="font-size:' + fontSize + '">' +
value + '</span>';
var color = getColorForValue(data, value);
if (color) {
return '<span style="color:' + color + '">'+ valueString + '</span>';
}
function getBigValueHtml() {
var body = '<div class="singlestat-panel-value-container">';
return valueString;
}
if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); }
function getSpan(className, fontSize, value) {
value = templateSrv.replace(value);
return '<span class="' + className + '" style="font-size:' + fontSize + '">' +
value + '</span>';
}
var value = applyColoringThresholds(data.valueRounded, data.valueFormated);
body += getSpan('singlestat-panel-value', panel.valueFontSize, value);
function getBigValueHtml() {
var body = '<div class="singlestat-panel-value-container">';
if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); }
if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, panel.prefix); }
body += '</div>';
var value = applyColoringThresholds(data.valueRounded, data.valueFormated);
body += getSpan('singlestat-panel-value', panel.valueFontSize, value);
return body;
if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); }
body += '</div>';
return body;
}
function addSparkline() {
var width = elem.width() + 20;
var height = elem.height() || 100;
var plotCanvas = $('<div></div>');
var plotCss: any = {};
plotCss.position = 'absolute';
if (panel.sparkline.full) {
plotCss.bottom = '5px';
plotCss.left = '-5px';
plotCss.width = (width - 10) + 'px';
var dynamicHeightMargin = height <= 100 ? 5 : (Math.round((height/100)) * 15) + 5;
plotCss.height = (height - dynamicHeightMargin) + 'px';
} else {
plotCss.bottom = "0px";
plotCss.left = "-5px";
plotCss.width = (width - 10) + 'px';
plotCss.height = Math.floor(height * 0.25) + "px";
}
function addSparkline() {
var panel = scope.panel;
var width = elem.width() + 20;
var height = elem.height() || 100;
plotCanvas.css(plotCss);
var plotCanvas = $('<div></div>');
var plotCss: any = {};
plotCss.position = 'absolute';
if (panel.sparkline.full) {
plotCss.bottom = '5px';
plotCss.left = '-5px';
plotCss.width = (width - 10) + 'px';
var dynamicHeightMargin = height <= 100 ? 5 : (Math.round((height/100)) * 15) + 5;
plotCss.height = (height - dynamicHeightMargin) + 'px';
} else {
plotCss.bottom = "0px";
plotCss.left = "-5px";
plotCss.width = (width - 10) + 'px';
plotCss.height = Math.floor(height * 0.25) + "px";
}
plotCanvas.css(plotCss);
var options = {
legend: { show: false },
series: {
lines: {
show: true,
fill: 1,
lineWidth: 1,
fillColor: panel.sparkline.fillColor,
},
var options = {
legend: { show: false },
series: {
lines: {
show: true,
fill: 1,
lineWidth: 1,
fillColor: panel.sparkline.fillColor,
},
yaxes: { show: false },
xaxis: {
show: false,
mode: "time",
min: scope.range.from.valueOf(),
max: scope.range.to.valueOf(),
},
grid: { hoverable: false, show: false },
};
},
yaxes: { show: false },
xaxis: {
show: false,
mode: "time",
min: ctrl.range.from.valueOf(),
max: ctrl.range.to.valueOf(),
},
grid: { hoverable: false, show: false },
};
elem.append(plotCanvas);
elem.append(plotCanvas);
var plotSeries = {
data: data.flotpairs,
color: panel.sparkline.lineColor
};
var plotSeries = {
data: data.flotpairs,
color: panel.sparkline.lineColor
};
$.plot(plotCanvas, [plotSeries], options);
}
$.plot(plotCanvas, [plotSeries], options);
}
function render() {
if (!scope.data) { return; }
function render() {
if (!ctrl.data) { return; }
data = scope.data;
panel = scope.panel;
data = ctrl.data;
setElementHeight();
setElementHeight();
var body = getBigValueHtml();
var body = getBigValueHtml();
if (panel.colorBackground && !isNaN(data.valueRounded)) {
var color = getColorForValue(data, data.valueRounded);
if (color) {
$panelContainer.css('background-color', color);
if (scope.fullscreen) {
elem.css('background-color', color);
} else {
elem.css('background-color', '');
}
}
} else {
$panelContainer.css('background-color', '');
elem.css('background-color', '');
}
elem.html(body);
if (panel.sparkline.show) {
addSparkline();
}
elem.toggleClass('pointer', panel.links.length > 0);
if (panel.links.length > 0) {
linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], scope.panel.scopedVars);
} else {
linkInfo = null;
}
}
function hookupDrilldownLinkTooltip() {
// drilldown link tooltip
var drilldownTooltip = $('<div id="tooltip" class="">hello</div>"');
elem.mouseleave(function() {
if (panel.links.length === 0) { return;}
drilldownTooltip.detach();
});
elem.click(function(evt) {
if (!linkInfo) { return; }
// ignore title clicks in title
if ($(evt).parents('.panel-header').length > 0) { return; }
if (linkInfo.target === '_blank') {
var redirectWindow = window.open(linkInfo.href, '_blank');
redirectWindow.location;
return;
}
if (linkInfo.href.indexOf('http') === 0) {
window.location.href = linkInfo.href;
if (panel.colorBackground && !isNaN(data.valueRounded)) {
var color = getColorForValue(data, data.valueRounded);
if (color) {
$panelContainer.css('background-color', color);
if (scope.fullscreen) {
elem.css('background-color', color);
} else {
$timeout(function() {
$location.url(linkInfo.href);
});
elem.css('background-color', '');
}
}
} else {
$panelContainer.css('background-color', '');
elem.css('background-color', '');
}
drilldownTooltip.detach();
});
elem.html(body);
elem.mousemove(function(e) {
if (!linkInfo) { return;}
if (panel.sparkline.show) {
addSparkline();
}
drilldownTooltip.text('click to go to: ' + linkInfo.title);
drilldownTooltip.place_tt(e.pageX+20, e.pageY-15);
});
elem.toggleClass('pointer', panel.links.length > 0);
if (panel.links.length > 0) {
linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], panel.scopedVars);
} else {
linkInfo = null;
}
}
};
function hookupDrilldownLinkTooltip() {
// drilldown link tooltip
var drilldownTooltip = $('<div id="tooltip" class="">hello</div>"');
elem.mouseleave(function() {
if (panel.links.length === 0) { return;}
drilldownTooltip.detach();
});
elem.click(function(evt) {
if (!linkInfo) { return; }
// ignore title clicks in title
if ($(evt).parents('.panel-header').length > 0) { return; }
if (linkInfo.target === '_blank') {
var redirectWindow = window.open(linkInfo.href, '_blank');
redirectWindow.location;
return;
}
if (linkInfo.href.indexOf('http') === 0) {
window.location.href = linkInfo.href;
} else {
$timeout(function() {
$location.url(linkInfo.href);
});
}
drilldownTooltip.detach();
});
elem.mousemove(function(e) {
if (!linkInfo) { return;}
drilldownTooltip.text('click to go to: ' + linkInfo.title);
drilldownTooltip.place_tt(e.pageX+20, e.pageY-15);
});
}
}
}
function getColorForValue(data, value) {
@ -226,8 +229,11 @@ function getColorForValue(data, value) {
return data.colorMap[i];
}
}
return _.first(data.colorMap);
}
export {singleStatPanel as panel, getColorForValue};
export {
SingleStatPanel,
SingleStatPanel as Panel,
getColorForValue
};

View File

@ -9,9 +9,6 @@ import angular from 'angular';
import helpers from '../../../../../test/specs/helpers';
import {SingleStatCtrl} from '../controller';
angular.module('grafana.controllers').controller('SingleStatCtrl', SingleStatCtrl);
describe('SingleStatCtrl', function() {
var ctx = new helpers.ControllerTestContext();
@ -25,7 +22,7 @@ describe('SingleStatCtrl', function() {
beforeEach(angularMocks.module('grafana.controllers'));
beforeEach(ctx.providePhase());
beforeEach(ctx.createControllerPhase('SingleStatCtrl'));
beforeEach(ctx.createPanelController(SingleStatCtrl));
beforeEach(function() {
setupFunc();
@ -33,9 +30,9 @@ describe('SingleStatCtrl', function() {
data: [{target: 'test.cpu1', datapoints: ctx.datapoints}]
}));
ctx.scope.refreshData(ctx.datasource);
ctx.ctrl.refreshData(ctx.datasource);
ctx.scope.$digest();
ctx.data = ctx.scope.data;
ctx.data = ctx.ctrl.data;
});
};
@ -76,7 +73,7 @@ describe('SingleStatCtrl', function() {
singleStatScenario('When value to text mapping is specified', function(ctx) {
ctx.setup(function() {
ctx.datapoints = [[10,1]];
ctx.scope.panel.valueMaps = [{value: '10', text: 'OK'}];
ctx.ctrl.panel.valueMaps = [{value: '10', text: 'OK'}];
});
it('Should replace value with text', function() {

View File

@ -4,132 +4,130 @@ import angular from 'angular';
import _ from 'lodash';
import moment from 'moment';
import * as FileExport from 'app/core/utils/file_export';
import PanelMeta from 'app/features/panel/panel_meta2';
import {MetricsPanelCtrl} from '../../../features/panel/panel';
import {transformDataToTable} from './transformers';
import {tablePanelEditor} from './editor';
export class TablePanelCtrl {
var panelDefaults = {
targets: [{}],
transform: 'timeseries_to_columns',
pageSize: null,
showHeader: true,
styles: [
{
type: 'date',
pattern: 'Time',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
},
{
unit: 'short',
type: 'number',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: null,
pattern: '/.*/',
thresholds: [],
}
],
columns: [],
scroll: true,
fontSize: '100%',
sort: {col: 0, desc: true},
};
export class TablePanelCtrl extends MetricsPanelCtrl {
pageIndex: number;
dataRaw: any;
table: any;
/** @ngInject */
constructor($scope, $rootScope, $q, panelSrv, panelHelper, annotationsSrv) {
$scope.ctrl = this;
$scope.pageIndex = 0;
constructor($scope, $injector, private annotationsSrv) {
super($scope, $injector);
this.pageIndex = 0;
$scope.panelMeta = new PanelMeta({
panelName: 'Table',
editIcon: "fa fa-table",
fullscreen: true,
metricsEditor: true,
});
if (this.panel.styles === void 0) {
this.panel.styles = this.panel.columns;
this.panel.columns = this.panel.fields;
delete this.panel.columns;
delete this.panel.fields;
}
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/table/options.html');
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
$scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()');
_.defaults(this.panel, panelDefaults);
}
var panelDefaults = {
targets: [{}],
transform: 'timeseries_to_columns',
pageSize: null,
showHeader: true,
styles: [
{
type: 'date',
pattern: 'Time',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
},
{
unit: 'short',
type: 'number',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: null,
pattern: '/.*/',
thresholds: [],
}
],
columns: [],
scroll: true,
fontSize: '100%',
sort: {col: 0, desc: true},
};
initEditMode() {
super.initEditMode();
this.addEditorTab('Options', tablePanelEditor, 1);
}
$scope.init = function() {
if ($scope.panel.styles === void 0) {
$scope.panel.styles = $scope.panel.columns;
$scope.panel.columns = $scope.panel.fields;
delete $scope.panel.columns;
delete $scope.panel.fields;
}
getExtendedMenu() {
var menu = super.getExtendedMenu();
menu.push({text: 'Export CSV', click: 'ctrl.exportCsv()'});
return menu;
}
_.defaults($scope.panel, panelDefaults);
panelSrv.init($scope);
};
refreshData(datasource) {
this.pageIndex = 0;
$scope.refreshData = function(datasource) {
panelHelper.updateTimeRange($scope);
$scope.pageIndex = 0;
if ($scope.panel.transform === 'annotations') {
return annotationsSrv.getAnnotations($scope.dashboard).then(annotations => {
$scope.dataRaw = annotations;
$scope.render();
});
}
return panelHelper.issueMetricQuery($scope, datasource)
.then($scope.dataHandler, function(err) {
$scope.render();
throw err;
if (this.panel.transform === 'annotations') {
return this.annotationsSrv.getAnnotations(this.dashboard).then(annotations => {
this.dataRaw = annotations;
this.render();
});
};
}
$scope.toggleColumnSort = function(col, colIndex) {
if ($scope.panel.sort.col === colIndex) {
if ($scope.panel.sort.desc) {
$scope.panel.sort.desc = false;
} else {
$scope.panel.sort.col = null;
}
return this.issueQueries(datasource)
.then(this.dataHandler.bind(this))
.catch(err => {
this.render();
throw err;
});
}
toggleColumnSort(col, colIndex) {
if (this.panel.sort.col === colIndex) {
if (this.panel.sort.desc) {
this.panel.sort.desc = false;
} else {
$scope.panel.sort.col = colIndex;
$scope.panel.sort.desc = true;
this.panel.sort.col = null;
}
} else {
this.panel.sort.col = colIndex;
this.panel.sort.desc = true;
}
$scope.render();
};
this.render();
}
$scope.dataHandler = function(results) {
$scope.dataRaw = results.data;
$scope.pageIndex = 0;
$scope.render();
};
dataHandler(results) {
this.dataRaw = results.data;
this.pageIndex = 0;
this.render();
}
$scope.render = function() {
// automatically correct transform mode
// based on data
if ($scope.dataRaw && $scope.dataRaw.length) {
if ($scope.dataRaw[0].type === 'table') {
$scope.panel.transform = 'table';
render() {
// automatically correct transform mode
// based on data
if (this.dataRaw && this.dataRaw.length) {
if (this.dataRaw[0].type === 'table') {
this.panel.transform = 'table';
} else {
if (this.dataRaw[0].type === 'docs') {
this.panel.transform = 'json';
} else {
if ($scope.dataRaw[0].type === 'docs') {
$scope.panel.transform = 'json';
} else {
if ($scope.panel.transform === 'table' || $scope.panel.transform === 'json') {
$scope.panel.transform = 'timeseries_to_rows';
}
if (this.panel.transform === 'table' || this.panel.transform === 'json') {
this.panel.transform = 'timeseries_to_rows';
}
}
}
}
$scope.table = transformDataToTable($scope.dataRaw, $scope.panel);
$scope.table.sort($scope.panel.sort);
panelHelper.broadcastRender($scope, $scope.table, $scope.dataRaw);
};
this.table = transformDataToTable(this.dataRaw, this.panel);
this.table.sort(this.panel.sort);
this.broadcastRender(this.table);
}
$scope.exportCsv = function() {
FileExport.exportTableDataToCsv($scope.table);
};
$scope.init();
exportCsv() {
FileExport.exportTableDataToCsv(this.table);
}
}

View File

@ -9,9 +9,9 @@
</li>
<li>
<select class="input-large tight-form-input"
ng-model="panel.transform"
ng-options="k as v.description for (k, v) in transformers"
ng-change="transformChanged()"></select>
ng-model="editor.panel.transform"
ng-options="k as v.description for (k, v) in editor.transformers"
ng-change="editor.transformChanged()"></select>
</li>
</ul>
<div class="clearfix"></div>
@ -21,14 +21,14 @@
<li class="tight-form-item" style="width: 140px">
Columns
</li>
<li class="tight-form-item" ng-repeat="column in panel.columns">
<i class="pointer fa fa-remove" ng-click="removeColumn(column)"></i>
<li class="tight-form-item" ng-repeat="column in editor.panel.columns">
<i class="pointer fa fa-remove" ng-click="editor.removeColumn(column)"></i>
<span>
{{column.text}}
</span>
</li>
<li>
<metric-segment segment="addColumnSegment" get-options="getColumnOptions()" on-change="addColumn()"></metric-segment>
<metric-segment segment="editor.addColumnSegment" get-options="editor.getColumnOptions()" on-change="editor.addColumn()"></metric-segment>
</li>
</ul>
<div class="clearfix"></div>
@ -46,16 +46,16 @@
</li>
<li>
<input type="number" class="input-small tight-form-input" placeholder="100"
empty-to-null ng-model="panel.pageSize" ng-change="render()" ng-model-onblur>
empty-to-null ng-model="editor.panel.pageSize" ng-change="editor.render()" ng-model-onblur>
</li>
<li class="tight-form-item">
<editor-checkbox text="Scroll" model="panel.scroll" change="render()"></editor-checkbox>
<editor-checkbox text="Scroll" model="editor.panel.scroll" change="editor.render()"></editor-checkbox>
</li>
<li class="tight-form-item">
Font size
</li>
<li>
<select class="input-small tight-form-input" ng-model="panel.fontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
<select class="input-small tight-form-input" ng-model="editor.panel.fontSize" ng-options="f for f in editor.fontSizes" ng-change="editor.render()"></select>
</li>
</ul>
<div class="clearfix"></div>
@ -68,11 +68,11 @@
<h5>Column Styles</h5>
<div class="tight-form-container">
<div ng-repeat="style in panel.styles">
<div ng-repeat="style in editor.panel.styles">
<div class="tight-form">
<ul class="tight-form-list pull-right">
<li class="tight-form-item last">
<i class="fa fa-remove pointer" ng-click="removeColumnStyle(style)"></i>
<i class="fa fa-remove pointer" ng-click="editor.removeColumnStyle(style)"></i>
</li>
</ul>
@ -81,7 +81,7 @@
Name or regex
</li>
<li>
<input type="text" ng-model="style.pattern" bs-typeahead="getColumnNames" ng-blur="render()" data-min-length=0 data-items=100 class="input-medium tight-form-input">
<input type="text" ng-model="style.pattern" bs-typeahead="editor.getColumnNames" ng-blur="editor.render()" data-min-length=0 data-items=100 class="input-medium tight-form-input">
</li>
<li class="tight-form-item" style="width: 86px">
Type
@ -89,8 +89,8 @@
<li>
<select class="input-small tight-form-input"
ng-model="style.type"
ng-options="c.value as c.text for c in columnTypes"
ng-change="render()"
ng-options="c.value as c.text for c in editor.columnTypes"
ng-change="editor.render()"
style="width: 150px"
></select>
</li>
@ -100,7 +100,7 @@
Format
</li>
<li>
<metric-segment-model property="style.dateFormat" options="dateFormats" on-change="render()" custom="true"></metric-segment-model>
<metric-segment-model property="style.dateFormat" options="editor.dateFormats" on-change="editor.render()" custom="true"></metric-segment-model>
</li>
</ul>
<div class="clearfix"></div>
@ -113,8 +113,8 @@
<li>
<select class="input-small tight-form-input"
ng-model="style.colorMode"
ng-options="c.value as c.text for c in colorModes"
ng-change="render()"
ng-options="c.value as c.text for c in editor.colorModes"
ng-change="editor.render()"
style="width: 150px"
></select>
</li>
@ -122,18 +122,18 @@
Thresholds<tip>Comma seperated values</tip>
</li>
<li>
<input type="text" class="input-small tight-form-input" style="width: 150px" ng-model="style.thresholds" ng-blur="render()" placeholder="0,50,80" array-join></input>
<input type="text" class="input-small tight-form-input" style="width: 150px" ng-model="style.thresholds" ng-blur="editor.render()" placeholder="0,50,80" array-join></input>
</li>
<li class="tight-form-item" style="width: 60px">
Colors
</li>
<li class="tight-form-item">
<spectrum-picker ng-model="style.colors[0]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="style.colors[1]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="style.colors[2]" ng-change="render()" ></spectrum-picker>
<spectrum-picker ng-model="style.colors[0]" ng-change="editor.render()" ></spectrum-picker>
<spectrum-picker ng-model="style.colors[1]" ng-change="editor.render()" ></spectrum-picker>
<spectrum-picker ng-model="style.colors[2]" ng-change="editor.render()" ></spectrum-picker>
</li>
<li class="tight-form-item last">
<a class="pointer" ng-click="invertColorOrder($index)">invert order</a>
<a class="pointer" ng-click="editor.invertColorOrder($index)">invert order</a>
</li>
</ul>
<div class="clearfix"></div>
@ -145,8 +145,8 @@
</li>
<li class="dropdown" style="width: 150px"
ng-model="style.unit"
dropdown-typeahead="unitFormats"
dropdown-typeahead-on-select="setUnitFormat(style, $subItem)">
dropdown-typeahead="editor.unitFormats"
dropdown-typeahead-on-select="editor.setUnitFormat(style, $subItem)">
</li>
<li class="tight-form-item" style="width: 86px">
Decimals
@ -161,7 +161,7 @@
</div>
</div>
<button class="btn btn-inverse" style="margin-top: 20px" ng-click="addColumnStyle()">
<button class="btn btn-inverse" style="margin-top: 20px" ng-click="editor.addColumnStyle()">
Add column style rule
</button>
</div>

View File

@ -10,105 +10,123 @@ import {transformers} from './transformers';
import kbn from 'app/core/utils/kbn';
export class TablePanelEditorCtrl {
panel: any;
panelCtrl: any;
transformers: any;
colorModes: any;
columnStyles: any;
columnTypes: any;
fontSizes: any;
dateFormats: any;
addColumnSegment: any;
unitFormats: any;
getColumnNames: any;
/** @ngInject */
constructor($scope, $q, uiSegmentSrv) {
$scope.transformers = transformers;
$scope.unitFormats = kbn.getUnitFormats();
$scope.colorModes = [
constructor($scope, private $q, private uiSegmentSrv) {
$scope.editor = this;
this.panelCtrl = $scope.ctrl;
this.panel = this.panelCtrl.panel;
this.transformers = transformers;
this.unitFormats = kbn.getUnitFormats();
this.colorModes = [
{text: 'Disabled', value: null},
{text: 'Cell', value: 'cell'},
{text: 'Value', value: 'value'},
{text: 'Row', value: 'row'},
];
$scope.columnTypes = [
this.columnTypes = [
{text: 'Number', value: 'number'},
{text: 'String', value: 'string'},
{text: 'Date', value: 'date'},
];
$scope.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%'];
$scope.dateFormats = [
this.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%'];
this.dateFormats = [
{text: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss'},
{text: 'MM/DD/YY h:mm:ss a', value: 'MM/DD/YY h:mm:ss a'},
{text: 'MMMM D, YYYY LT', value: 'MMMM D, YYYY LT'},
];
$scope.addColumnSegment = uiSegmentSrv.newPlusButton();
this.addColumnSegment = uiSegmentSrv.newPlusButton();
$scope.getColumnOptions = function() {
if (!$scope.dataRaw) {
return $q.when([]);
}
var columns = transformers[$scope.panel.transform].getColumns($scope.dataRaw);
var segments = _.map(columns, (c: any) => uiSegmentSrv.newSegment({value: c.text}));
return $q.when(segments);
};
$scope.addColumn = function() {
var columns = transformers[$scope.panel.transform].getColumns($scope.dataRaw);
var column = _.findWhere(columns, {text: $scope.addColumnSegment.value});
if (column) {
$scope.panel.columns.push(column);
$scope.render();
}
var plusButton = uiSegmentSrv.newPlusButton();
$scope.addColumnSegment.html = plusButton.html;
$scope.addColumnSegment.value = plusButton.value;
};
$scope.transformChanged = function() {
$scope.panel.columns = [];
$scope.render();
};
$scope.removeColumn = function(column) {
$scope.panel.columns = _.without($scope.panel.columns, column);
$scope.render();
};
$scope.setUnitFormat = function(column, subItem) {
column.unit = subItem.value;
$scope.render();
};
$scope.addColumnStyle = function() {
var columnStyleDefaults = {
unit: 'short',
type: 'number',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: null,
pattern: '/.*/',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
thresholds: [],
};
$scope.panel.styles.push(angular.copy(columnStyleDefaults));
};
$scope.removeColumnStyle = function(style) {
$scope.panel.styles = _.without($scope.panel.styles, style);
};
$scope.getColumnNames = function() {
if (!$scope.table) {
// this is used from bs-typeahead and needs to be instance bound
this.getColumnNames = () => {
if (!this.panelCtrl.table) {
return [];
}
return _.map($scope.table.columns, function(col: any) {
return _.map(this.panelCtrl.table.columns, function(col: any) {
return col.text;
});
};
}
$scope.invertColorOrder = function(index) {
var ref = $scope.panel.styles[index].colors;
var copy = ref[0];
ref[0] = ref[2];
ref[2] = copy;
$scope.render();
getColumnOptions() {
if (!this.panelCtrl.dataRaw) {
return this.$q.when([]);
}
var columns = this.transformers[this.panel.transform].getColumns(this.panelCtrl.dataRaw);
var segments = _.map(columns, (c: any) => this.uiSegmentSrv.newSegment({value: c.text}));
return this.$q.when(segments);
}
addColumn() {
var columns = transformers[this.panel.transform].getColumns(this.panelCtrl.dataRaw);
var column = _.findWhere(columns, {text: this.addColumnSegment.value});
if (column) {
this.panel.columns.push(column);
this.render();
}
var plusButton = this.uiSegmentSrv.newPlusButton();
this.addColumnSegment.html = plusButton.html;
this.addColumnSegment.value = plusButton.value;
}
transformChanged() {
this.panel.columns = [];
this.render();
}
render() {
this.panelCtrl.render();
}
removeColumn(column) {
this.panel.columns = _.without(this.panel.columns, column);
this.panelCtrl.render();
}
setUnitFormat(column, subItem) {
column.unit = subItem.value;
this.panelCtrl.render();
};
addColumnStyle() {
var columnStyleDefaults = {
unit: 'short',
type: 'number',
decimals: 2,
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
colorMode: null,
pattern: '/.*/',
dateFormat: 'YYYY-MM-DD HH:mm:ss',
thresholds: [],
};
this.panel.styles.push(angular.copy(columnStyleDefaults));
}
removeColumnStyle(style) {
this.panel.styles = _.without(this.panel.styles, style);
}
invertColorOrder(index) {
var ref = this.panel.styles[index].colors;
var copy = ref[0];
ref[0] = ref[2];
ref[2] = copy;
this.panelCtrl.render();
}
}

View File

@ -1,28 +1,24 @@
<div class="table-panel-wrapper">
<grafana-panel>
<div class="table-panel-container">
<div class="table-panel-header-bg"></div>
<div class="table-panel-scroll">
<table class="table-panel-table">
<thead>
<tr>
<th ng-repeat="col in table.columns">
<div class="table-panel-table-header-inner pointer" ng-click="toggleColumnSort(col, $index)">
{{col.text}}
<span class="table-panel-table-header-controls" ng-if="col.sort">
<i class="fa fa-caret-down" ng-show="col.desc"></i>
<i class="fa fa-caret-up" ng-hide="col.desc"></i>
</span>
</div>
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="table-panel-footer">
</div>
</grafana-panel>
<div class="table-panel-container">
<div class="table-panel-header-bg"></div>
<div class="table-panel-scroll">
<table class="table-panel-table">
<thead>
<tr>
<th ng-repeat="col in ctrl.table.columns">
<div class="table-panel-table-header-inner pointer" ng-click="ctrl.toggleColumnSort(col, $index)">
{{col.text}}
<span class="table-panel-table-header-controls" ng-if="col.sort">
<i class="fa fa-caret-down" ng-show="col.desc"></i>
<i class="fa fa-caret-up" ng-hide="col.desc"></i>
</span>
</div>
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div class="table-panel-footer">
</div>

View File

@ -5,103 +5,98 @@ import kbn = require('app/core/utils/kbn');
import _ from 'lodash';
import $ from 'jquery';
import moment from 'moment';
import angular from 'angular';
import {PanelDirective} from '../../../features/panel/panel';
import {TablePanelCtrl} from './controller';
import {TableRenderer} from './renderer';
import {tablePanelEditor} from './editor';
angular.module('grafana.directives').directive('grafanaPanelTableEditor', tablePanelEditor);
class TablePanel extends PanelDirective {
templateUrl = 'app/plugins/panel/table/module.html';
controller = TablePanelCtrl;
function tablePanel() {
'use strict';
return {
restrict: 'E',
templateUrl: 'app/plugins/panel/table/module.html',
controller: TablePanelCtrl,
link: function(scope, elem) {
var data;
var panel = scope.panel;
var pageCount = 0;
var formaters = [];
link(scope, elem, attrs, ctrl) {
var data;
var panel = ctrl.panel;
var pageCount = 0;
var formaters = [];
function getTableHeight() {
var panelHeight = scope.height || scope.panel.height || scope.row.height;
if (_.isString(panelHeight)) {
panelHeight = parseInt(panelHeight.replace('px', ''), 10);
}
if (pageCount > 1) {
panelHeight -= 28;
}
return (panelHeight - 60) + 'px';
function getTableHeight() {
var panelHeight = ctrl.height || ctrl.panel.height || ctrl.row.height;
if (_.isString(panelHeight)) {
panelHeight = parseInt(panelHeight.replace('px', ''), 10);
}
if (pageCount > 1) {
panelHeight -= 28;
}
function appendTableRows(tbodyElem) {
var renderer = new TableRenderer(panel, data, scope.dashboard.timezone);
tbodyElem.empty();
tbodyElem.html(renderer.render(scope.pageIndex));
}
function switchPage(e) {
var el = $(e.currentTarget);
scope.pageIndex = (parseInt(el.text(), 10)-1);
renderPanel();
}
function appendPaginationControls(footerElem) {
footerElem.empty();
var pageSize = panel.pageSize || 100;
pageCount = Math.ceil(data.rows.length / pageSize);
if (pageCount === 1) {
return;
}
var startPage = Math.max(scope.pageIndex - 3, 0);
var endPage = Math.min(pageCount, startPage + 9);
var paginationList = $('<ul></ul>');
for (var i = startPage; i < endPage; i++) {
var activeClass = i === scope.pageIndex ? 'active' : '';
var pageLinkElem = $('<li><a class="table-panel-page-link pointer ' + activeClass + '">' + (i+1) + '</a></li>');
paginationList.append(pageLinkElem);
}
footerElem.append(paginationList);
}
function renderPanel() {
var container = elem.find('.table-panel-container');
var rootElem = elem.find('.table-panel-scroll');
var tbodyElem = elem.find('tbody');
var footerElem = elem.find('.table-panel-footer');
appendTableRows(tbodyElem);
container.css({'font-size': panel.fontSize});
appendPaginationControls(footerElem);
rootElem.css({'max-height': panel.scroll ? getTableHeight() : '' });
}
elem.on('click', '.table-panel-page-link', switchPage);
scope.$on('$destroy', function() {
elem.off('click', '.table-panel-page-link');
});
scope.$on('render', function(event, renderData) {
data = renderData || data;
if (!data) {
scope.get_data();
return;
}
renderPanel();
});
return (panelHeight - 60) + 'px';
}
};
function appendTableRows(tbodyElem) {
var renderer = new TableRenderer(panel, data, ctrl.dashboard.timezone);
tbodyElem.empty();
tbodyElem.html(renderer.render(ctrl.pageIndex));
}
function switchPage(e) {
var el = $(e.currentTarget);
ctrl.pageIndex = (parseInt(el.text(), 10)-1);
renderPanel();
}
function appendPaginationControls(footerElem) {
footerElem.empty();
var pageSize = panel.pageSize || 100;
pageCount = Math.ceil(data.rows.length / pageSize);
if (pageCount === 1) {
return;
}
var startPage = Math.max(ctrl.pageIndex - 3, 0);
var endPage = Math.min(pageCount, startPage + 9);
var paginationList = $('<ul></ul>');
for (var i = startPage; i < endPage; i++) {
var activeClass = i === ctrl.pageIndex ? 'active' : '';
var pageLinkElem = $('<li><a class="table-panel-page-link pointer ' + activeClass + '">' + (i+1) + '</a></li>');
paginationList.append(pageLinkElem);
}
footerElem.append(paginationList);
}
function renderPanel() {
var panelElem = elem.parents('.panel');
var rootElem = elem.find('.table-panel-scroll');
var tbodyElem = elem.find('tbody');
var footerElem = elem.find('.table-panel-footer');
elem.css({'font-size': panel.fontSize});
panelElem.addClass('table-panel-wrapper');
appendTableRows(tbodyElem);
appendPaginationControls(footerElem);
rootElem.css({'max-height': panel.scroll ? getTableHeight() : '' });
}
elem.on('click', '.table-panel-page-link', switchPage);
scope.$on('$destroy', function() {
elem.off('click', '.table-panel-page-link');
});
scope.$on('render', function(event, renderData) {
data = renderData || data;
if (data) {
renderPanel();
}
});
}
}
export {tablePanel as panel};
export {
TablePanel,
TablePanel as Panel
};

View File

@ -1,43 +0,0 @@
///<reference path="../../../headers/common.d.ts" />
import PanelMeta from 'app/features/panel/panel_meta2';
class PanelBaseCtrl {
constructor(private $scope) {
$scope.panelMeta = new PanelMeta({
panelName: 'Table',
editIcon: "fa fa-table",
fullscreen: true,
metricsEditor: true,
});
$scope.testProp = "hello";
}
}
class TestPanelCtrl extends PanelBaseCtrl {
constructor($scope) {
super($scope);
$scope.panelMeta.panelName = "Test";
}
}
function testPanelDirective() {
return {
restrict: 'E',
template: `
<grafana-panel>
<div class="text-center" style="padding-top: 2rem">
<h2>Test Panel, {{testProp}}</h2>
</div>
</grafana-panel>
`,
controller: TestPanelCtrl,
controllerAs: 'ctrl',
};
}
export {
PanelBaseCtrl,
TestPanelCtrl,
testPanelDirective as panel
}

View File

@ -1,5 +0,0 @@
{
"type": "panel",
"name": "Test",
"id": "test"
}

View File

@ -1,17 +1,17 @@
<div>
<div class="row-fluid">
<div class="span4">
<label class="small">Mode</label> <select class="input-medium" ng-model="panel.mode" ng-options="f for f in ['html','markdown','text']"></select>
<label class="small">Mode</label> <select class="input-medium" ng-model="ctrl.panel.mode" ng-options="f for f in ['html','markdown','text']"></select>
</div>
<div class="span2" ng-show="panel.mode == 'text'">
<label class="small">Font Size</label> <select class="input-mini" ng-model="panel.style['font-size']" ng-options="f for f in ['6pt','7pt','8pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select>
<div class="span2" ng-show="ctrl.panel.mode == 'text'">
<label class="small">Font Size</label> <select class="input-mini" ng-model="ctrl.panel.style['font-size']" ng-options="f for f in ['6pt','7pt','8pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select>
</div>
</div>
<label class=small>Content
<span ng-show="panel.mode == 'markdown'">(This area uses <a target="_blank" href="http://en.wikipedia.org/wiki/Markdown">Markdown</a>. HTML is not supported)</span>
<span ng-show="ctrl.panel.mode == 'markdown'">(This area uses <a target="_blank" href="http://en.wikipedia.org/wiki/Markdown">Markdown</a>. HTML is not supported)</span>
</label>
<textarea ng-model="panel.content" rows="20" style="width:95%" ng-change="render()" ng-model-onblur>
<textarea ng-model="ctrl.panel.content" rows="20" style="width:95%" ng-change="ctrl.render()" ng-model-onblur>
</textarea>
</div>

View File

@ -1,3 +1 @@
<grafana-panel>
<p ng-bind-html="content" ng-show="content"></p>
</grafana-panel>
<p ng-bind-html="ctrl.content" ng-show="ctrl.content"></p>

View File

@ -1,113 +0,0 @@
define([
'angular',
'app/app',
'lodash',
'require',
'app/features/panel/panel_meta',
],
function (angular, app, _, require, PanelMeta) {
'use strict';
var converter;
/** @ngInject */
function TextPanelCtrl($scope, templateSrv, $sce, panelSrv) {
$scope.panelMeta = new PanelMeta({
panelName: 'Text',
editIcon: "fa fa-text-width",
fullscreen: true,
});
$scope.panelMeta.addEditorTab('Edit text', 'app/plugins/panel/text/editor.html');
// Set and populate defaults
var _d = {
title : 'default title',
mode : "markdown", // 'html', 'markdown', 'text'
content : "",
style: {},
};
_.defaults($scope.panel, _d);
$scope.init = function() {
panelSrv.init($scope);
$scope.ready = false;
$scope.render();
};
$scope.refreshData = function() {
$scope.panelMeta.loading = false;
$scope.render();
};
$scope.render = function() {
if ($scope.panel.mode === 'markdown') {
$scope.renderMarkdown($scope.panel.content);
}
else if ($scope.panel.mode === 'html') {
$scope.updateContent($scope.panel.content);
}
else if ($scope.panel.mode === 'text') {
$scope.renderText($scope.panel.content);
}
$scope.panelRenderingComplete();
};
$scope.renderText = function(content) {
content = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
.replace(/\n/g, '<br/>');
$scope.updateContent(content);
};
$scope.renderMarkdown = function(content) {
var text = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
if (converter) {
$scope.updateContent(converter.makeHtml(text));
}
else {
require(['vendor/showdown'], function (Showdown) {
converter = new Showdown.converter();
$scope.updateContent(converter.makeHtml(text));
});
}
};
$scope.updateContent = function(html) {
try {
$scope.content = $sce.trustAsHtml(templateSrv.replace(html, $scope.panel.scopedVars));
} catch(e) {
console.log('Text panel error: ', e);
$scope.content = $sce.trustAsHtml(html);
}
if(!$scope.$$phase) {
$scope.$digest();
}
};
$scope.openEditor = function() {
};
$scope.init();
}
function textPanel() {
return {
controller: TextPanelCtrl,
templateUrl: 'app/plugins/panel/text/module.html',
};
}
return {
panel: textPanel,
};
});

View File

@ -0,0 +1,85 @@
///<reference path="../../../headers/common.d.ts" />
import _ from 'lodash';
import {PanelDirective, PanelCtrl} from '../../../features/panel/panel';
// Set and populate defaults
var panelDefaults = {
mode : "markdown", // 'html', 'markdown', 'text'
content : "# title",
};
export class TextPanelCtrl extends PanelCtrl {
converter: any;
content: string;
/** @ngInject */
constructor($scope, $injector, private templateSrv, private $sce) {
super($scope, $injector);
_.defaults(this.panel, panelDefaults);
}
initEditMode() {
this.icon = 'fa fa-text-width';
this.addEditorTab('Options', 'public/app/plugins/panel/text/editor.html');
}
refresh() {
this.render();
}
render() {
if (this.panel.mode === 'markdown') {
this.renderMarkdown(this.panel.content);
} else if (this.panel.mode === 'html') {
this.updateContent(this.panel.content);
} else if (this.panel.mode === 'text') {
this.renderText(this.panel.content);
}
this.renderingCompleted();
}
renderText(content) {
content = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
.replace(/\n/g, '<br/>');
this.updateContent(content);
}
renderMarkdown(content) {
var text = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
if (this.converter) {
this.updateContent(this.converter.makeHtml(text));
} else {
System.import('vendor/showdown').then(Showdown => {
this.converter = new Showdown.converter();
this.$scope.$apply(() => {
this.updateContent(this.converter.makeHtml(text));
});
});
}
}
updateContent(html) {
try {
this.content = this.$sce.trustAsHtml(this.templateSrv.replace(html, this.panel.scopedVars));
} catch (e) {
console.log('Text panel error: ', e);
this.content = this.$sce.trustAsHtml(html);
}
}
}
class TextPanel extends PanelDirective {
templateUrl = `app/plugins/panel/text/module.html`;
controller = TextPanelCtrl;
}
export {TextPanel as Panel}

View File

@ -0,0 +1,5 @@
<div class="text-center" style="padding-top: 2rem">
Unknown panel type: <strong>{{ctrl.panel.type}}</strong>
</div

View File

@ -1,15 +1,14 @@
///<reference path="../../../headers/common.d.ts" />
export function unknownPanelDirective() {
return {
restrict: 'E',
template: `
<grafana-panel>
<div class="text-center" style="padding-top: 2rem">
Unknown panel type: <strong>{{panel.type}}</strong>
</div>
</grafana-panel>
`,
};
import {PanelDirective} from '../../../features/panel/panel';
class UnknownPanel extends PanelDirective {
templateUrl = 'public/app/plugins/panel/unknown/module.html';
}
export {
UnknownPanel,
UnknownPanel as Panel
}

View File

@ -9,7 +9,8 @@ define([
beforeEach(module('grafana.services'));
beforeEach(inject(function(dashboardViewStateSrv, $location, $rootScope) {
$rootScope.onAppEvent = function(){};
$rootScope.onAppEvent = function() {};
$rootScope.dashboard = {meta: {}};
viewState = dashboardViewStateSrv.create($rootScope);
location = $location;
}));
@ -19,7 +20,7 @@ define([
var updateState = { fullscreen: true, edit: true, panelId: 1 };
viewState.update(updateState);
expect(location.search()).to.eql(updateState);
expect(viewState.fullscreen).to.be(true);
expect(viewState.dashboard.meta.fullscreen).to.be(true);
expect(viewState.state.fullscreen).to.be(true);
});
});
@ -29,7 +30,7 @@ define([
viewState.update({fullscreen: true, panelId: 1, edit: true});
viewState.update({fullscreen: false});
expect(location.search()).to.eql({});
expect(viewState.fullscreen).to.be(false);
expect(viewState.dashboard.meta.fullscreen).to.be(false);
expect(viewState.state.fullscreen).to.be(null);
});
});

View File

@ -1,7 +1,8 @@
define([
'lodash',
'app/core/config',
'app/core/utils/datemath',
], function(_, dateMath) {
], function(_, config, dateMath) {
'use strict';
function ControllerTestContext() {
@ -36,6 +37,28 @@ define([
});
};
this.createPanelController = function(Ctrl) {
return inject(function($controller, $rootScope, $q, $location, $browser) {
self.scope = $rootScope.$new();
self.$location = $location;
self.$browser = $browser;
self.$q = $q;
self.panel = {type: 'test'};
self.dashboard = {meta: {}};
$rootScope.appEvent = sinon.spy();
$rootScope.onAppEvent = sinon.spy();
$rootScope.colors = [];
for (var i = 0; i < 50; i++) { $rootScope.colors.push('#' + i); }
config.panels['test'] = {info: {}};
self.ctrl = $controller(Ctrl, {$scope: self.scope}, {
panel: self.panel, dashboard: self.dashboard
});
});
};
this.createControllerPhase = function(controllerName) {
return inject(function($controller, $rootScope, $q, $location, $browser) {
self.scope = $rootScope.$new();
@ -44,7 +67,7 @@ define([
self.scope.contextSrv = {};
self.scope.panel = {};
self.scope.row = { panels:[] };
self.scope.dashboard = {};
self.scope.dashboard = {meta: {}};
self.scope.dashboardMeta = {};
self.scope.dashboardViewState = new DashboardViewStateStub();
self.scope.appEvent = sinon.spy();
@ -59,7 +82,6 @@ define([
self.controller = $controller(controllerName, {
$scope: self.scope
});
});
};
}
@ -74,10 +96,10 @@ define([
self.$routeParams = {};
this.providePhase = function(mocks) {
return module(function($provide) {
_.each(mocks, function(key) {
$provide.value(key, self[key]);
});
return module(function($provide) {
_.each(mocks, function(key) {
$provide.value(key, self[key]);
});
});
};

View File

@ -9,16 +9,25 @@ define([
var popoverSrv = {};
beforeEach(module('grafana.services'));
beforeEach(module('grafana.panels.graph'));
beforeEach(module('grafana.controllers'));
beforeEach(ctx.providePhase({
popoverSrv: popoverSrv
}));
beforeEach(ctx.createControllerPhase('SeriesOverridesCtrl'));
beforeEach(function() {
beforeEach(inject(function($rootScope, $controller) {
// ctx.createControllerPhase('SeriesOverridesCtrl'));
// beforeEach(function() {
ctx.scope = $rootScope.$new();
ctx.scope.ctrl = {
render: sinon.spy(),
seriesList: []
};
ctx.scope.render = function() {};
});
ctx.controller = $controller('SeriesOverridesCtrl', {
$scope: ctx.scope
});
}));
describe('When setting an override', function() {
beforeEach(function() {

View File

@ -1,5 +0,0 @@
export { Animation } from './src/animate/animation';
export { AnimationBuilder } from './src/animate/animation_builder';
export { BrowserDetails } from './src/animate/browser_details';
export { CssAnimationBuilder } from './src/animate/css_animation_builder';
export { CssAnimationOptions } from './src/animate/css_animation_options';

View File

@ -1,6 +0,0 @@
/**
* See {@link bootstrap} for more information.
* @deprecated
*/
export { bootstrap } from 'angular2/platform/browser';
export { AngularEntrypoint } from 'angular2/src/core/angular_entrypoint';

View File

@ -1,6 +0,0 @@
/**
* See {@link bootstrap} for more information.
* @deprecated
*/
export { bootstrapStatic } from 'angular2/platform/browser_static';
export { AngularEntrypoint } from 'angular2/src/core/angular_entrypoint';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,774 +0,0 @@
"format register";
System.register("angular2/src/upgrade/metadata", ["angular2/core"], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
var core_1 = require("angular2/core");
var COMPONENT_SELECTOR = /^[\w|-]*$/;
var SKEWER_CASE = /-(\w)/g;
var directiveResolver = new core_1.DirectiveResolver();
function getComponentInfo(type) {
var resolvedMetadata = directiveResolver.resolve(type);
var selector = resolvedMetadata.selector;
if (!selector.match(COMPONENT_SELECTOR)) {
throw new Error('Only selectors matching element names are supported, got: ' + selector);
}
var selector = selector.replace(SKEWER_CASE, function(all, letter) {
return letter.toUpperCase();
});
return {
type: type,
selector: selector,
inputs: parseFields(resolvedMetadata.inputs),
outputs: parseFields(resolvedMetadata.outputs)
};
}
exports.getComponentInfo = getComponentInfo;
function parseFields(names) {
var attrProps = [];
if (names) {
for (var i = 0; i < names.length; i++) {
var parts = names[i].split(':');
var prop = parts[0].trim();
var attr = (parts[1] || parts[0]).trim();
var capitalAttr = attr.charAt(0).toUpperCase() + attr.substr(1);
attrProps.push({
prop: prop,
attr: attr,
bracketAttr: "[" + attr + "]",
parenAttr: "(" + attr + ")",
bracketParenAttr: "[(" + attr + ")]",
onAttr: "on" + capitalAttr,
bindAttr: "bind" + capitalAttr,
bindonAttr: "bindon" + capitalAttr
});
}
}
return attrProps;
}
exports.parseFields = parseFields;
global.define = __define;
return module.exports;
});
System.register("angular2/src/upgrade/util", [], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
function stringify(obj) {
if (typeof obj == 'function')
return obj.name || obj.toString();
return '' + obj;
}
exports.stringify = stringify;
function onError(e) {
console.log(e, e.stack);
throw e;
}
exports.onError = onError;
function controllerKey(name) {
return '$' + name + 'Controller';
}
exports.controllerKey = controllerKey;
global.define = __define;
return module.exports;
});
System.register("angular2/src/upgrade/constants", [], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
exports.NG2_APP_VIEW_MANAGER = 'ng2.AppViewManager';
exports.NG2_COMPILER = 'ng2.Compiler';
exports.NG2_INJECTOR = 'ng2.Injector';
exports.NG2_PROTO_VIEW_REF_MAP = 'ng2.ProtoViewRefMap';
exports.NG2_ZONE = 'ng2.NgZone';
exports.NG1_CONTROLLER = '$controller';
exports.NG1_SCOPE = '$scope';
exports.NG1_ROOT_SCOPE = '$rootScope';
exports.NG1_COMPILE = '$compile';
exports.NG1_HTTP_BACKEND = '$httpBackend';
exports.NG1_INJECTOR = '$injector';
exports.NG1_PARSE = '$parse';
exports.NG1_TEMPLATE_CACHE = '$templateCache';
exports.REQUIRE_INJECTOR = '^' + exports.NG2_INJECTOR;
global.define = __define;
return module.exports;
});
System.register("angular2/src/upgrade/downgrade_ng2_adapter", ["angular2/core", "angular2/src/upgrade/constants"], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
var core_1 = require("angular2/core");
var constants_1 = require("angular2/src/upgrade/constants");
var INITIAL_VALUE = {__UNINITIALIZED__: true};
var DowngradeNg2ComponentAdapter = (function() {
function DowngradeNg2ComponentAdapter(id, info, element, attrs, scope, parentInjector, parse, viewManager, protoView) {
this.id = id;
this.info = info;
this.element = element;
this.attrs = attrs;
this.scope = scope;
this.parentInjector = parentInjector;
this.parse = parse;
this.viewManager = viewManager;
this.protoView = protoView;
this.component = null;
this.inputChangeCount = 0;
this.inputChanges = null;
this.hostViewRef = null;
this.changeDetector = null;
this.contentInserctionPoint = null;
this.element[0].id = id;
this.componentScope = scope.$new();
this.childNodes = element.contents();
}
DowngradeNg2ComponentAdapter.prototype.bootstrapNg2 = function() {
var childInjector = this.parentInjector.resolveAndCreateChild([core_1.provide(constants_1.NG1_SCOPE, {useValue: this.componentScope})]);
this.hostViewRef = this.viewManager.createRootHostView(this.protoView, '#' + this.id, childInjector);
var renderer = this.hostViewRef.render;
var hostElement = this.viewManager.getHostElement(this.hostViewRef);
this.changeDetector = this.hostViewRef.changeDetectorRef;
this.component = this.viewManager.getComponent(hostElement);
this.contentInserctionPoint = renderer.rootContentInsertionPoints[0];
};
DowngradeNg2ComponentAdapter.prototype.setupInputs = function() {
var _this = this;
var attrs = this.attrs;
var inputs = this.info.inputs;
for (var i = 0; i < inputs.length; i++) {
var input = inputs[i];
var expr = null;
if (attrs.hasOwnProperty(input.attr)) {
var observeFn = (function(prop) {
var prevValue = INITIAL_VALUE;
return function(value) {
if (_this.inputChanges !== null) {
_this.inputChangeCount++;
_this.inputChanges[prop] = new Ng1Change(value, prevValue === INITIAL_VALUE ? value : prevValue);
prevValue = value;
}
_this.component[prop] = value;
};
})(input.prop);
attrs.$observe(input.attr, observeFn);
} else if (attrs.hasOwnProperty(input.bindAttr)) {
expr = attrs[input.bindAttr];
} else if (attrs.hasOwnProperty(input.bracketAttr)) {
expr = attrs[input.bracketAttr];
} else if (attrs.hasOwnProperty(input.bindonAttr)) {
expr = attrs[input.bindonAttr];
} else if (attrs.hasOwnProperty(input.bracketParenAttr)) {
expr = attrs[input.bracketParenAttr];
}
if (expr != null) {
var watchFn = (function(prop) {
return function(value, prevValue) {
if (_this.inputChanges != null) {
_this.inputChangeCount++;
_this.inputChanges[prop] = new Ng1Change(prevValue, value);
}
_this.component[prop] = value;
};
})(input.prop);
this.componentScope.$watch(expr, watchFn);
}
}
var prototype = this.info.type.prototype;
if (prototype && prototype.ngOnChanges) {
this.inputChanges = {};
this.componentScope.$watch(function() {
return _this.inputChangeCount;
}, function() {
var inputChanges = _this.inputChanges;
_this.inputChanges = {};
_this.component.ngOnChanges(inputChanges);
});
}
this.componentScope.$watch(function() {
return _this.changeDetector && _this.changeDetector.detectChanges();
});
};
DowngradeNg2ComponentAdapter.prototype.projectContent = function() {
var childNodes = this.childNodes;
if (this.contentInserctionPoint) {
var parent = this.contentInserctionPoint.parentNode;
for (var i = 0,
ii = childNodes.length; i < ii; i++) {
parent.insertBefore(childNodes[i], this.contentInserctionPoint);
}
}
};
DowngradeNg2ComponentAdapter.prototype.setupOutputs = function() {
var _this = this;
var attrs = this.attrs;
var outputs = this.info.outputs;
for (var j = 0; j < outputs.length; j++) {
var output = outputs[j];
var expr = null;
var assignExpr = false;
var bindonAttr = output.bindonAttr ? output.bindonAttr.substring(0, output.bindonAttr.length - 6) : null;
var bracketParenAttr = output.bracketParenAttr ? "[(" + output.bracketParenAttr.substring(2, output.bracketParenAttr.length - 8) + ")]" : null;
if (attrs.hasOwnProperty(output.onAttr)) {
expr = attrs[output.onAttr];
} else if (attrs.hasOwnProperty(output.parenAttr)) {
expr = attrs[output.parenAttr];
} else if (attrs.hasOwnProperty(bindonAttr)) {
expr = attrs[bindonAttr];
assignExpr = true;
} else if (attrs.hasOwnProperty(bracketParenAttr)) {
expr = attrs[bracketParenAttr];
assignExpr = true;
}
if (expr != null && assignExpr != null) {
var getter = this.parse(expr);
var setter = getter.assign;
if (assignExpr && !setter) {
throw new Error("Expression '" + expr + "' is not assignable!");
}
var emitter = this.component[output.prop];
if (emitter) {
emitter.subscribe({next: assignExpr ? (function(setter) {
return function(value) {
return setter(_this.scope, value);
};
})(setter) : (function(getter) {
return function(value) {
return getter(_this.scope, {$event: value});
};
})(getter)});
} else {
throw new Error("Missing emitter '" + output.prop + "' on component '" + this.info.selector + "'!");
}
}
}
};
DowngradeNg2ComponentAdapter.prototype.registerCleanup = function() {
var _this = this;
this.element.bind('$remove', function() {
return _this.viewManager.destroyRootHostView(_this.hostViewRef);
});
};
return DowngradeNg2ComponentAdapter;
})();
exports.DowngradeNg2ComponentAdapter = DowngradeNg2ComponentAdapter;
var Ng1Change = (function() {
function Ng1Change(previousValue, currentValue) {
this.previousValue = previousValue;
this.currentValue = currentValue;
}
Ng1Change.prototype.isFirstChange = function() {
return this.previousValue === this.currentValue;
};
return Ng1Change;
})();
global.define = __define;
return module.exports;
});
System.register("angular2/src/upgrade/angular_js", [], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
function noNg() {
throw new Error('AngularJS v1.x is not loaded!');
}
var angular = {
bootstrap: noNg,
module: noNg,
element: noNg,
version: noNg
};
try {
if (window.hasOwnProperty('angular')) {
angular = window.angular;
}
} catch (e) {}
exports.bootstrap = angular.bootstrap;
exports.module = angular.module;
exports.element = angular.element;
exports.version = angular.version;
global.define = __define;
return module.exports;
});
System.register("angular2/src/upgrade/upgrade_ng1_adapter", ["angular2/core", "angular2/src/upgrade/constants", "angular2/src/upgrade/util", "angular2/src/upgrade/angular_js"], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
var core_1 = require("angular2/core");
var constants_1 = require("angular2/src/upgrade/constants");
var util_1 = require("angular2/src/upgrade/util");
var angular = require("angular2/src/upgrade/angular_js");
var CAMEL_CASE = /([A-Z])/g;
var INITIAL_VALUE = {__UNINITIALIZED__: true};
var NOT_SUPPORTED = 'NOT_SUPPORTED';
var UpgradeNg1ComponentAdapterBuilder = (function() {
function UpgradeNg1ComponentAdapterBuilder(name) {
this.name = name;
this.inputs = [];
this.inputsRename = [];
this.outputs = [];
this.outputsRename = [];
this.propertyOutputs = [];
this.checkProperties = [];
this.propertyMap = {};
this.linkFn = null;
this.directive = null;
this.$controller = null;
var selector = name.replace(CAMEL_CASE, function(all, next) {
return '-' + next.toLowerCase();
});
var self = this;
this.type = core_1.Directive({
selector: selector,
inputs: this.inputsRename,
outputs: this.outputsRename
}).Class({
constructor: [new core_1.Inject(constants_1.NG1_SCOPE), core_1.ElementRef, function(scope, elementRef) {
return new UpgradeNg1ComponentAdapter(self.linkFn, scope, self.directive, elementRef, self.$controller, self.inputs, self.outputs, self.propertyOutputs, self.checkProperties, self.propertyMap);
}],
ngOnChanges: function() {},
ngDoCheck: function() {}
});
}
UpgradeNg1ComponentAdapterBuilder.prototype.extractDirective = function(injector) {
var directives = injector.get(this.name + 'Directive');
if (directives.length > 1) {
throw new Error('Only support single directive definition for: ' + this.name);
}
var directive = directives[0];
if (directive.replace)
this.notSupported('replace');
if (directive.terminal)
this.notSupported('terminal');
var link = directive.link;
if (typeof link == 'object') {
if (link.post)
this.notSupported('link.post');
}
return directive;
};
UpgradeNg1ComponentAdapterBuilder.prototype.notSupported = function(feature) {
throw new Error("Upgraded directive '" + this.name + "' does not support '" + feature + "'.");
};
UpgradeNg1ComponentAdapterBuilder.prototype.extractBindings = function() {
var scope = this.directive.scope;
if (typeof scope == 'object') {
for (var name in scope) {
if (scope.hasOwnProperty(name)) {
var localName = scope[name];
var type = localName.charAt(0);
localName = localName.substr(1) || name;
var outputName = 'output_' + name;
var outputNameRename = outputName + ': ' + name;
var outputNameRenameChange = outputName + ': ' + name + 'Change';
var inputName = 'input_' + name;
var inputNameRename = inputName + ': ' + name;
switch (type) {
case '=':
this.propertyOutputs.push(outputName);
this.checkProperties.push(localName);
this.outputs.push(outputName);
this.outputsRename.push(outputNameRenameChange);
this.propertyMap[outputName] = localName;
case '@':
this.inputs.push(inputName);
this.inputsRename.push(inputNameRename);
this.propertyMap[inputName] = localName;
break;
case '&':
this.outputs.push(outputName);
this.outputsRename.push(outputNameRename);
this.propertyMap[outputName] = localName;
break;
default:
var json = JSON.stringify(scope);
throw new Error("Unexpected mapping '" + type + "' in '" + json + "' in '" + this.name + "' directive.");
}
}
}
}
};
UpgradeNg1ComponentAdapterBuilder.prototype.compileTemplate = function(compile, templateCache, httpBackend) {
var _this = this;
if (this.directive.template !== undefined) {
this.linkFn = compileHtml(this.directive.template);
} else if (this.directive.templateUrl) {
var url = this.directive.templateUrl;
var html = templateCache.get(url);
if (html !== undefined) {
this.linkFn = compileHtml(html);
} else {
return new Promise(function(resolve, err) {
httpBackend('GET', url, null, function(status, response) {
if (status == 200) {
resolve(_this.linkFn = compileHtml(templateCache.put(url, response)));
} else {
err("GET " + url + " returned " + status + ": " + response);
}
});
});
}
} else {
throw new Error("Directive '" + this.name + "' is not a component, it is missing template.");
}
return null;
function compileHtml(html) {
var div = document.createElement('div');
div.innerHTML = html;
return compile(div.childNodes);
}
};
UpgradeNg1ComponentAdapterBuilder.resolve = function(exportedComponents, injector) {
var promises = [];
var compile = injector.get(constants_1.NG1_COMPILE);
var templateCache = injector.get(constants_1.NG1_TEMPLATE_CACHE);
var httpBackend = injector.get(constants_1.NG1_HTTP_BACKEND);
var $controller = injector.get(constants_1.NG1_CONTROLLER);
for (var name in exportedComponents) {
if (exportedComponents.hasOwnProperty(name)) {
var exportedComponent = exportedComponents[name];
exportedComponent.directive = exportedComponent.extractDirective(injector);
exportedComponent.$controller = $controller;
exportedComponent.extractBindings();
var promise = exportedComponent.compileTemplate(compile, templateCache, httpBackend);
if (promise)
promises.push(promise);
}
}
return Promise.all(promises);
};
return UpgradeNg1ComponentAdapterBuilder;
})();
exports.UpgradeNg1ComponentAdapterBuilder = UpgradeNg1ComponentAdapterBuilder;
var UpgradeNg1ComponentAdapter = (function() {
function UpgradeNg1ComponentAdapter(linkFn, scope, directive, elementRef, $controller, inputs, outputs, propOuts, checkProperties, propertyMap) {
this.directive = directive;
this.inputs = inputs;
this.outputs = outputs;
this.propOuts = propOuts;
this.checkProperties = checkProperties;
this.propertyMap = propertyMap;
this.destinationObj = null;
this.checkLastValues = [];
var element = elementRef.nativeElement;
var childNodes = [];
var childNode;
while (childNode = element.firstChild) {
element.removeChild(childNode);
childNodes.push(childNode);
}
var componentScope = scope.$new(!!directive.scope);
var $element = angular.element(element);
var controllerType = directive.controller;
var controller = null;
if (controllerType) {
var locals = {
$scope: componentScope,
$element: $element
};
controller = $controller(controllerType, locals, null, directive.controllerAs);
$element.data(util_1.controllerKey(directive.name), controller);
}
var link = directive.link;
if (typeof link == 'object')
link = link.pre;
if (link) {
var attrs = NOT_SUPPORTED;
var transcludeFn = NOT_SUPPORTED;
var linkController = this.resolveRequired($element, directive.require);
directive.link(componentScope, $element, attrs, linkController, transcludeFn);
}
this.destinationObj = directive.bindToController && controller ? controller : componentScope;
linkFn(componentScope, function(clonedElement, scope) {
for (var i = 0,
ii = clonedElement.length; i < ii; i++) {
element.appendChild(clonedElement[i]);
}
}, {parentBoundTranscludeFn: function(scope, cloneAttach) {
cloneAttach(childNodes);
}});
for (var i = 0; i < inputs.length; i++) {
this[inputs[i]] = null;
}
for (var j = 0; j < outputs.length; j++) {
var emitter = this[outputs[j]] = new core_1.EventEmitter();
this.setComponentProperty(outputs[j], (function(emitter) {
return function(value) {
return emitter.emit(value);
};
})(emitter));
}
for (var k = 0; k < propOuts.length; k++) {
this[propOuts[k]] = new core_1.EventEmitter();
this.checkLastValues.push(INITIAL_VALUE);
}
}
UpgradeNg1ComponentAdapter.prototype.ngOnChanges = function(changes) {
for (var name in changes) {
if (changes.hasOwnProperty(name)) {
var change = changes[name];
this.setComponentProperty(name, change.currentValue);
}
}
};
UpgradeNg1ComponentAdapter.prototype.ngDoCheck = function() {
var count = 0;
var destinationObj = this.destinationObj;
var lastValues = this.checkLastValues;
var checkProperties = this.checkProperties;
for (var i = 0; i < checkProperties.length; i++) {
var value = destinationObj[checkProperties[i]];
var last = lastValues[i];
if (value !== last) {
if (typeof value == 'number' && isNaN(value) && typeof last == 'number' && isNaN(last)) {} else {
var eventEmitter = this[this.propOuts[i]];
eventEmitter.emit(lastValues[i] = value);
}
}
}
return count;
};
UpgradeNg1ComponentAdapter.prototype.setComponentProperty = function(name, value) {
this.destinationObj[this.propertyMap[name]] = value;
};
UpgradeNg1ComponentAdapter.prototype.resolveRequired = function($element, require) {
if (!require) {
return undefined;
} else if (typeof require == 'string') {
var name = require;
var isOptional = false;
var startParent = false;
var searchParents = false;
var ch;
if (name.charAt(0) == '?') {
isOptional = true;
name = name.substr(1);
}
if (name.charAt(0) == '^') {
searchParents = true;
name = name.substr(1);
}
if (name.charAt(0) == '^') {
startParent = true;
name = name.substr(1);
}
var key = util_1.controllerKey(name);
if (startParent)
$element = $element.parent();
var dep = searchParents ? $element.inheritedData(key) : $element.data(key);
if (!dep && !isOptional) {
throw new Error("Can not locate '" + require + "' in '" + this.directive.name + "'.");
}
return dep;
} else if (require instanceof Array) {
var deps = [];
for (var i = 0; i < require.length; i++) {
deps.push(this.resolveRequired($element, require[i]));
}
return deps;
}
throw new Error("Directive '" + this.directive.name + "' require syntax unrecognized: " + this.directive.require);
};
return UpgradeNg1ComponentAdapter;
})();
global.define = __define;
return module.exports;
});
System.register("angular2/src/upgrade/upgrade_adapter", ["angular2/core", "angular2/src/facade/async", "angular2/platform/browser", "angular2/src/upgrade/metadata", "angular2/src/upgrade/util", "angular2/src/upgrade/constants", "angular2/src/upgrade/downgrade_ng2_adapter", "angular2/src/upgrade/upgrade_ng1_adapter", "angular2/src/upgrade/angular_js"], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
var core_1 = require("angular2/core");
var async_1 = require("angular2/src/facade/async");
var browser_1 = require("angular2/platform/browser");
var metadata_1 = require("angular2/src/upgrade/metadata");
var util_1 = require("angular2/src/upgrade/util");
var constants_1 = require("angular2/src/upgrade/constants");
var downgrade_ng2_adapter_1 = require("angular2/src/upgrade/downgrade_ng2_adapter");
var upgrade_ng1_adapter_1 = require("angular2/src/upgrade/upgrade_ng1_adapter");
var angular = require("angular2/src/upgrade/angular_js");
var upgradeCount = 0;
var UpgradeAdapter = (function() {
function UpgradeAdapter() {
this.idPrefix = "NG2_UPGRADE_" + upgradeCount++ + "_";
this.upgradedComponents = [];
this.downgradedComponents = {};
this.providers = [];
}
UpgradeAdapter.prototype.downgradeNg2Component = function(type) {
this.upgradedComponents.push(type);
var info = metadata_1.getComponentInfo(type);
return ng1ComponentDirective(info, "" + this.idPrefix + info.selector + "_c");
};
UpgradeAdapter.prototype.upgradeNg1Component = function(name) {
if (this.downgradedComponents.hasOwnProperty(name)) {
return this.downgradedComponents[name].type;
} else {
return (this.downgradedComponents[name] = new upgrade_ng1_adapter_1.UpgradeNg1ComponentAdapterBuilder(name)).type;
}
};
UpgradeAdapter.prototype.bootstrap = function(element, modules, config) {
var _this = this;
var upgrade = new UpgradeAdapterRef();
var ng1Injector = null;
var platformRef = core_1.platform(browser_1.BROWSER_PROVIDERS);
var applicationRef = platformRef.application([browser_1.BROWSER_APP_PROVIDERS, core_1.provide(constants_1.NG1_INJECTOR, {useFactory: function() {
return ng1Injector;
}}), core_1.provide(constants_1.NG1_COMPILE, {useFactory: function() {
return ng1Injector.get(constants_1.NG1_COMPILE);
}}), this.providers]);
var injector = applicationRef.injector;
var ngZone = injector.get(core_1.NgZone);
var compiler = injector.get(core_1.Compiler);
var delayApplyExps = [];
var original$applyFn;
var rootScopePrototype;
var rootScope;
var protoViewRefMap = {};
var ng1Module = angular.module(this.idPrefix, modules);
var ng1compilePromise = null;
ng1Module.value(constants_1.NG2_INJECTOR, injector).value(constants_1.NG2_ZONE, ngZone).value(constants_1.NG2_COMPILER, compiler).value(constants_1.NG2_PROTO_VIEW_REF_MAP, protoViewRefMap).value(constants_1.NG2_APP_VIEW_MANAGER, injector.get(core_1.AppViewManager)).config(['$provide', function(provide) {
provide.decorator(constants_1.NG1_ROOT_SCOPE, ['$delegate', function(rootScopeDelegate) {
rootScopePrototype = rootScopeDelegate.constructor.prototype;
if (rootScopePrototype.hasOwnProperty('$apply')) {
original$applyFn = rootScopePrototype.$apply;
rootScopePrototype.$apply = function(exp) {
return delayApplyExps.push(exp);
};
} else {
throw new Error("Failed to find '$apply' on '$rootScope'!");
}
return rootScope = rootScopeDelegate;
}]);
}]).run(['$injector', '$rootScope', function(injector, rootScope) {
ng1Injector = injector;
async_1.ObservableWrapper.subscribe(ngZone.onTurnDone, function(_) {
ngZone.run(function() {
return rootScope.$apply();
});
});
ng1compilePromise = upgrade_ng1_adapter_1.UpgradeNg1ComponentAdapterBuilder.resolve(_this.downgradedComponents, injector);
}]);
angular.element(element).data(util_1.controllerKey(constants_1.NG2_INJECTOR), injector);
ngZone.run(function() {
angular.bootstrap(element, [_this.idPrefix], config);
});
Promise.all([this.compileNg2Components(compiler, protoViewRefMap), ng1compilePromise]).then(function() {
ngZone.run(function() {
if (rootScopePrototype) {
rootScopePrototype.$apply = original$applyFn;
while (delayApplyExps.length) {
rootScope.$apply(delayApplyExps.shift());
}
upgrade._bootstrapDone(applicationRef, ng1Injector);
rootScopePrototype = null;
}
});
}, util_1.onError);
return upgrade;
};
UpgradeAdapter.prototype.addProvider = function(provider) {
this.providers.push(provider);
};
UpgradeAdapter.prototype.upgradeNg1Provider = function(name, options) {
var token = options && options.asToken || name;
this.providers.push(core_1.provide(token, {
useFactory: function(ng1Injector) {
return ng1Injector.get(name);
},
deps: [constants_1.NG1_INJECTOR]
}));
};
UpgradeAdapter.prototype.downgradeNg2Provider = function(token) {
var factory = function(injector) {
return injector.get(token);
};
factory.$inject = [constants_1.NG2_INJECTOR];
return factory;
};
UpgradeAdapter.prototype.compileNg2Components = function(compiler, protoViewRefMap) {
var _this = this;
var promises = [];
var types = this.upgradedComponents;
for (var i = 0; i < types.length; i++) {
promises.push(compiler.compileInHost(types[i]));
}
return Promise.all(promises).then(function(protoViews) {
var types = _this.upgradedComponents;
for (var i = 0; i < protoViews.length; i++) {
protoViewRefMap[metadata_1.getComponentInfo(types[i]).selector] = protoViews[i];
}
return protoViewRefMap;
}, util_1.onError);
};
return UpgradeAdapter;
})();
exports.UpgradeAdapter = UpgradeAdapter;
function ng1ComponentDirective(info, idPrefix) {
directiveFactory.$inject = [constants_1.NG2_PROTO_VIEW_REF_MAP, constants_1.NG2_APP_VIEW_MANAGER, constants_1.NG1_PARSE];
function directiveFactory(protoViewRefMap, viewManager, parse) {
var protoView = protoViewRefMap[info.selector];
if (!protoView)
throw new Error('Expecting ProtoViewRef for: ' + info.selector);
var idCount = 0;
return {
restrict: 'E',
require: constants_1.REQUIRE_INJECTOR,
link: {post: function(scope, element, attrs, parentInjector, transclude) {
var domElement = element[0];
var facade = new downgrade_ng2_adapter_1.DowngradeNg2ComponentAdapter(idPrefix + (idCount++), info, element, attrs, scope, parentInjector, parse, viewManager, protoView);
facade.setupInputs();
facade.bootstrapNg2();
facade.projectContent();
facade.setupOutputs();
facade.registerCleanup();
}}
};
}
return directiveFactory;
}
var UpgradeAdapterRef = (function() {
function UpgradeAdapterRef() {
this._readyFn = null;
this.ng1RootScope = null;
this.ng1Injector = null;
this.ng2ApplicationRef = null;
this.ng2Injector = null;
}
UpgradeAdapterRef.prototype._bootstrapDone = function(applicationRef, ng1Injector) {
this.ng2ApplicationRef = applicationRef;
this.ng2Injector = applicationRef.injector;
this.ng1Injector = ng1Injector;
this.ng1RootScope = ng1Injector.get(constants_1.NG1_ROOT_SCOPE);
this._readyFn && this._readyFn(this);
};
UpgradeAdapterRef.prototype.ready = function(fn) {
this._readyFn = fn;
};
UpgradeAdapterRef.prototype.dispose = function() {
this.ng1Injector.get(constants_1.NG1_ROOT_SCOPE).$destroy();
this.ng2ApplicationRef.dispose();
};
return UpgradeAdapterRef;
})();
exports.UpgradeAdapterRef = UpgradeAdapterRef;
global.define = __define;
return module.exports;
});
System.register("angular2/upgrade", ["angular2/src/upgrade/upgrade_adapter"], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
var upgrade_adapter_1 = require("angular2/src/upgrade/upgrade_adapter");
exports.UpgradeAdapter = upgrade_adapter_1.UpgradeAdapter;
exports.UpgradeAdapterRef = upgrade_adapter_1.UpgradeAdapterRef;
global.define = __define;
return module.exports;
});
//# sourceMappingURLDisabled=upgrade.dev.js.map

View File

@ -1,774 +0,0 @@
"format register";
System.register("angular2/src/upgrade/metadata", ["angular2/core"], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
var core_1 = require("angular2/core");
var COMPONENT_SELECTOR = /^[\w|-]*$/;
var SKEWER_CASE = /-(\w)/g;
var directiveResolver = new core_1.DirectiveResolver();
function getComponentInfo(type) {
var resolvedMetadata = directiveResolver.resolve(type);
var selector = resolvedMetadata.selector;
if (!selector.match(COMPONENT_SELECTOR)) {
throw new Error('Only selectors matching element names are supported, got: ' + selector);
}
var selector = selector.replace(SKEWER_CASE, function(all, letter) {
return letter.toUpperCase();
});
return {
type: type,
selector: selector,
inputs: parseFields(resolvedMetadata.inputs),
outputs: parseFields(resolvedMetadata.outputs)
};
}
exports.getComponentInfo = getComponentInfo;
function parseFields(names) {
var attrProps = [];
if (names) {
for (var i = 0; i < names.length; i++) {
var parts = names[i].split(':');
var prop = parts[0].trim();
var attr = (parts[1] || parts[0]).trim();
var capitalAttr = attr.charAt(0).toUpperCase() + attr.substr(1);
attrProps.push({
prop: prop,
attr: attr,
bracketAttr: "[" + attr + "]",
parenAttr: "(" + attr + ")",
bracketParenAttr: "[(" + attr + ")]",
onAttr: "on" + capitalAttr,
bindAttr: "bind" + capitalAttr,
bindonAttr: "bindon" + capitalAttr
});
}
}
return attrProps;
}
exports.parseFields = parseFields;
global.define = __define;
return module.exports;
});
System.register("angular2/src/upgrade/util", [], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
function stringify(obj) {
if (typeof obj == 'function')
return obj.name || obj.toString();
return '' + obj;
}
exports.stringify = stringify;
function onError(e) {
console.log(e, e.stack);
throw e;
}
exports.onError = onError;
function controllerKey(name) {
return '$' + name + 'Controller';
}
exports.controllerKey = controllerKey;
global.define = __define;
return module.exports;
});
System.register("angular2/src/upgrade/constants", [], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
exports.NG2_APP_VIEW_MANAGER = 'ng2.AppViewManager';
exports.NG2_COMPILER = 'ng2.Compiler';
exports.NG2_INJECTOR = 'ng2.Injector';
exports.NG2_PROTO_VIEW_REF_MAP = 'ng2.ProtoViewRefMap';
exports.NG2_ZONE = 'ng2.NgZone';
exports.NG1_CONTROLLER = '$controller';
exports.NG1_SCOPE = '$scope';
exports.NG1_ROOT_SCOPE = '$rootScope';
exports.NG1_COMPILE = '$compile';
exports.NG1_HTTP_BACKEND = '$httpBackend';
exports.NG1_INJECTOR = '$injector';
exports.NG1_PARSE = '$parse';
exports.NG1_TEMPLATE_CACHE = '$templateCache';
exports.REQUIRE_INJECTOR = '^' + exports.NG2_INJECTOR;
global.define = __define;
return module.exports;
});
System.register("angular2/src/upgrade/downgrade_ng2_adapter", ["angular2/core", "angular2/src/upgrade/constants"], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
var core_1 = require("angular2/core");
var constants_1 = require("angular2/src/upgrade/constants");
var INITIAL_VALUE = {__UNINITIALIZED__: true};
var DowngradeNg2ComponentAdapter = (function() {
function DowngradeNg2ComponentAdapter(id, info, element, attrs, scope, parentInjector, parse, viewManager, protoView) {
this.id = id;
this.info = info;
this.element = element;
this.attrs = attrs;
this.scope = scope;
this.parentInjector = parentInjector;
this.parse = parse;
this.viewManager = viewManager;
this.protoView = protoView;
this.component = null;
this.inputChangeCount = 0;
this.inputChanges = null;
this.hostViewRef = null;
this.changeDetector = null;
this.contentInserctionPoint = null;
this.element[0].id = id;
this.componentScope = scope.$new();
this.childNodes = element.contents();
}
DowngradeNg2ComponentAdapter.prototype.bootstrapNg2 = function() {
var childInjector = this.parentInjector.resolveAndCreateChild([core_1.provide(constants_1.NG1_SCOPE, {useValue: this.componentScope})]);
this.hostViewRef = this.viewManager.createRootHostView(this.protoView, '#' + this.id, childInjector);
var renderer = this.hostViewRef.render;
var hostElement = this.viewManager.getHostElement(this.hostViewRef);
this.changeDetector = this.hostViewRef.changeDetectorRef;
this.component = this.viewManager.getComponent(hostElement);
this.contentInserctionPoint = renderer.rootContentInsertionPoints[0];
};
DowngradeNg2ComponentAdapter.prototype.setupInputs = function() {
var _this = this;
var attrs = this.attrs;
var inputs = this.info.inputs;
for (var i = 0; i < inputs.length; i++) {
var input = inputs[i];
var expr = null;
if (attrs.hasOwnProperty(input.attr)) {
var observeFn = (function(prop) {
var prevValue = INITIAL_VALUE;
return function(value) {
if (_this.inputChanges !== null) {
_this.inputChangeCount++;
_this.inputChanges[prop] = new Ng1Change(value, prevValue === INITIAL_VALUE ? value : prevValue);
prevValue = value;
}
_this.component[prop] = value;
};
})(input.prop);
attrs.$observe(input.attr, observeFn);
} else if (attrs.hasOwnProperty(input.bindAttr)) {
expr = attrs[input.bindAttr];
} else if (attrs.hasOwnProperty(input.bracketAttr)) {
expr = attrs[input.bracketAttr];
} else if (attrs.hasOwnProperty(input.bindonAttr)) {
expr = attrs[input.bindonAttr];
} else if (attrs.hasOwnProperty(input.bracketParenAttr)) {
expr = attrs[input.bracketParenAttr];
}
if (expr != null) {
var watchFn = (function(prop) {
return function(value, prevValue) {
if (_this.inputChanges != null) {
_this.inputChangeCount++;
_this.inputChanges[prop] = new Ng1Change(prevValue, value);
}
_this.component[prop] = value;
};
})(input.prop);
this.componentScope.$watch(expr, watchFn);
}
}
var prototype = this.info.type.prototype;
if (prototype && prototype.ngOnChanges) {
this.inputChanges = {};
this.componentScope.$watch(function() {
return _this.inputChangeCount;
}, function() {
var inputChanges = _this.inputChanges;
_this.inputChanges = {};
_this.component.ngOnChanges(inputChanges);
});
}
this.componentScope.$watch(function() {
return _this.changeDetector && _this.changeDetector.detectChanges();
});
};
DowngradeNg2ComponentAdapter.prototype.projectContent = function() {
var childNodes = this.childNodes;
if (this.contentInserctionPoint) {
var parent = this.contentInserctionPoint.parentNode;
for (var i = 0,
ii = childNodes.length; i < ii; i++) {
parent.insertBefore(childNodes[i], this.contentInserctionPoint);
}
}
};
DowngradeNg2ComponentAdapter.prototype.setupOutputs = function() {
var _this = this;
var attrs = this.attrs;
var outputs = this.info.outputs;
for (var j = 0; j < outputs.length; j++) {
var output = outputs[j];
var expr = null;
var assignExpr = false;
var bindonAttr = output.bindonAttr ? output.bindonAttr.substring(0, output.bindonAttr.length - 6) : null;
var bracketParenAttr = output.bracketParenAttr ? "[(" + output.bracketParenAttr.substring(2, output.bracketParenAttr.length - 8) + ")]" : null;
if (attrs.hasOwnProperty(output.onAttr)) {
expr = attrs[output.onAttr];
} else if (attrs.hasOwnProperty(output.parenAttr)) {
expr = attrs[output.parenAttr];
} else if (attrs.hasOwnProperty(bindonAttr)) {
expr = attrs[bindonAttr];
assignExpr = true;
} else if (attrs.hasOwnProperty(bracketParenAttr)) {
expr = attrs[bracketParenAttr];
assignExpr = true;
}
if (expr != null && assignExpr != null) {
var getter = this.parse(expr);
var setter = getter.assign;
if (assignExpr && !setter) {
throw new Error("Expression '" + expr + "' is not assignable!");
}
var emitter = this.component[output.prop];
if (emitter) {
emitter.subscribe({next: assignExpr ? (function(setter) {
return function(value) {
return setter(_this.scope, value);
};
})(setter) : (function(getter) {
return function(value) {
return getter(_this.scope, {$event: value});
};
})(getter)});
} else {
throw new Error("Missing emitter '" + output.prop + "' on component '" + this.info.selector + "'!");
}
}
}
};
DowngradeNg2ComponentAdapter.prototype.registerCleanup = function() {
var _this = this;
this.element.bind('$remove', function() {
return _this.viewManager.destroyRootHostView(_this.hostViewRef);
});
};
return DowngradeNg2ComponentAdapter;
})();
exports.DowngradeNg2ComponentAdapter = DowngradeNg2ComponentAdapter;
var Ng1Change = (function() {
function Ng1Change(previousValue, currentValue) {
this.previousValue = previousValue;
this.currentValue = currentValue;
}
Ng1Change.prototype.isFirstChange = function() {
return this.previousValue === this.currentValue;
};
return Ng1Change;
})();
global.define = __define;
return module.exports;
});
System.register("angular2/src/upgrade/angular_js", [], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
function noNg() {
throw new Error('AngularJS v1.x is not loaded!');
}
var angular = {
bootstrap: noNg,
module: noNg,
element: noNg,
version: noNg
};
try {
if (window.hasOwnProperty('angular')) {
angular = window.angular;
}
} catch (e) {}
exports.bootstrap = angular.bootstrap;
exports.module = angular.module;
exports.element = angular.element;
exports.version = angular.version;
global.define = __define;
return module.exports;
});
System.register("angular2/src/upgrade/upgrade_ng1_adapter", ["angular2/core", "angular2/src/upgrade/constants", "angular2/src/upgrade/util", "angular2/src/upgrade/angular_js"], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
var core_1 = require("angular2/core");
var constants_1 = require("angular2/src/upgrade/constants");
var util_1 = require("angular2/src/upgrade/util");
var angular = require("angular2/src/upgrade/angular_js");
var CAMEL_CASE = /([A-Z])/g;
var INITIAL_VALUE = {__UNINITIALIZED__: true};
var NOT_SUPPORTED = 'NOT_SUPPORTED';
var UpgradeNg1ComponentAdapterBuilder = (function() {
function UpgradeNg1ComponentAdapterBuilder(name) {
this.name = name;
this.inputs = [];
this.inputsRename = [];
this.outputs = [];
this.outputsRename = [];
this.propertyOutputs = [];
this.checkProperties = [];
this.propertyMap = {};
this.linkFn = null;
this.directive = null;
this.$controller = null;
var selector = name.replace(CAMEL_CASE, function(all, next) {
return '-' + next.toLowerCase();
});
var self = this;
this.type = core_1.Directive({
selector: selector,
inputs: this.inputsRename,
outputs: this.outputsRename
}).Class({
constructor: [new core_1.Inject(constants_1.NG1_SCOPE), core_1.ElementRef, function(scope, elementRef) {
return new UpgradeNg1ComponentAdapter(self.linkFn, scope, self.directive, elementRef, self.$controller, self.inputs, self.outputs, self.propertyOutputs, self.checkProperties, self.propertyMap);
}],
ngOnChanges: function() {},
ngDoCheck: function() {}
});
}
UpgradeNg1ComponentAdapterBuilder.prototype.extractDirective = function(injector) {
var directives = injector.get(this.name + 'Directive');
if (directives.length > 1) {
throw new Error('Only support single directive definition for: ' + this.name);
}
var directive = directives[0];
if (directive.replace)
this.notSupported('replace');
if (directive.terminal)
this.notSupported('terminal');
var link = directive.link;
if (typeof link == 'object') {
if (link.post)
this.notSupported('link.post');
}
return directive;
};
UpgradeNg1ComponentAdapterBuilder.prototype.notSupported = function(feature) {
throw new Error("Upgraded directive '" + this.name + "' does not support '" + feature + "'.");
};
UpgradeNg1ComponentAdapterBuilder.prototype.extractBindings = function() {
var scope = this.directive.scope;
if (typeof scope == 'object') {
for (var name in scope) {
if (scope.hasOwnProperty(name)) {
var localName = scope[name];
var type = localName.charAt(0);
localName = localName.substr(1) || name;
var outputName = 'output_' + name;
var outputNameRename = outputName + ': ' + name;
var outputNameRenameChange = outputName + ': ' + name + 'Change';
var inputName = 'input_' + name;
var inputNameRename = inputName + ': ' + name;
switch (type) {
case '=':
this.propertyOutputs.push(outputName);
this.checkProperties.push(localName);
this.outputs.push(outputName);
this.outputsRename.push(outputNameRenameChange);
this.propertyMap[outputName] = localName;
case '@':
this.inputs.push(inputName);
this.inputsRename.push(inputNameRename);
this.propertyMap[inputName] = localName;
break;
case '&':
this.outputs.push(outputName);
this.outputsRename.push(outputNameRename);
this.propertyMap[outputName] = localName;
break;
default:
var json = JSON.stringify(scope);
throw new Error("Unexpected mapping '" + type + "' in '" + json + "' in '" + this.name + "' directive.");
}
}
}
}
};
UpgradeNg1ComponentAdapterBuilder.prototype.compileTemplate = function(compile, templateCache, httpBackend) {
var _this = this;
if (this.directive.template !== undefined) {
this.linkFn = compileHtml(this.directive.template);
} else if (this.directive.templateUrl) {
var url = this.directive.templateUrl;
var html = templateCache.get(url);
if (html !== undefined) {
this.linkFn = compileHtml(html);
} else {
return new Promise(function(resolve, err) {
httpBackend('GET', url, null, function(status, response) {
if (status == 200) {
resolve(_this.linkFn = compileHtml(templateCache.put(url, response)));
} else {
err("GET " + url + " returned " + status + ": " + response);
}
});
});
}
} else {
throw new Error("Directive '" + this.name + "' is not a component, it is missing template.");
}
return null;
function compileHtml(html) {
var div = document.createElement('div');
div.innerHTML = html;
return compile(div.childNodes);
}
};
UpgradeNg1ComponentAdapterBuilder.resolve = function(exportedComponents, injector) {
var promises = [];
var compile = injector.get(constants_1.NG1_COMPILE);
var templateCache = injector.get(constants_1.NG1_TEMPLATE_CACHE);
var httpBackend = injector.get(constants_1.NG1_HTTP_BACKEND);
var $controller = injector.get(constants_1.NG1_CONTROLLER);
for (var name in exportedComponents) {
if (exportedComponents.hasOwnProperty(name)) {
var exportedComponent = exportedComponents[name];
exportedComponent.directive = exportedComponent.extractDirective(injector);
exportedComponent.$controller = $controller;
exportedComponent.extractBindings();
var promise = exportedComponent.compileTemplate(compile, templateCache, httpBackend);
if (promise)
promises.push(promise);
}
}
return Promise.all(promises);
};
return UpgradeNg1ComponentAdapterBuilder;
})();
exports.UpgradeNg1ComponentAdapterBuilder = UpgradeNg1ComponentAdapterBuilder;
var UpgradeNg1ComponentAdapter = (function() {
function UpgradeNg1ComponentAdapter(linkFn, scope, directive, elementRef, $controller, inputs, outputs, propOuts, checkProperties, propertyMap) {
this.directive = directive;
this.inputs = inputs;
this.outputs = outputs;
this.propOuts = propOuts;
this.checkProperties = checkProperties;
this.propertyMap = propertyMap;
this.destinationObj = null;
this.checkLastValues = [];
var element = elementRef.nativeElement;
var childNodes = [];
var childNode;
while (childNode = element.firstChild) {
element.removeChild(childNode);
childNodes.push(childNode);
}
var componentScope = scope.$new(!!directive.scope);
var $element = angular.element(element);
var controllerType = directive.controller;
var controller = null;
if (controllerType) {
var locals = {
$scope: componentScope,
$element: $element
};
controller = $controller(controllerType, locals, null, directive.controllerAs);
$element.data(util_1.controllerKey(directive.name), controller);
}
var link = directive.link;
if (typeof link == 'object')
link = link.pre;
if (link) {
var attrs = NOT_SUPPORTED;
var transcludeFn = NOT_SUPPORTED;
var linkController = this.resolveRequired($element, directive.require);
directive.link(componentScope, $element, attrs, linkController, transcludeFn);
}
this.destinationObj = directive.bindToController && controller ? controller : componentScope;
linkFn(componentScope, function(clonedElement, scope) {
for (var i = 0,
ii = clonedElement.length; i < ii; i++) {
element.appendChild(clonedElement[i]);
}
}, {parentBoundTranscludeFn: function(scope, cloneAttach) {
cloneAttach(childNodes);
}});
for (var i = 0; i < inputs.length; i++) {
this[inputs[i]] = null;
}
for (var j = 0; j < outputs.length; j++) {
var emitter = this[outputs[j]] = new core_1.EventEmitter();
this.setComponentProperty(outputs[j], (function(emitter) {
return function(value) {
return emitter.emit(value);
};
})(emitter));
}
for (var k = 0; k < propOuts.length; k++) {
this[propOuts[k]] = new core_1.EventEmitter();
this.checkLastValues.push(INITIAL_VALUE);
}
}
UpgradeNg1ComponentAdapter.prototype.ngOnChanges = function(changes) {
for (var name in changes) {
if (changes.hasOwnProperty(name)) {
var change = changes[name];
this.setComponentProperty(name, change.currentValue);
}
}
};
UpgradeNg1ComponentAdapter.prototype.ngDoCheck = function() {
var count = 0;
var destinationObj = this.destinationObj;
var lastValues = this.checkLastValues;
var checkProperties = this.checkProperties;
for (var i = 0; i < checkProperties.length; i++) {
var value = destinationObj[checkProperties[i]];
var last = lastValues[i];
if (value !== last) {
if (typeof value == 'number' && isNaN(value) && typeof last == 'number' && isNaN(last)) {} else {
var eventEmitter = this[this.propOuts[i]];
eventEmitter.emit(lastValues[i] = value);
}
}
}
return count;
};
UpgradeNg1ComponentAdapter.prototype.setComponentProperty = function(name, value) {
this.destinationObj[this.propertyMap[name]] = value;
};
UpgradeNg1ComponentAdapter.prototype.resolveRequired = function($element, require) {
if (!require) {
return undefined;
} else if (typeof require == 'string') {
var name = require;
var isOptional = false;
var startParent = false;
var searchParents = false;
var ch;
if (name.charAt(0) == '?') {
isOptional = true;
name = name.substr(1);
}
if (name.charAt(0) == '^') {
searchParents = true;
name = name.substr(1);
}
if (name.charAt(0) == '^') {
startParent = true;
name = name.substr(1);
}
var key = util_1.controllerKey(name);
if (startParent)
$element = $element.parent();
var dep = searchParents ? $element.inheritedData(key) : $element.data(key);
if (!dep && !isOptional) {
throw new Error("Can not locate '" + require + "' in '" + this.directive.name + "'.");
}
return dep;
} else if (require instanceof Array) {
var deps = [];
for (var i = 0; i < require.length; i++) {
deps.push(this.resolveRequired($element, require[i]));
}
return deps;
}
throw new Error("Directive '" + this.directive.name + "' require syntax unrecognized: " + this.directive.require);
};
return UpgradeNg1ComponentAdapter;
})();
global.define = __define;
return module.exports;
});
System.register("angular2/src/upgrade/upgrade_adapter", ["angular2/core", "angular2/src/facade/async", "angular2/platform/browser", "angular2/src/upgrade/metadata", "angular2/src/upgrade/util", "angular2/src/upgrade/constants", "angular2/src/upgrade/downgrade_ng2_adapter", "angular2/src/upgrade/upgrade_ng1_adapter", "angular2/src/upgrade/angular_js"], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
var core_1 = require("angular2/core");
var async_1 = require("angular2/src/facade/async");
var browser_1 = require("angular2/platform/browser");
var metadata_1 = require("angular2/src/upgrade/metadata");
var util_1 = require("angular2/src/upgrade/util");
var constants_1 = require("angular2/src/upgrade/constants");
var downgrade_ng2_adapter_1 = require("angular2/src/upgrade/downgrade_ng2_adapter");
var upgrade_ng1_adapter_1 = require("angular2/src/upgrade/upgrade_ng1_adapter");
var angular = require("angular2/src/upgrade/angular_js");
var upgradeCount = 0;
var UpgradeAdapter = (function() {
function UpgradeAdapter() {
this.idPrefix = "NG2_UPGRADE_" + upgradeCount++ + "_";
this.upgradedComponents = [];
this.downgradedComponents = {};
this.providers = [];
}
UpgradeAdapter.prototype.downgradeNg2Component = function(type) {
this.upgradedComponents.push(type);
var info = metadata_1.getComponentInfo(type);
return ng1ComponentDirective(info, "" + this.idPrefix + info.selector + "_c");
};
UpgradeAdapter.prototype.upgradeNg1Component = function(name) {
if (this.downgradedComponents.hasOwnProperty(name)) {
return this.downgradedComponents[name].type;
} else {
return (this.downgradedComponents[name] = new upgrade_ng1_adapter_1.UpgradeNg1ComponentAdapterBuilder(name)).type;
}
};
UpgradeAdapter.prototype.bootstrap = function(element, modules, config) {
var _this = this;
var upgrade = new UpgradeAdapterRef();
var ng1Injector = null;
var platformRef = core_1.platform(browser_1.BROWSER_PROVIDERS);
var applicationRef = platformRef.application([browser_1.BROWSER_APP_PROVIDERS, core_1.provide(constants_1.NG1_INJECTOR, {useFactory: function() {
return ng1Injector;
}}), core_1.provide(constants_1.NG1_COMPILE, {useFactory: function() {
return ng1Injector.get(constants_1.NG1_COMPILE);
}}), this.providers]);
var injector = applicationRef.injector;
var ngZone = injector.get(core_1.NgZone);
var compiler = injector.get(core_1.Compiler);
var delayApplyExps = [];
var original$applyFn;
var rootScopePrototype;
var rootScope;
var protoViewRefMap = {};
var ng1Module = angular.module(this.idPrefix, modules);
var ng1compilePromise = null;
ng1Module.value(constants_1.NG2_INJECTOR, injector).value(constants_1.NG2_ZONE, ngZone).value(constants_1.NG2_COMPILER, compiler).value(constants_1.NG2_PROTO_VIEW_REF_MAP, protoViewRefMap).value(constants_1.NG2_APP_VIEW_MANAGER, injector.get(core_1.AppViewManager)).config(['$provide', function(provide) {
provide.decorator(constants_1.NG1_ROOT_SCOPE, ['$delegate', function(rootScopeDelegate) {
rootScopePrototype = rootScopeDelegate.constructor.prototype;
if (rootScopePrototype.hasOwnProperty('$apply')) {
original$applyFn = rootScopePrototype.$apply;
rootScopePrototype.$apply = function(exp) {
return delayApplyExps.push(exp);
};
} else {
throw new Error("Failed to find '$apply' on '$rootScope'!");
}
return rootScope = rootScopeDelegate;
}]);
}]).run(['$injector', '$rootScope', function(injector, rootScope) {
ng1Injector = injector;
async_1.ObservableWrapper.subscribe(ngZone.onTurnDone, function(_) {
ngZone.run(function() {
return rootScope.$apply();
});
});
ng1compilePromise = upgrade_ng1_adapter_1.UpgradeNg1ComponentAdapterBuilder.resolve(_this.downgradedComponents, injector);
}]);
angular.element(element).data(util_1.controllerKey(constants_1.NG2_INJECTOR), injector);
ngZone.run(function() {
angular.bootstrap(element, [_this.idPrefix], config);
});
Promise.all([this.compileNg2Components(compiler, protoViewRefMap), ng1compilePromise]).then(function() {
ngZone.run(function() {
if (rootScopePrototype) {
rootScopePrototype.$apply = original$applyFn;
while (delayApplyExps.length) {
rootScope.$apply(delayApplyExps.shift());
}
upgrade._bootstrapDone(applicationRef, ng1Injector);
rootScopePrototype = null;
}
});
}, util_1.onError);
return upgrade;
};
UpgradeAdapter.prototype.addProvider = function(provider) {
this.providers.push(provider);
};
UpgradeAdapter.prototype.upgradeNg1Provider = function(name, options) {
var token = options && options.asToken || name;
this.providers.push(core_1.provide(token, {
useFactory: function(ng1Injector) {
return ng1Injector.get(name);
},
deps: [constants_1.NG1_INJECTOR]
}));
};
UpgradeAdapter.prototype.downgradeNg2Provider = function(token) {
var factory = function(injector) {
return injector.get(token);
};
factory.$inject = [constants_1.NG2_INJECTOR];
return factory;
};
UpgradeAdapter.prototype.compileNg2Components = function(compiler, protoViewRefMap) {
var _this = this;
var promises = [];
var types = this.upgradedComponents;
for (var i = 0; i < types.length; i++) {
promises.push(compiler.compileInHost(types[i]));
}
return Promise.all(promises).then(function(protoViews) {
var types = _this.upgradedComponents;
for (var i = 0; i < protoViews.length; i++) {
protoViewRefMap[metadata_1.getComponentInfo(types[i]).selector] = protoViews[i];
}
return protoViewRefMap;
}, util_1.onError);
};
return UpgradeAdapter;
})();
exports.UpgradeAdapter = UpgradeAdapter;
function ng1ComponentDirective(info, idPrefix) {
directiveFactory.$inject = [constants_1.NG2_PROTO_VIEW_REF_MAP, constants_1.NG2_APP_VIEW_MANAGER, constants_1.NG1_PARSE];
function directiveFactory(protoViewRefMap, viewManager, parse) {
var protoView = protoViewRefMap[info.selector];
if (!protoView)
throw new Error('Expecting ProtoViewRef for: ' + info.selector);
var idCount = 0;
return {
restrict: 'E',
require: constants_1.REQUIRE_INJECTOR,
link: {post: function(scope, element, attrs, parentInjector, transclude) {
var domElement = element[0];
var facade = new downgrade_ng2_adapter_1.DowngradeNg2ComponentAdapter(idPrefix + (idCount++), info, element, attrs, scope, parentInjector, parse, viewManager, protoView);
facade.setupInputs();
facade.bootstrapNg2();
facade.projectContent();
facade.setupOutputs();
facade.registerCleanup();
}}
};
}
return directiveFactory;
}
var UpgradeAdapterRef = (function() {
function UpgradeAdapterRef() {
this._readyFn = null;
this.ng1RootScope = null;
this.ng1Injector = null;
this.ng2ApplicationRef = null;
this.ng2Injector = null;
}
UpgradeAdapterRef.prototype._bootstrapDone = function(applicationRef, ng1Injector) {
this.ng2ApplicationRef = applicationRef;
this.ng2Injector = applicationRef.injector;
this.ng1Injector = ng1Injector;
this.ng1RootScope = ng1Injector.get(constants_1.NG1_ROOT_SCOPE);
this._readyFn && this._readyFn(this);
};
UpgradeAdapterRef.prototype.ready = function(fn) {
this._readyFn = fn;
};
UpgradeAdapterRef.prototype.dispose = function() {
this.ng1Injector.get(constants_1.NG1_ROOT_SCOPE).$destroy();
this.ng2ApplicationRef.dispose();
};
return UpgradeAdapterRef;
})();
exports.UpgradeAdapterRef = UpgradeAdapterRef;
global.define = __define;
return module.exports;
});
System.register("angular2/upgrade", ["angular2/src/upgrade/upgrade_adapter"], true, function(require, exports, module) {
var global = System.global,
__define = global.define;
global.define = undefined;
var upgrade_adapter_1 = require("angular2/src/upgrade/upgrade_adapter");
exports.UpgradeAdapter = upgrade_adapter_1.UpgradeAdapter;
exports.UpgradeAdapterRef = upgrade_adapter_1.UpgradeAdapterRef;
global.define = __define;
return module.exports;
});
//# sourceMappingURLDisabled=upgrade.js.map

File diff suppressed because one or more lines are too long

View File

@ -1,4 +0,0 @@
export * from './src/common/pipes';
export * from './src/common/directives';
export * from './src/common/forms';
export * from './src/common/common_directives';

View File

@ -1,8 +0,0 @@
/**
* @module
* @description
* Starting point to import all compiler APIs.
*/
export * from './src/compiler/url_resolver';
export * from './src/compiler/xhr';
export * from './src/compiler/compiler';

Some files were not shown because too many files have changed in this diff Show More