Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dceda6e27d | ||
|
|
ef264920db | ||
|
|
e9b1c92911 | ||
|
|
123f90d24d | ||
|
|
ff01764f95 | ||
|
|
4132dd940a | ||
|
|
9fc6d3d6f0 | ||
|
|
7d9141a055 | ||
|
|
2214c0cdf8 | ||
|
|
11c5fdc328 | ||
|
|
c81c4f184b | ||
|
|
a35ed05bc3 | ||
|
|
e574314472 | ||
|
|
45ab6d7c5e | ||
|
|
48325b6452 | ||
|
|
4a1c9ac390 | ||
|
|
a868bab24e | ||
|
|
1fada5dd0b | ||
|
|
a55b9bb8e1 | ||
|
|
6c0f3d701f | ||
|
|
e86f4f94ef | ||
|
|
ca258deb7a | ||
|
|
79ca016409 | ||
|
|
869bebed6e | ||
|
|
0e3c0fcc2e | ||
|
|
bf9c0bb914 | ||
|
|
8669599089 | ||
|
|
775c0ff2f1 | ||
|
|
98ab75029e | ||
|
|
682740a020 | ||
|
|
278decfb87 | ||
|
|
b8a0ca082a | ||
|
|
f03e4be683 | ||
|
|
ba6a6292f9 | ||
|
|
16599a07a9 | ||
|
|
a9ac11216d | ||
|
|
8e2008f821 | ||
|
|
0a8b9bad4f | ||
|
|
bd4b75f5d8 | ||
|
|
084e7e7d73 | ||
|
|
76f4e3c5b4 | ||
|
|
adb97a0f3e | ||
|
|
f2f435b4cb |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,5 +2,5 @@ node_modules
|
||||
.aws-config.json
|
||||
dist
|
||||
web.config
|
||||
config.dev.js
|
||||
config.js
|
||||
*.sublime-workspace
|
||||
@@ -13,7 +13,7 @@ A beautiful, easy to use and feature rich Graphite dashboard replacement and gra
|
||||
- Templating
|
||||
- Integrated function documentation (TODO)
|
||||
- Click & drag functions to rearrange order (TODO)
|
||||
- Much more...
|
||||
- Native Graphite PNG render support
|
||||
|
||||
## Graphing
|
||||
- Fast rendering, even over large timespans.
|
||||
@@ -33,7 +33,7 @@ A beautiful, easy to use and feature rich Graphite dashboard replacement and gra
|
||||
- Import & export dashboard (json file)
|
||||
- Import dashboard from Graphite
|
||||
- Templating
|
||||
- Much more...
|
||||
- [Scripted dashboards](https://github.com/torkelo/grafana/wiki/Scripted-dashboards) (generate from js script and url parameters)
|
||||
|
||||
# Requirements
|
||||
Grafana is very easy to install. It is a client side web app with no backend. Any webserver will do. Optionally you will need ElasticSearch if you want to be able to save and load dashboards quickly instead of json files or local storage.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "1.0.4",
|
||||
"version": "1.2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/torkelo/grafana.git"
|
||||
|
||||
@@ -12,7 +12,7 @@ define([
|
||||
'angular-strap',
|
||||
'angular-dragdrop',
|
||||
'extend-jquery',
|
||||
'bindonce',
|
||||
'bindonce'
|
||||
],
|
||||
function (angular, $, _, appLevelRequire) {
|
||||
|
||||
@@ -27,7 +27,7 @@ function (angular, $, _, appLevelRequire) {
|
||||
register_fns = {};
|
||||
|
||||
// This stores the Kibana revision number, @REV@ is replaced by grunt.
|
||||
app.constant('kbnVersion',"@REV@");
|
||||
app.constant('grafanaVersion',"@grafanaVersion@");
|
||||
|
||||
// Use this for cache busting partials
|
||||
app.constant('cacheBust',"cache-bust="+Date.now());
|
||||
|
||||
@@ -5,7 +5,7 @@ require.config({
|
||||
baseUrl: 'app',
|
||||
// urlArgs: 'r=@REV@',
|
||||
paths: {
|
||||
config: '../config',
|
||||
config: ['../config', '../config.sample'],
|
||||
settings: 'components/settings',
|
||||
kbn: 'components/kbn',
|
||||
|
||||
@@ -22,6 +22,7 @@ require.config({
|
||||
datepicker: '../vendor/angular/datepicker',
|
||||
bindonce: '../vendor/angular/bindonce',
|
||||
crypto: '../vendor/crypto.min',
|
||||
spectrum: '../vendor/spectrum',
|
||||
|
||||
underscore: 'components/underscore.extended',
|
||||
'underscore-src': '../vendor/underscore',
|
||||
@@ -44,12 +45,18 @@ require.config({
|
||||
|
||||
modernizr: '../vendor/modernizr-2.6.1',
|
||||
elasticjs: '../vendor/elasticjs/elastic-angular-client',
|
||||
|
||||
'bootstrap-tagsinput': '../vendor/tagsinput/bootstrap-tagsinput',
|
||||
},
|
||||
shim: {
|
||||
underscore: {
|
||||
exports: '_'
|
||||
},
|
||||
|
||||
spectrum: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
|
||||
crypto: {
|
||||
exports: 'Crypto'
|
||||
},
|
||||
@@ -99,6 +106,7 @@ require.config({
|
||||
|
||||
elasticjs: ['angular', '../vendor/elasticjs/elastic'],
|
||||
|
||||
'bootstrap-tagsinput': ['jquery'],
|
||||
},
|
||||
waitSeconds: 60,
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ define([
|
||||
'./dash',
|
||||
'./dashLoader',
|
||||
'./row',
|
||||
'./submenuCtrl',
|
||||
'./pulldown',
|
||||
'./search',
|
||||
'./metricKeys',
|
||||
|
||||
@@ -30,7 +30,7 @@ function (angular, $, config, _) {
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('DashCtrl', function(
|
||||
$scope, $rootScope, $route, ejsResource, dashboard, alertSrv, panelMove, keyboardManager) {
|
||||
$scope, $rootScope, $route, ejsResource, dashboard, alertSrv, panelMove, keyboardManager, grafanaVersion) {
|
||||
|
||||
$scope.requiredElasticSearchVersion = ">=0.90.3";
|
||||
|
||||
@@ -38,6 +38,8 @@ function (angular, $, config, _) {
|
||||
index: 0
|
||||
};
|
||||
|
||||
$scope.grafanaVersion = grafanaVersion[0] === '@' ? 'version: master' : grafanaVersion;
|
||||
|
||||
// For moving stuff around the dashboard.
|
||||
$scope.panelMoveDrop = panelMove.onDrop;
|
||||
$scope.panelMoveStart = panelMove.onStart;
|
||||
|
||||
@@ -103,16 +103,6 @@ function (angular, _, moment) {
|
||||
);
|
||||
};
|
||||
|
||||
$scope.elasticsearch_dblist = function(query) {
|
||||
dashboard.elasticsearch_list(query,$scope.loader.load_elasticsearch_size).then(
|
||||
function(result) {
|
||||
if(!_.isUndefined(result.hits)) {
|
||||
$scope.hits = result.hits.total;
|
||||
$scope.elasticsearch.dashboards = result.hits.hits;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.save_gist = function() {
|
||||
dashboard.save_gist($scope.gist.title).then(
|
||||
function(link) {
|
||||
|
||||
@@ -12,10 +12,10 @@ function (angular, _, config, $) {
|
||||
module.controller('SearchCtrl', function($scope, $rootScope, dashboard, $element, $location) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.elasticsearch = $scope.elasticsearch || {};
|
||||
$scope.giveSearchFocus = 0;
|
||||
$scope.selectedIndex = -1;
|
||||
|
||||
$scope.results = {dashboards: [], tags: [], metrics: []};
|
||||
$scope.query = { query: 'title:' };
|
||||
$rootScope.$on('open-search', $scope.openSearch);
|
||||
};
|
||||
|
||||
@@ -30,7 +30,15 @@ function (angular, _, config, $) {
|
||||
$scope.selectedIndex--;
|
||||
}
|
||||
if (evt.keyCode === 13) {
|
||||
var selectedDash = $scope.search_results.dashboards[$scope.selectedIndex];
|
||||
if ($scope.tagsOnly) {
|
||||
var tag = $scope.results.tags[$scope.selectedIndex];
|
||||
if (tag) {
|
||||
$scope.filterByTag(tag.term);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedDash = $scope.results.dashboards[$scope.selectedIndex];
|
||||
if (selectedDash) {
|
||||
$location.path("/dashboard/elasticsearch/" + encodeURIComponent(selectedDash._id));
|
||||
setTimeout(function(){
|
||||
@@ -40,25 +48,69 @@ function (angular, _, config, $) {
|
||||
}
|
||||
};
|
||||
|
||||
$scope.elasticsearch_dashboards = function(queryStr) {
|
||||
dashboard.elasticsearch_list(queryStr + '*', 50).then(function(results) {
|
||||
if(_.isUndefined(results.hits)) {
|
||||
$scope.search_results = { dashboards: [] };
|
||||
return;
|
||||
$scope.searchDasboards = function(query) {
|
||||
var request = $scope.ejs.Request().indices(config.grafana_index).types('dashboard');
|
||||
var tagsOnly = query.indexOf('tags!:') === 0;
|
||||
if (tagsOnly) {
|
||||
var tagsQuery = query.substring(6, query.length);
|
||||
query = 'tags:' + tagsQuery + '*';
|
||||
}
|
||||
else {
|
||||
if (query.length === 0) {
|
||||
query = 'title:';
|
||||
}
|
||||
|
||||
$scope.search_results = { dashboards: results.hits.hits };
|
||||
});
|
||||
if (query[query.length - 1] !== '*') {
|
||||
query += '*';
|
||||
}
|
||||
}
|
||||
|
||||
return request
|
||||
.query($scope.ejs.QueryStringQuery(query))
|
||||
.sort('_uid')
|
||||
.facet($scope.ejs.TermsFacet("tags").field("tags").order('term').size(50))
|
||||
.size(50).doSearch()
|
||||
.then(function(results) {
|
||||
|
||||
if(_.isUndefined(results.hits)) {
|
||||
$scope.results.dashboards = [];
|
||||
$scope.results.tags = [];
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.tagsOnly = tagsOnly;
|
||||
$scope.results.dashboards = results.hits.hits;
|
||||
$scope.results.tags = results.facets.tags.terms;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.elasticsearch_dblist = function(queryStr) {
|
||||
$scope.filterByTag = function(tag, evt) {
|
||||
$scope.query.query = "tags:" + tag + " AND title:";
|
||||
$scope.search();
|
||||
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
|
||||
if (evt) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.showTags = function(evt) {
|
||||
evt.stopPropagation();
|
||||
$scope.tagsOnly = !$scope.tagsOnly;
|
||||
$scope.query.query = $scope.tagsOnly ? "tags!:" : "";
|
||||
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
|
||||
$scope.selectedIndex = -1;
|
||||
};
|
||||
|
||||
$scope.search = function() {
|
||||
$scope.showImport = false;
|
||||
$scope.selectedIndex = -1;
|
||||
|
||||
queryStr = queryStr.toLowerCase();
|
||||
var queryStr = $scope.query.query.toLowerCase();
|
||||
|
||||
if (queryStr.indexOf('m:') !== 0) {
|
||||
$scope.elasticsearch_dashboards(queryStr);
|
||||
queryStr = queryStr.replace(' and ', ' AND ');
|
||||
$scope.searchDasboards(queryStr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -81,10 +133,10 @@ function (angular, _, config, $) {
|
||||
|
||||
results.then(function(results) {
|
||||
if (results && results.hits && results.hits.hits.length > 0) {
|
||||
$scope.search_results = { metrics: results.hits.hits };
|
||||
$scope.results.metrics = { metrics: results.hits.hits };
|
||||
}
|
||||
else {
|
||||
$scope.search_results = { metric: [] };
|
||||
$scope.results.metrics = { metric: [] };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -95,7 +147,8 @@ function (angular, _, config, $) {
|
||||
}
|
||||
|
||||
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
|
||||
$scope.elasticsearch_dblist("");
|
||||
$scope.query.query = 'title:';
|
||||
$scope.search();
|
||||
};
|
||||
|
||||
$scope.addMetricToCurrentDashboard = function (metricId) {
|
||||
@@ -125,8 +178,6 @@ function (angular, _, config, $) {
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
module.directive('xngFocus', function() {
|
||||
return function(scope, element, attrs) {
|
||||
$(element).click(function(e) {
|
||||
|
||||
30
src/app/controllers/submenuCtrl.js
Normal file
30
src/app/controllers/submenuCtrl.js
Normal file
@@ -0,0 +1,30 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.controllers');
|
||||
|
||||
module.controller('SubmenuCtrl', function($scope) {
|
||||
var _d = {
|
||||
collapse: false,
|
||||
notice: false,
|
||||
enable: true
|
||||
};
|
||||
|
||||
_.defaults($scope.pulldown,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.panel = $scope.pulldown;
|
||||
$scope.row = $scope.pulldown;
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
@@ -42,7 +42,6 @@
|
||||
"span": 12,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"loadingEditor": false,
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
|
||||
74
src/app/dashboards/scripted.js
Normal file
74
src/app/dashboards/scripted.js
Normal file
@@ -0,0 +1,74 @@
|
||||
/* global _ */
|
||||
|
||||
/*
|
||||
* Complex scripted dashboard
|
||||
* This script generates a dashboard object that Grafana can load. It also takes a number of user
|
||||
* supplied URL parameters (int ARGS variable)
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Setup some variables
|
||||
var dashboard, _d_timespan;
|
||||
|
||||
// All url parameters are available via the ARGS object
|
||||
var ARGS;
|
||||
|
||||
// Set a default timespan if one isn't specified
|
||||
_d_timespan = '1d';
|
||||
|
||||
// Intialize a skeleton with nothing but a rows array and service object
|
||||
dashboard = {
|
||||
rows : [],
|
||||
services : {}
|
||||
};
|
||||
|
||||
// Set a title
|
||||
dashboard.title = 'Scripted dash';
|
||||
dashboard.services.filter = {
|
||||
time: {
|
||||
from: "now-"+(ARGS.from || _d_timespan),
|
||||
to: "now"
|
||||
}
|
||||
};
|
||||
|
||||
var rows = 1;
|
||||
var seriesName = 'argName';
|
||||
|
||||
if(!_.isUndefined(ARGS.rows)) {
|
||||
rows = parseInt(ARGS.rows, 10);
|
||||
}
|
||||
|
||||
if(!_.isUndefined(ARGS.name)) {
|
||||
seriesName = ARGS.name;
|
||||
}
|
||||
|
||||
for (var i = 0; i < rows; i++) {
|
||||
|
||||
dashboard.rows.push({
|
||||
title: 'Chart',
|
||||
height: '300px',
|
||||
panels: [
|
||||
{
|
||||
title: 'Events',
|
||||
type: 'graphite',
|
||||
span: 12,
|
||||
fill: 1,
|
||||
linewidth: 2,
|
||||
targets: [
|
||||
{
|
||||
'target': "randomWalk('" + seriesName + "')"
|
||||
},
|
||||
{
|
||||
'target': "randomWalk('random walk2')"
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Now return the object and we're good!
|
||||
return dashboard;
|
||||
@@ -32,4 +32,5 @@ function (angular, app, _) {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -9,5 +9,7 @@ define([
|
||||
'./tip',
|
||||
'./confirmClick',
|
||||
'./configModal',
|
||||
'./grafanaGraph'
|
||||
'./spectrumPicker',
|
||||
'./grafanaGraph',
|
||||
'./bootstrap-tagsinput'
|
||||
], function () {});
|
||||
85
src/app/directives/bootstrap-tagsinput.js
vendored
Normal file
85
src/app/directives/bootstrap-tagsinput.js
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'bootstrap-tagsinput'
|
||||
],
|
||||
function (angular, $) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('bootstrapTagsinput', function() {
|
||||
|
||||
function getItemProperty(scope, property) {
|
||||
if (!property) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (angular.isFunction(scope.$parent[property])) {
|
||||
return scope.$parent[property];
|
||||
}
|
||||
|
||||
return function(item) {
|
||||
return item[property];
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'EA',
|
||||
scope: {
|
||||
model: '=ngModel'
|
||||
},
|
||||
template: '<select multiple></select>',
|
||||
replace: false,
|
||||
link: function(scope, element, attrs) {
|
||||
|
||||
if (!angular.isArray(scope.model)) {
|
||||
scope.model = [];
|
||||
}
|
||||
|
||||
var select = $('select', element);
|
||||
|
||||
if (attrs.placeholder) {
|
||||
select.attr('placeholder', attrs.placeholder);
|
||||
}
|
||||
|
||||
select.tagsinput({
|
||||
typeahead : {
|
||||
source : angular.isFunction(scope.$parent[attrs.typeaheadSource]) ? scope.$parent[attrs.typeaheadSource] : null
|
||||
},
|
||||
itemValue: getItemProperty(scope, attrs.itemvalue),
|
||||
itemText : getItemProperty(scope, attrs.itemtext),
|
||||
tagClass : angular.isFunction(scope.$parent[attrs.tagclass]) ?
|
||||
scope.$parent[attrs.tagclass] : function() { return attrs.tagclass; }
|
||||
});
|
||||
|
||||
select.on('itemAdded', function(event) {
|
||||
if (scope.model.indexOf(event.item) === -1) {
|
||||
scope.model.push(event.item);
|
||||
}
|
||||
});
|
||||
|
||||
select.on('itemRemoved', function(event) {
|
||||
var idx = scope.model.indexOf(event.item);
|
||||
if (idx !== -1) {
|
||||
scope.model.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch("model", function() {
|
||||
if (!angular.isArray(scope.model)) {
|
||||
scope.model = [];
|
||||
}
|
||||
|
||||
select.tagsinput('removeAll');
|
||||
|
||||
for (var i = 0; i < scope.model.length; i++) {
|
||||
select.tagsinput('add', scope.model[i]);
|
||||
}
|
||||
|
||||
}, true);
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -64,6 +64,8 @@ function (angular, $, kbn, moment, _) {
|
||||
return;
|
||||
}
|
||||
|
||||
var panel = scope.panel;
|
||||
|
||||
_.each(_.keys(scope.hiddenSeries), function(seriesAlias) {
|
||||
var dataSeries = _.find(data, function(series) {
|
||||
return series.info.alias === seriesAlias;
|
||||
@@ -77,40 +79,40 @@ function (angular, $, kbn, moment, _) {
|
||||
// Set barwidth based on specified interval
|
||||
var barwidth = kbn.interval_to_ms(scope.interval);
|
||||
|
||||
var stack = scope.panel.stack ? true : null;
|
||||
var stack = panel.stack ? true : null;
|
||||
|
||||
// Populate element
|
||||
var options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
stackpercent: scope.panel.stack ? scope.panel.percentage : false,
|
||||
stack: scope.panel.percentage ? null : stack,
|
||||
stackpercent: panel.stack ? panel.percentage : false,
|
||||
stack: panel.percentage ? null : stack,
|
||||
lines: {
|
||||
show: scope.panel.lines,
|
||||
// Silly, but fixes bug in stacked percentages
|
||||
fill: scope.panel.fill === 0 ? 0.001 : scope.panel.fill/10,
|
||||
lineWidth: scope.panel.linewidth,
|
||||
steps: scope.panel.steppedLine
|
||||
show: panel.lines,
|
||||
zero: false,
|
||||
fill: panel.fill === 0 ? 0.001 : panel.fill/10,
|
||||
lineWidth: panel.linewidth,
|
||||
steps: panel.steppedLine
|
||||
},
|
||||
bars: {
|
||||
show: scope.panel.bars,
|
||||
show: panel.bars,
|
||||
fill: 1,
|
||||
barWidth: barwidth/1.5,
|
||||
zero: false,
|
||||
lineWidth: 0
|
||||
},
|
||||
points: {
|
||||
show: scope.panel.points,
|
||||
show: panel.points,
|
||||
fill: 1,
|
||||
fillColor: false,
|
||||
radius: scope.panel.pointradius
|
||||
radius: panel.pointradius
|
||||
},
|
||||
shadowSize: 1
|
||||
},
|
||||
yaxes: [],
|
||||
xaxis: {
|
||||
timezone: dashboard.current.timezone,
|
||||
show: scope.panel['x-axis'],
|
||||
show: panel['x-axis'],
|
||||
mode: "time",
|
||||
min: _.isUndefined(scope.range.from) ? null : scope.range.from.getTime(),
|
||||
max: _.isUndefined(scope.range.to) ? null : scope.range.to.getTime(),
|
||||
@@ -130,10 +132,32 @@ function (angular, $, kbn, moment, _) {
|
||||
}
|
||||
};
|
||||
|
||||
if (panel.grid.threshold1) {
|
||||
var limit1 = panel.grid.thresholdLine ? panel.grid.threshold1 : (panel.grid.threshold2 || null);
|
||||
options.grid.markings = [];
|
||||
options.grid.markings.push({
|
||||
yaxis: { from: panel.grid.threshold1, to: limit1 },
|
||||
color: panel.grid.threshold1Color
|
||||
});
|
||||
|
||||
if (panel.grid.threshold2) {
|
||||
var limit2;
|
||||
if (panel.grid.thresholdLine) {
|
||||
limit2 = panel.grid.threshold2;
|
||||
} else {
|
||||
limit2 = panel.grid.threshold1 > panel.grid.threshold2 ? -Infinity : +Infinity;
|
||||
}
|
||||
options.grid.markings.push({
|
||||
yaxis: { from: panel.grid.threshold2, to: limit2 },
|
||||
color: panel.grid.threshold2Color
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addAnnotations(options);
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var _d = data[i].getFlotPairs(scope.panel.nullPointMode);
|
||||
var _d = data[i].getFlotPairs(panel.nullPointMode);
|
||||
data[i].data = _d;
|
||||
}
|
||||
|
||||
@@ -186,25 +210,26 @@ function (angular, $, kbn, moment, _) {
|
||||
elem.html('<img src="' + url + '"></img>');
|
||||
}
|
||||
|
||||
|
||||
function addAnnotations(options) {
|
||||
if(scope.panel.annotate.enable) {
|
||||
options.events = {
|
||||
levels: 1,
|
||||
data: scope.annotations,
|
||||
types: {
|
||||
'annotation': {
|
||||
level: 1,
|
||||
icon: {
|
||||
icon: "icon-tag icon-flip-vertical",
|
||||
size: 20,
|
||||
color: "#222",
|
||||
outline: "#bbb"
|
||||
}
|
||||
if(!data.annotations || data.annotations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.events = {
|
||||
levels: 1,
|
||||
data: data.annotations,
|
||||
types: {
|
||||
'annotation': {
|
||||
level: 1,
|
||||
icon: {
|
||||
icon: "icon-tag icon-flip-vertical",
|
||||
size: 20,
|
||||
color: "#222",
|
||||
outline: "#bbb"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function addAxisLabels() {
|
||||
@@ -246,7 +271,7 @@ function (angular, $, kbn, moment, _) {
|
||||
}
|
||||
if (format === 'short') {
|
||||
axis.tickFormatter = function(val) {
|
||||
return kbn.shortFormat(val,0);
|
||||
return kbn.shortFormat(val, 1);
|
||||
};
|
||||
}
|
||||
if (format === 'ms') {
|
||||
@@ -320,4 +345,4 @@ function (angular, $, kbn, moment, _) {
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
38
src/app/directives/spectrumPicker.js
Normal file
38
src/app/directives/spectrumPicker.js
Normal file
@@ -0,0 +1,38 @@
|
||||
define([
|
||||
'angular',
|
||||
'spectrum'
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('spectrumPicker', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
require: 'ngModel',
|
||||
scope: false,
|
||||
replace: true,
|
||||
template: "<span><input class='input-small' /></span>",
|
||||
link: function(scope, element, attrs, ngModel) {
|
||||
var input = element.find('input');
|
||||
var options = angular.extend({
|
||||
showAlpha: true,
|
||||
showButtons: false,
|
||||
color: ngModel.$viewValue,
|
||||
change: function(color) {
|
||||
scope.$apply(function() {
|
||||
ngModel.$setViewValue(color.toRgbString());
|
||||
});
|
||||
}
|
||||
}, scope.$eval(attrs.options));
|
||||
|
||||
ngModel.$render = function() {
|
||||
input.spectrum('set', ngModel.$viewValue || '');
|
||||
};
|
||||
|
||||
input.spectrum(options);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
13
src/app/panels/annotations/module.html
Normal file
13
src/app/panels/annotations/module.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<div ng-controller='AnnotationsCtrl' ng-init="init()">
|
||||
|
||||
<!-- <div class="submenu-toggle" ng-class="{'annotation-disabled': panel.hideAll }">
|
||||
<a ng-click="hideAll();" class="small">Hide All</a>
|
||||
<i class="icon-ok"></i>
|
||||
</div>
|
||||
-->
|
||||
<div class="submenu-toggle" ng-repeat="annotation in annotationList" ng-class="{'annotation-disabled': !annotation.enabled }">
|
||||
<a ng-click="hide(annotation)" class="small">{{annotation.name}}</a>
|
||||
<i class="icon-ok"></i>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
51
src/app/panels/annotations/module.js
Normal file
51
src/app/panels/annotations/module.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
|
||||
## annotations
|
||||
|
||||
*/
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.panels.annotations', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('AnnotationsCtrl', function($scope, dashboard, annotationsSrv, $rootScope) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
status : "Stable",
|
||||
description : "Annotations"
|
||||
};
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
};
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.annotationList = annotationsSrv.annotationList;
|
||||
};
|
||||
|
||||
$scope.hideAll = function () {
|
||||
$scope.panel.hideAll = !$scope.panel.hideAll;
|
||||
|
||||
_.each($scope.annotationList, function(annotation) {
|
||||
annotation.enabled = !$scope.panel.hideAll;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.hide = function (annotation) {
|
||||
annotation.enabled = !annotation.enabled;
|
||||
$scope.panel.hideAll = !annotation.enabled;
|
||||
|
||||
$rootScope.$broadcast('refresh');
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,6 @@
|
||||
}
|
||||
.filtering-container label {
|
||||
float: left;
|
||||
font-size: inherit;
|
||||
}
|
||||
.filtering-container input[type=checkbox] {
|
||||
margin: 0;
|
||||
@@ -13,11 +12,14 @@
|
||||
.filter-panel-filter {
|
||||
display:inline-block;
|
||||
vertical-align: top;
|
||||
padding: 3px 10px 0px 10px;
|
||||
padding: 4px 10px 3px 10px;
|
||||
border-right: 1px solid #202020;
|
||||
}
|
||||
.filter-panel-filter:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
.filter-panel-filter ul {
|
||||
margin-bottom: 3px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.filter-deselected {
|
||||
@@ -25,12 +27,13 @@
|
||||
}
|
||||
.filter-action {
|
||||
float:right;
|
||||
padding-right: 2px;
|
||||
margin-bottom: 0px !important;
|
||||
margin-left: 3px;
|
||||
margin-left: 0px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.add-filter-action {
|
||||
padding: 3px 10px 0px 10px;
|
||||
padding: 3px 10px 0px 5px;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
@@ -78,7 +81,7 @@
|
||||
<input type='text' ng-model="filter.query">
|
||||
</li>
|
||||
<li>
|
||||
<label for="includeAll">Include all</label>:
|
||||
<label for="includeAll">Include all:</label>
|
||||
<input id="includeAll" type='checkbox' ng-model="filter.includeAll">
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -41,8 +41,19 @@ function (angular, app, _) {
|
||||
filter.options = _.map(results, function(node) {
|
||||
return { text: node.text, value: node.text };
|
||||
});
|
||||
|
||||
if (filter.includeAll) {
|
||||
filter.options.unshift({text: 'All', value: '*'});
|
||||
if(endsWithWildcard(filter.query)) {
|
||||
filter.options.unshift({text: 'All', value: '*'});
|
||||
}
|
||||
else {
|
||||
var allExpr = '{';
|
||||
_.each(filter.options, function(option) {
|
||||
allExpr += option.text + ',';
|
||||
});
|
||||
allExpr = allExpr.substring(0, allExpr.length - 1) + '}';
|
||||
filter.options.unshift({text: 'All', value: allExpr});
|
||||
}
|
||||
}
|
||||
|
||||
filterSrv.filterOptionSelected(filter, filter.options[0]);
|
||||
@@ -66,5 +77,13 @@ function (angular, app, _) {
|
||||
$rootScope.$broadcast('render');
|
||||
};
|
||||
|
||||
function endsWithWildcard(query) {
|
||||
if (query.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return query[query.length - 1] === '*';
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
@@ -10,11 +10,11 @@
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Left Y Format <tip>Y-axis formatting</tip></label>
|
||||
<select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'ms']" ng-change="render()"></select>
|
||||
<select class="input-small" ng-model="panel.y_formats[0]" ng-options="f for f in ['none','short','bytes', 'ms']" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Right Y Format <tip>Y-axis formatting</tip></label>
|
||||
<select class="input-small" ng-model="panel.y_formats[2]" ng-options="f for f in ['none','short','bytes', 'ms']" ng-change="render()"></select>
|
||||
<select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'ms']" ng-change="render()"></select>
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
@@ -45,6 +45,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Grid thresholds</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Level1</label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.threshold1" ng-change="render()" ng-model-onblur />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Color</label>
|
||||
<spectrum-picker ng-model="panel.grid.threshold1Color" ng-change="render()" ></spectrum-picker>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Level2</label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.threshold2" ng-change="render()" ng-model-onblur />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Color</label>
|
||||
<spectrum-picker ng-model="panel.grid.threshold2Color" ng-change="render()" ></spectrum-picker>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Line mode</label><input type="checkbox" ng-model="panel.grid.thresholdLine" ng-checked="panel.grid.thresholdLine" ng-change="render();">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Legend</h5>
|
||||
<div class="editor-option">
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<div class="grafana-target-inner-wrapper">
|
||||
<div class="grafana-target-inner">
|
||||
<ul class="grafana-target-controls">
|
||||
<li ng-if="target.yaxis">
|
||||
<a class="pointer" ng-click="setYAxis()">
|
||||
y²
|
||||
<li ng-show="parserError">
|
||||
<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
|
||||
<i class="icon-warning-sign"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -42,24 +42,20 @@
|
||||
</ul>
|
||||
|
||||
<ul class="grafana-target-controls-left">
|
||||
<li ng-hide="parserError">
|
||||
<li>
|
||||
<a class="grafana-target-segment"
|
||||
ng-click="target.hide = !target.hide; get_data();"
|
||||
role="menuitem">
|
||||
<i class="icon-eye-open"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-show="parserError">
|
||||
<a class="grafana-target-segment" bs-tooltip="parserError" style="color: rgb(229, 189, 28)" ng-click="hideit()" role="menuitem">
|
||||
<i class="icon-warning-sign"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<input type="text"
|
||||
class="grafana-target-text-input"
|
||||
ng-model="target.target"
|
||||
focus-me="showTextEditor"
|
||||
spellcheck='false'
|
||||
ng-model-onblur ng-change="targetTextChanged()"
|
||||
ng-show="showTextEditor" />
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<div ng-switch-when="int">
|
||||
<input
|
||||
type="number"
|
||||
placeholder="seconds"
|
||||
step="any"
|
||||
focus-me="true"
|
||||
class="input-mini"
|
||||
ng-change="functionParamsChanged(func)" ng-model-onblur
|
||||
|
||||
@@ -34,7 +34,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
var module = angular.module('kibana.panels.graphite', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('graphite', function($scope, $rootScope, filterSrv, graphiteSrv, $timeout) {
|
||||
module.controller('graphite', function($scope, $rootScope, filterSrv, graphiteSrv, $timeout, annotationsSrv) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
modals : [],
|
||||
@@ -109,7 +109,11 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
*/
|
||||
grid : {
|
||||
max: null,
|
||||
min: 0
|
||||
min: 0,
|
||||
threshold1: null,
|
||||
threshold2: null,
|
||||
threshold1Color: 'rgba(216, 200, 27, 0.27)',
|
||||
threshold2Color: 'rgba(234, 112, 112, 0.22)'
|
||||
},
|
||||
|
||||
annotate : {
|
||||
@@ -221,7 +225,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.panel.tooltip.query_as_alias = true;
|
||||
|
||||
$scope.get_data();
|
||||
|
||||
};
|
||||
|
||||
$scope.remove_panel_from_row = function(row, panel) {
|
||||
@@ -240,6 +243,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
$scope.updateTimeRange = function () {
|
||||
$scope.range = filterSrv.timeRange();
|
||||
$scope.rangeUnparsed = filterSrv.timeRange(false);
|
||||
|
||||
$scope.interval = '10m';
|
||||
|
||||
if ($scope.range) {
|
||||
@@ -276,12 +281,14 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.updateTimeRange();
|
||||
|
||||
var graphiteQuery = {
|
||||
range: filterSrv.timeRange(false),
|
||||
range: $scope.rangeUnparsed,
|
||||
targets: $scope.panel.targets,
|
||||
renderer: $scope.panel.renderer,
|
||||
format: $scope.panel.renderer === 'png' ? 'png' : 'json',
|
||||
maxDataPoints: $scope.panel.span * 50
|
||||
};
|
||||
|
||||
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.rangeUnparsed);
|
||||
|
||||
return graphiteSrv.query(graphiteQuery)
|
||||
.then($scope.receiveGraphiteData)
|
||||
.then(null, function(err) {
|
||||
@@ -324,7 +331,13 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
data.push(series);
|
||||
});
|
||||
|
||||
$scope.render(data);
|
||||
$scope.annotationsPromise
|
||||
.then(function(annotations) {
|
||||
data.annotations = annotations;
|
||||
$scope.render(data);
|
||||
}, function() {
|
||||
$scope.render(data);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.add_target = function() {
|
||||
|
||||
@@ -10,13 +10,14 @@ function (_) {
|
||||
this.datapoints = opts.datapoints;
|
||||
this.info = opts.info;
|
||||
this.label = opts.info.alias;
|
||||
this.color = opts.info.color;
|
||||
this.yaxis = opts.info.yaxis;
|
||||
};
|
||||
|
||||
ts.ZeroFilled.prototype.getFlotPairs = function (fillStyle) {
|
||||
var result = [];
|
||||
|
||||
this.color = this.info.color;
|
||||
this.yaxis = this.info.yaxis;
|
||||
|
||||
this.info.total = 0;
|
||||
this.info.max = null;
|
||||
this.info.min = 212312321312;
|
||||
|
||||
@@ -77,8 +77,8 @@ function (angular, app, _, moment, kbn) {
|
||||
$scope.temptime = cloneTime($scope.time);
|
||||
|
||||
// Date picker needs the date to be at the start of the day
|
||||
$scope.temptime.from.date.setHours(0,0,0,0);
|
||||
$scope.temptime.to.date.setHours(0,0,0,0);
|
||||
$scope.temptime.from.date.setHours(1,0,0,0);
|
||||
$scope.temptime.to.date.setHours(1,0,0,0);
|
||||
|
||||
$q.when(customTimeModal).then(function(modalEl) {
|
||||
modalEl.modal('show');
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<!-- is there a better way to repeat without actually affecting the page? -->
|
||||
<div class="filter-pulldown" ng-repeat="pulldown in dashboard.current.pulldowns" ng-controller="PulldownCtrl" ng-show="pulldown.enable">
|
||||
<div class="top-row-close pointer pull-left" ng-click="toggle_pulldown(pulldown);dismiss();" bs-tooltip="'Toggle '+pulldown.type" data-placement="bottom">
|
||||
<span class="small"><strong>{{pulldown.type}}</strong></span>
|
||||
</div>
|
||||
<div class="top-row-open" ng-hide="pulldown.collapse">
|
||||
<kibana-simple-panel type="pulldown.type" ng-cloak></kibana-simple-panel>
|
||||
<div class="submenu-controls">
|
||||
<div class="submenu-panel" ng-controller="SubmenuCtrl" ng-repeat="pulldown in dashboard.current.pulldowns | filter:{ enable: true }">
|
||||
<div class="submenu-panel-title">
|
||||
<span class="small"><strong>{{pulldown.type}}:</strong></span>
|
||||
</div>
|
||||
<div class="submenu-panel-wrapper">
|
||||
<kibana-simple-panel type="pulldown.type" ng-cloak></kibana-simple-panel>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
<div class="container-fluid main" ng-class="{'grafana-dashboard-hide-controls': dashboard.current.hideControls}">
|
||||
<div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="pull-right editor-title">Dashboard settings</div>
|
||||
|
||||
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
|
||||
<div ng-repeat="tab in ['General', 'Rows','Controls', 'Import']" data-title="{{tab}}">
|
||||
<div ng-repeat="tab in ['General', 'Rows','Controls', 'Metrics', 'Import']" data-title="{{tab}}">
|
||||
</div>
|
||||
<div ng-repeat="tab in dashboard.current.nav" data-title="{{tab.type}}">
|
||||
</div>
|
||||
@@ -30,6 +30,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<div class="editor-option">
|
||||
<label class="small">Tags</label>
|
||||
<bootstrap-tagsinput ng-model="dashboard.current.tags" tagclass="label label-tag" placeholder="add tags">
|
||||
</bootstrap-tagsinput>
|
||||
<tip>Press enter to a add tag</tip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="editor.index == 1">
|
||||
@@ -133,7 +143,7 @@
|
||||
<ng-include src="'app/partials/import.html'"></ng-include>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="pulldown in dashboard.current.nav" ng-controller="PulldownCtrl" ng-show="editor.index == 5+$index">
|
||||
<div ng-repeat="pulldown in dashboard.current.nav" ng-controller="SubmenuCtrl" ng-show="editor.index == 5+$index">
|
||||
<ng-include ng-show="pulldown.enable" src="edit_path(pulldown.type)"></ng-include>
|
||||
<button ng-hide="pulldown.enable" class="btn" ng-click="pulldown.enable = true">Enable the {{pulldown.type}}</button>
|
||||
</div>
|
||||
@@ -141,6 +151,13 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="pull-left" style="padding-top: 15px;" ng-if="editor.index == 0">
|
||||
<span class="editor-option small">
|
||||
Grafana {{grafanaVersion}}
|
||||
</span>
|
||||
(<a class="small" href="https://github.com/torkelo/grafana/releases" target="_blank">check for updates</a>)
|
||||
</div>
|
||||
|
||||
<button ng-click="add_row(dashboard.current,row); reset_row();" class="btn btn-success" ng-show="editor.index == 1">Create Row</button>
|
||||
<button type="button" class="btn btn-danger" ng-click="editor.index=0;dismiss();reset_panel();dashboard.refresh()">Close</button>
|
||||
</div>
|
||||
@@ -1,7 +1,10 @@
|
||||
<div ng-controller="MetricKeysCtrl" ng-init="init()">
|
||||
<h5>Load metrics keys into elastic search</h5>
|
||||
|
||||
<div class="row-fluid">
|
||||
<p>
|
||||
Work in progress...
|
||||
</p>
|
||||
<!-- <div class="row-fluid">
|
||||
<div class="span12">
|
||||
<label class="small">Load metrics recursive starting from this metric path</label>
|
||||
<input type="text" class="input-xlarge" ng-model="metricPath"> </input>
|
||||
@@ -29,5 +32,5 @@
|
||||
<div class="span12" style="padding-top: 10px;">
|
||||
Metrics indexed: {{metricCounter}}
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
@@ -28,18 +28,36 @@
|
||||
<i class="icon-th-large"></i>
|
||||
New
|
||||
</button>
|
||||
<span>
|
||||
<span class="position: relative;">
|
||||
<input type="text"
|
||||
placeholder="search dashboards, metrics, or graphs"
|
||||
xng-focus="giveSearchFocus"
|
||||
ng-keydown="keyDown($event)"
|
||||
ng-model="elasticsearch.query"
|
||||
ng-change="elasticsearch_dblist(elasticsearch.query)" />
|
||||
ng-model="query.query" spellcheck='false'
|
||||
ng-change="search()" />
|
||||
<a class="search-tagview-switch" href="javascript:void(0);"
|
||||
ng-class="{'active': tagsOnly}"
|
||||
ng-click="showTags($event)">Tags</a>
|
||||
</span>
|
||||
</div>
|
||||
<h6 ng-hide="search_results.dashboards.length || search_results.metrics.length">No dashboards or metrics matching your query found</h6>
|
||||
<table class="table table-condensed table-striped">
|
||||
<tr bindonce ng-repeat="row in search_results.metrics"
|
||||
|
||||
<h6 ng-hide="results.dashboards.length || results.metrics.length">No dashboards or metrics matching your query found</h6>
|
||||
|
||||
<table class="table table-condensed table-striped" ng-if="tagsOnly">
|
||||
<tr ng-repeat="tag in results.tags" ng-class="{'selected-tag': $index === selectedIndex }">
|
||||
<td>
|
||||
<a ng-click="filterByTag(tag.term, $event)" class="label label-tag">
|
||||
{{tag.term}} ({{tag.count}})
|
||||
</a>
|
||||
</td>
|
||||
<td style="width:100%;padding-left: 10px;font-weight: bold;">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="table table-condensed table-striped" ng-if="!tagsOnly">
|
||||
<tr bindonce ng-repeat="row in results.metrics"
|
||||
class="grafana-search-metric-result"
|
||||
ng-class="{'selected': $index === selectedIndex }">
|
||||
<td><span class="label label-info">metric</span></td>
|
||||
@@ -54,10 +72,17 @@
|
||||
</tr>
|
||||
|
||||
<tr bindonce
|
||||
ng-repeat="row in search_results.dashboards"
|
||||
ng-repeat="row in results.dashboards"
|
||||
ng-class="{'selected': $index === selectedIndex }">
|
||||
<td><a confirm-click="elasticsearch_delete(row._id)" confirmation="Are you sure you want to delete the {{row._id}} dashboard"><i class="icon-remove"></i></a></td>
|
||||
<td style="width:100%"><a href="#/dashboard/elasticsearch/{{row._id}}" bo-text="row._id"></a></td>
|
||||
<td style="width:100%">
|
||||
<a href="#/dashboard/elasticsearch/{{row._id}}" bo-text="row._id"></a>
|
||||
</td>
|
||||
<td style="white-space: nowrap; text-align: right;">
|
||||
<a ng-click="filterByTag(tag, $event)" ng-repeat="tag in row._source.tags" style="margin-right: 5px;" class="label label-tag">
|
||||
{{tag}}
|
||||
</a>
|
||||
</td>
|
||||
<td><a><i class="icon-share" ng-click="share = dashboard.share_link(row._id,'elasticsearch',row._id)" bs-modal="'app/partials/dashLoaderShare.html'"></i></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -6,5 +6,6 @@ define([
|
||||
'./panelMove',
|
||||
'./graphite/graphiteSrv',
|
||||
'./keyboardManager',
|
||||
'./annotationsSrv',
|
||||
],
|
||||
function () {});
|
||||
77
src/app/services/annotationsSrv.js
Normal file
77
src/app/services/annotationsSrv.js
Normal file
@@ -0,0 +1,77 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'moment'
|
||||
], function (angular, _, moment) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.services');
|
||||
|
||||
module.service('annotationsSrv', function(dashboard, graphiteSrv, $q, alertSrv) {
|
||||
|
||||
this.init = function() {
|
||||
this.annotationList = [
|
||||
/* {
|
||||
type: 'graphite-target',
|
||||
enabled: false,
|
||||
target: 'metrics_data.mysite.dolph.counters.payment.cart_klarna_payment_completed.count',
|
||||
name: 'deploys',
|
||||
},
|
||||
{
|
||||
type: 'graphite-target',
|
||||
enabled: true,
|
||||
target: 'metrics_data.mysite.dolph.counters.payment.cart_paypal_payment_completed.count',
|
||||
name: 'restarts',
|
||||
}*/
|
||||
];
|
||||
};
|
||||
|
||||
this.getAnnotations = function(rangeUnparsed) {
|
||||
var graphiteAnnotations = _.where(this.annotationList, { type: 'graphite-target', enabled: true });
|
||||
var graphiteTargets = _.map(graphiteAnnotations, function(annotation) {
|
||||
return { target: annotation.target };
|
||||
});
|
||||
|
||||
if (graphiteTargets.length === 0) {
|
||||
return $q.when(null);
|
||||
}
|
||||
|
||||
var graphiteQuery = {
|
||||
range: rangeUnparsed,
|
||||
targets: graphiteTargets,
|
||||
format: 'json',
|
||||
maxDataPoints: 100
|
||||
};
|
||||
|
||||
return graphiteSrv.query(graphiteQuery)
|
||||
.then(function(results) {
|
||||
return _.reduce(results.data, function(list, target) {
|
||||
_.each(target.datapoints, function (values) {
|
||||
if (values[0] === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
list.push({
|
||||
min: values[1] * 1000,
|
||||
max: values[1] * 1000,
|
||||
eventType: "annotation",
|
||||
title: null,
|
||||
description: "<small><i class='icon-tag icon-flip-vertical'></i>test</small><br>"+
|
||||
moment(values[1] * 1000).format('YYYY-MM-DD HH:mm:ss'),
|
||||
score: 1
|
||||
});
|
||||
});
|
||||
|
||||
return list;
|
||||
}, []);
|
||||
})
|
||||
.then(null, function() {
|
||||
alertSrv.set('Annotations','Could not fetch annotations','error');
|
||||
});
|
||||
};
|
||||
|
||||
// Now init
|
||||
this.init();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -21,13 +21,14 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
|
||||
var _dash = {
|
||||
title: "",
|
||||
tags: [],
|
||||
style: "dark",
|
||||
timezone: 'browser',
|
||||
editable: true,
|
||||
failover: false,
|
||||
panel_hints: true,
|
||||
rows: [],
|
||||
pulldowns: [ { type: 'filtering' } ],
|
||||
pulldowns: [ { type: 'filtering' }, /*{ type: 'annotations' }*/ ],
|
||||
nav: [ { type: 'timepicker' } ],
|
||||
services: {},
|
||||
loader: {
|
||||
@@ -119,18 +120,26 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
};
|
||||
|
||||
var dash_defaults = function(dashboard) {
|
||||
_.defaults(dashboard,_dash);
|
||||
|
||||
_.defaults(dashboard, _dash);
|
||||
_.defaults(dashboard.loader,_dash.loader);
|
||||
|
||||
var filtering = _.findWhere(dashboard.pulldowns, {type: 'filtering'});
|
||||
if (!filtering) {
|
||||
dashboard.pulldowns.push({
|
||||
type: 'filtering',
|
||||
enable: false,
|
||||
collapse: true
|
||||
enable: false
|
||||
});
|
||||
}
|
||||
|
||||
/*var annotations = _.findWhere(dashboard.pulldowns, {type: 'annotations'});
|
||||
if (!annotations) {
|
||||
dashboard.pulldowns.push({
|
||||
type: 'annotations',
|
||||
enable: false
|
||||
});
|
||||
}*/
|
||||
|
||||
return dashboard;
|
||||
};
|
||||
|
||||
@@ -138,11 +147,14 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
// Cancel all timers
|
||||
timer.cancel_all();
|
||||
|
||||
// reset fullscreen flag
|
||||
$rootScope.fullscreen = false;
|
||||
|
||||
// Make sure the dashboard being loaded has everything required
|
||||
dashboard = dash_defaults(dashboard);
|
||||
|
||||
// Set the current dashboard
|
||||
self.current = _.clone(dashboard);
|
||||
self.current = angular.copy(dashboard);
|
||||
|
||||
// Delay this until we're sure that querySrv and filterSrv are ready
|
||||
$timeout(function() {
|
||||
@@ -353,6 +365,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
user: 'guest',
|
||||
group: 'guest',
|
||||
title: save.title,
|
||||
tags: save.tags,
|
||||
dashboard: angular.toJson(save)
|
||||
});
|
||||
|
||||
@@ -386,26 +399,6 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
);
|
||||
};
|
||||
|
||||
this.elasticsearch_list = function(query, count) {
|
||||
var request = ejs.Request().indices(config.grafana_index).types('dashboard');
|
||||
// if elasticsearch has disabled _all field we need
|
||||
// need to specifiy field here
|
||||
var q = 'title:' + (query || '*');
|
||||
|
||||
return request.query(
|
||||
ejs.QueryStringQuery(q)
|
||||
).size(count).doSearch(
|
||||
// Success
|
||||
function(result) {
|
||||
return result;
|
||||
},
|
||||
// Failure
|
||||
function() {
|
||||
return false;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
this.save_gist = function(title,dashboard) {
|
||||
var save = _.clone(dashboard || self.current);
|
||||
save.title = title || self.current.title;
|
||||
|
||||
@@ -26,6 +26,7 @@ define([
|
||||
|
||||
self.list = dashboard.current.services.filter.list;
|
||||
self.time = dashboard.current.services.filter.time;
|
||||
self.filterTemplateData = undefined;
|
||||
|
||||
self.templateSettings = {
|
||||
interpolate : /\[\[([\s\S]+?)\]\]/g,
|
||||
|
||||
@@ -130,6 +130,11 @@ function (_) {
|
||||
defaultParams: [10]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'cactiStyle',
|
||||
category: categories.Special,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'scale',
|
||||
category: categories.Transform,
|
||||
@@ -137,6 +142,13 @@ function (_) {
|
||||
defaultParams: [1]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'offset',
|
||||
category: categories.Transform,
|
||||
params: [ { name: "amount", type: "int", } ],
|
||||
defaultParams: [10]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'integral',
|
||||
category: categories.Transform,
|
||||
@@ -164,8 +176,8 @@ function (_) {
|
||||
addFuncDef({
|
||||
name: 'summarize',
|
||||
category: categories.Transform,
|
||||
params: [ { name: "interval", type: "string" }],
|
||||
defaultParams: ['1h']
|
||||
params: [ { name: "interval", type: "string" }, { name: "func", type: "select", options: ['sum', 'avg', 'min', 'max', 'last'] }],
|
||||
defaultParams: ['1h', 'sum']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
|
||||
@@ -20,13 +20,13 @@ function (angular, _, $, config, kbn, moment) {
|
||||
from: this.translateTime(options.range.from),
|
||||
until: this.translateTime(options.range.to),
|
||||
targets: options.targets,
|
||||
renderer: options.renderer,
|
||||
format: options.format,
|
||||
maxDataPoints: options.maxDataPoints
|
||||
};
|
||||
|
||||
var params = buildGraphiteParams(graphOptions);
|
||||
|
||||
if (options.renderer === 'png') {
|
||||
if (options.format === 'png') {
|
||||
return $q.when(graphiteRenderUrl + '?' + params.join('&'));
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ function (angular, _, $, config, kbn, moment) {
|
||||
var clean_options = [];
|
||||
var graphite_options = ['target', 'targets', 'from', 'until', 'rawData', 'format', 'maxDataPoints'];
|
||||
|
||||
if (options.renderer !== 'png') {
|
||||
if (options.format !== 'png') {
|
||||
options['format'] = 'json';
|
||||
}
|
||||
|
||||
|
||||
@@ -402,13 +402,20 @@ define([
|
||||
(ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z");
|
||||
}
|
||||
|
||||
// handle negative num literals
|
||||
if (char === '-') {
|
||||
value += char;
|
||||
index += 1;
|
||||
char = this.peek(index);
|
||||
}
|
||||
|
||||
// Numbers must start either with a decimal digit or a point.
|
||||
if (char !== "." && !isDecimalDigit(char)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (char !== ".") {
|
||||
value = this.peek(index);
|
||||
value += this.peek(index);
|
||||
index += 1;
|
||||
char = this.peek(index);
|
||||
|
||||
@@ -555,7 +562,7 @@ define([
|
||||
|
||||
if (index < length) {
|
||||
char = this.peek(index);
|
||||
if (isIdentifierStart(char)) {
|
||||
if (!this.isPunctuator(char)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -569,9 +576,7 @@ define([
|
||||
};
|
||||
},
|
||||
|
||||
scanPunctuator: function () {
|
||||
var ch1 = this.peek();
|
||||
|
||||
isPunctuator: function (ch1) {
|
||||
switch (ch1) {
|
||||
case ".":
|
||||
case "(":
|
||||
@@ -579,6 +584,16 @@ define([
|
||||
case ",":
|
||||
case "{":
|
||||
case "}":
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
scanPunctuator: function () {
|
||||
var ch1 = this.peek();
|
||||
|
||||
if (this.isPunctuator(ch1)) {
|
||||
return {
|
||||
type: ch1,
|
||||
value: ch1,
|
||||
|
||||
@@ -172,7 +172,7 @@ define([
|
||||
|
||||
return {
|
||||
type: 'number',
|
||||
value: parseInt(this.consumeToken().value, 10)
|
||||
value: parseFloat(this.consumeToken().value)
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
2
src/css/bootstrap.dark.min.css
vendored
2
src/css/bootstrap.dark.min.css
vendored
File diff suppressed because one or more lines are too long
2
src/css/bootstrap.light.min.css
vendored
2
src/css/bootstrap.light.min.css
vendored
File diff suppressed because one or more lines are too long
52
src/css/less/bootstrap-tagsinput.less
vendored
Normal file
52
src/css/less/bootstrap-tagsinput.less
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
.bootstrap-tagsinput {
|
||||
display: inline-block;
|
||||
padding: 4px 6px;
|
||||
margin-bottom: 10px;
|
||||
color: #555;
|
||||
vertical-align: middle;
|
||||
border-radius: 4px;
|
||||
max-width: 100%;
|
||||
line-height: 22px;
|
||||
|
||||
background-color: @inputBackground;
|
||||
border: 1px solid @inputBorder;
|
||||
.box-shadow(inset 0 1px 1px rgba(0,0,0,.075));
|
||||
.transition(~"border linear .2s, box-shadow linear .2s");
|
||||
|
||||
input {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
padding-left: 5px;
|
||||
margin: 0;
|
||||
width: auto !important;
|
||||
max-width: inherit;
|
||||
|
||||
&:focus {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tag {
|
||||
margin-right: 2px;
|
||||
color: white;
|
||||
|
||||
[data-role="remove"] {
|
||||
margin-left:8px;
|
||||
cursor:pointer;
|
||||
&:after{
|
||||
content: "x";
|
||||
padding:0px 2px;
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
&:active {
|
||||
box-shadow: inset 0 3px 5px rgba(0,0,0,0.125);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
@import "submenu.less";
|
||||
@import "bootstrap-tagsinput.less";
|
||||
|
||||
.navbar-static-top {
|
||||
border-bottom: 1px solid black;
|
||||
@@ -10,6 +12,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Search
|
||||
|
||||
.grafana-search-panel {
|
||||
padding: 6px 10px;
|
||||
|
||||
@@ -35,19 +39,20 @@
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.selected-tag .label-tag {
|
||||
background-color: @blue;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-pulldown {
|
||||
background: #292929;
|
||||
}
|
||||
|
||||
.top-row-close {
|
||||
border-right: 1px solid #202020;
|
||||
}
|
||||
.top-row-open {
|
||||
float: left;
|
||||
padding: 0px;
|
||||
background: none;
|
||||
.search-tagview-switch {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 180px;
|
||||
color: darken(@linkColor, 30%);
|
||||
&.active {
|
||||
color: @linkColor;
|
||||
}
|
||||
}
|
||||
|
||||
.row-button {
|
||||
@@ -402,4 +407,31 @@ input[type=text].func-param {
|
||||
border: 1px solid #1f1f1f;
|
||||
border-top: 1px solid #666666;
|
||||
border-left: 1px solid #666666;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// SPECTRUM CSS overrides
|
||||
|
||||
.sp-replacer {
|
||||
background: inherit;
|
||||
border: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.sp-replacer:hover, .sp-replacer.sp-active {
|
||||
border-color: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.sp-container {
|
||||
border-radius: 0;
|
||||
background-color: @heroUnitBackground;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sp-palette-container, .sp-picker-container {
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -230,20 +230,6 @@ form input.ng-invalid {
|
||||
background: @kibanaPanelBackground;
|
||||
}
|
||||
|
||||
.top-row-open {
|
||||
background: @navbarBackground;
|
||||
padding: 5px 25px 5px 25px;
|
||||
}
|
||||
|
||||
.top-row-close {
|
||||
padding: 5px 10px;
|
||||
text-transform: uppercase;
|
||||
margin: 0px;
|
||||
text-align: left;
|
||||
min-height: 16px !important;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.row-open {
|
||||
margin-top: 5px;
|
||||
left:-34px;
|
||||
@@ -564,4 +550,17 @@ div.flot-text {
|
||||
border-top-color: @popoverArrowColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Labels & Badges
|
||||
|
||||
.label-tag {
|
||||
background-color: @purple;
|
||||
color: @linkColor;
|
||||
}
|
||||
|
||||
.label-tag:hover {
|
||||
background-color: darken(@purple, 10%);
|
||||
color: lighten(@linkColor, 5%);
|
||||
}
|
||||
41
src/css/less/submenu.less
Normal file
41
src/css/less/submenu.less
Normal file
@@ -0,0 +1,41 @@
|
||||
.submenu-controls {
|
||||
background: #292929;
|
||||
font-size: inherit;
|
||||
label {
|
||||
margin: 0;
|
||||
padding-right: 4px;
|
||||
display: inline;
|
||||
}
|
||||
input[type=checkbox] {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-panel {
|
||||
padding: 0 10px 0 17px;
|
||||
border-right: 1px solid #202020;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.submenu-panel-title {
|
||||
float: left;
|
||||
text-transform: uppercase;
|
||||
padding: 4px 10px 3px 0;
|
||||
}
|
||||
|
||||
.submenu-panel-wrapper {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.submenu-toggle {
|
||||
padding: 4px 0 3px 8px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.submenu-toggle:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.annotation-disabled, .annotation-disabled a {
|
||||
color: darken(@textColor, 25%);
|
||||
}
|
||||
@@ -159,7 +159,7 @@
|
||||
|
||||
// Input placeholder text color
|
||||
// -------------------------
|
||||
@placeholderText: @grayLight;
|
||||
@placeholderText: darken(@textColor, 25%);
|
||||
|
||||
|
||||
// Hr border color
|
||||
519
src/css/spectrum.css
Normal file
519
src/css/spectrum.css
Normal file
@@ -0,0 +1,519 @@
|
||||
/***
|
||||
Spectrum Colorpicker v1.3.0
|
||||
https://github.com/bgrins/spectrum
|
||||
Author: Brian Grinstead
|
||||
License: MIT
|
||||
***/
|
||||
|
||||
.sp-container {
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
display:inline-block;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
/* https://github.com/bgrins/spectrum/issues/40 */
|
||||
z-index: 9999994;
|
||||
overflow: hidden;
|
||||
}
|
||||
.sp-container.sp-flat {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Fix for * { box-sizing: border-box; } */
|
||||
.sp-container,
|
||||
.sp-container * {
|
||||
-webkit-box-sizing: content-box;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
|
||||
.sp-top {
|
||||
position:relative;
|
||||
width: 100%;
|
||||
display:inline-block;
|
||||
}
|
||||
.sp-top-inner {
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
bottom:0;
|
||||
right:0;
|
||||
}
|
||||
.sp-color {
|
||||
position: absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
bottom:0;
|
||||
right:20%;
|
||||
}
|
||||
.sp-hue {
|
||||
position: absolute;
|
||||
top:0;
|
||||
right:0;
|
||||
bottom:0;
|
||||
left:84%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sp-clear-enabled .sp-hue {
|
||||
top:33px;
|
||||
height: 77.5%;
|
||||
}
|
||||
|
||||
.sp-fill {
|
||||
padding-top: 80%;
|
||||
}
|
||||
.sp-sat, .sp-val {
|
||||
position: absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
right:0;
|
||||
bottom:0;
|
||||
}
|
||||
|
||||
.sp-alpha-enabled .sp-top {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.sp-alpha-enabled .sp-alpha {
|
||||
display: block;
|
||||
}
|
||||
.sp-alpha-handle {
|
||||
position:absolute;
|
||||
top:-4px;
|
||||
bottom: -4px;
|
||||
width: 6px;
|
||||
left: 50%;
|
||||
cursor: pointer;
|
||||
border: 1px solid black;
|
||||
background: white;
|
||||
opacity: .8;
|
||||
}
|
||||
.sp-alpha {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: -14px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 8px;
|
||||
}
|
||||
.sp-alpha-inner {
|
||||
border: solid 1px #333;
|
||||
}
|
||||
|
||||
.sp-clear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sp-clear.sp-clear-display {
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.sp-clear-enabled .sp-clear {
|
||||
display: block;
|
||||
position:absolute;
|
||||
top:0px;
|
||||
right:0;
|
||||
bottom:0;
|
||||
left:84%;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
/* Don't allow text selection */
|
||||
.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button {
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select: -moz-none;
|
||||
-o-user-select:none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sp-container.sp-input-disabled .sp-input-container {
|
||||
display: none;
|
||||
}
|
||||
.sp-container.sp-buttons-disabled .sp-button-container {
|
||||
display: none;
|
||||
}
|
||||
.sp-palette-only .sp-picker-container {
|
||||
display: none;
|
||||
}
|
||||
.sp-palette-disabled .sp-palette-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sp-initial-disabled .sp-initial {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* Gradients for hue, saturation and value instead of images. Not pretty... but it works */
|
||||
.sp-sat {
|
||||
background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0)));
|
||||
background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0));
|
||||
background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
|
||||
background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
|
||||
background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
|
||||
background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0));
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)";
|
||||
filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81');
|
||||
}
|
||||
.sp-val {
|
||||
background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0)));
|
||||
background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0));
|
||||
background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
|
||||
background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
|
||||
background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
|
||||
background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0));
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)";
|
||||
filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000');
|
||||
}
|
||||
|
||||
.sp-hue {
|
||||
background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
|
||||
background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
|
||||
background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000));
|
||||
background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
|
||||
}
|
||||
|
||||
/* IE filters do not support multiple color stops.
|
||||
Generate 6 divs, line them up, and do two color gradients for each.
|
||||
Yes, really.
|
||||
*/
|
||||
.sp-1 {
|
||||
height:17%;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00');
|
||||
}
|
||||
.sp-2 {
|
||||
height:16%;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00');
|
||||
}
|
||||
.sp-3 {
|
||||
height:17%;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff');
|
||||
}
|
||||
.sp-4 {
|
||||
height:17%;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff');
|
||||
}
|
||||
.sp-5 {
|
||||
height:16%;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff');
|
||||
}
|
||||
.sp-6 {
|
||||
height:17%;
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000');
|
||||
}
|
||||
|
||||
.sp-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Clearfix hack */
|
||||
.sp-cf:before, .sp-cf:after { content: ""; display: table; }
|
||||
.sp-cf:after { clear: both; }
|
||||
.sp-cf { *zoom: 1; }
|
||||
|
||||
/* Mobile devices, make hue slider bigger so it is easier to slide */
|
||||
@media (max-device-width: 480px) {
|
||||
.sp-color { right: 40%; }
|
||||
.sp-hue { left: 63%; }
|
||||
.sp-fill { padding-top: 60%; }
|
||||
}
|
||||
.sp-dragger {
|
||||
border-radius: 5px;
|
||||
height: 5px;
|
||||
width: 5px;
|
||||
border: 1px solid #fff;
|
||||
background: #000;
|
||||
cursor: pointer;
|
||||
position:absolute;
|
||||
top:0;
|
||||
left: 0;
|
||||
}
|
||||
.sp-slider {
|
||||
position: absolute;
|
||||
top:0;
|
||||
cursor:pointer;
|
||||
height: 3px;
|
||||
left: -1px;
|
||||
right: -1px;
|
||||
border: 1px solid #000;
|
||||
background: white;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
/*
|
||||
Theme authors:
|
||||
Here are the basic themeable display options (colors, fonts, global widths).
|
||||
See http://bgrins.github.io/spectrum/themes/ for instructions.
|
||||
*/
|
||||
|
||||
.sp-container {
|
||||
border-radius: 0;
|
||||
background-color: #ECECEC;
|
||||
border: solid 1px #f0c49B;
|
||||
padding: 0;
|
||||
}
|
||||
.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear
|
||||
{
|
||||
font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.sp-top
|
||||
{
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.sp-color, .sp-hue, .sp-clear
|
||||
{
|
||||
border: solid 1px #666;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
.sp-input-container {
|
||||
float:right;
|
||||
width: 100px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.sp-initial-disabled .sp-input-container {
|
||||
width: 100%;
|
||||
}
|
||||
.sp-input {
|
||||
font-size: 12px !important;
|
||||
border: 1px inset;
|
||||
padding: 4px 5px;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
background:transparent;
|
||||
border-radius: 3px;
|
||||
color: #222;
|
||||
}
|
||||
.sp-input:focus {
|
||||
border: 1px solid orange;
|
||||
}
|
||||
.sp-input.sp-validation-error
|
||||
{
|
||||
border: 1px solid red;
|
||||
background: #fdd;
|
||||
}
|
||||
.sp-picker-container , .sp-palette-container
|
||||
{
|
||||
float:left;
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
padding-bottom: 300px;
|
||||
margin-bottom: -290px;
|
||||
}
|
||||
.sp-picker-container
|
||||
{
|
||||
width: 172px;
|
||||
border-left: solid 1px #fff;
|
||||
}
|
||||
|
||||
/* Palettes */
|
||||
.sp-palette-container
|
||||
{
|
||||
border-right: solid 1px #ccc;
|
||||
}
|
||||
|
||||
.sp-palette .sp-thumb-el {
|
||||
display: block;
|
||||
position:relative;
|
||||
float:left;
|
||||
width: 24px;
|
||||
height: 15px;
|
||||
margin: 3px;
|
||||
cursor: pointer;
|
||||
border:solid 2px transparent;
|
||||
}
|
||||
.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active {
|
||||
border-color: orange;
|
||||
}
|
||||
.sp-thumb-el
|
||||
{
|
||||
position:relative;
|
||||
}
|
||||
|
||||
/* Initial */
|
||||
.sp-initial
|
||||
{
|
||||
float: left;
|
||||
border: solid 1px #333;
|
||||
}
|
||||
.sp-initial span {
|
||||
width: 30px;
|
||||
height: 25px;
|
||||
border:none;
|
||||
display:block;
|
||||
float:left;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
.sp-initial .sp-clear-display {
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.sp-button-container {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* Replacer (the little preview div that shows up instead of the <input>) */
|
||||
.sp-replacer {
|
||||
margin:0;
|
||||
overflow:hidden;
|
||||
cursor:pointer;
|
||||
padding: 4px;
|
||||
display:inline-block;
|
||||
*zoom: 1;
|
||||
*display: inline;
|
||||
border: solid 1px #91765d;
|
||||
background: #eee;
|
||||
color: #333;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.sp-replacer:hover, .sp-replacer.sp-active {
|
||||
border-color: #F0C49B;
|
||||
color: #111;
|
||||
}
|
||||
.sp-replacer.sp-disabled {
|
||||
cursor:default;
|
||||
border-color: silver;
|
||||
color: silver;
|
||||
}
|
||||
.sp-dd {
|
||||
padding: 2px 0;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
float:left;
|
||||
font-size:10px;
|
||||
}
|
||||
.sp-preview
|
||||
{
|
||||
position:relative;
|
||||
width:25px;
|
||||
height: 20px;
|
||||
border: solid 1px #222;
|
||||
margin-right: 5px;
|
||||
float:left;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.sp-palette
|
||||
{
|
||||
*width: 220px;
|
||||
max-width: 220px;
|
||||
}
|
||||
.sp-palette .sp-thumb-el
|
||||
{
|
||||
width:16px;
|
||||
height: 16px;
|
||||
margin:2px 1px;
|
||||
border: solid 1px #d0d0d0;
|
||||
}
|
||||
|
||||
.sp-container
|
||||
{
|
||||
padding-bottom:0;
|
||||
}
|
||||
|
||||
|
||||
/* Buttons: http://hellohappy.org/css3-buttons/ */
|
||||
.sp-container button {
|
||||
background-color: #eeeeee;
|
||||
background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
|
||||
background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
|
||||
background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
|
||||
background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
|
||||
background-image: linear-gradient(to bottom, #eeeeee, #cccccc);
|
||||
border: 1px solid #ccc;
|
||||
border-bottom: 1px solid #bbb;
|
||||
border-radius: 3px;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
padding: 5px 4px;
|
||||
text-align: center;
|
||||
text-shadow: 0 1px 0 #eee;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.sp-container button:hover {
|
||||
background-color: #dddddd;
|
||||
background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb);
|
||||
background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb);
|
||||
background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb);
|
||||
background-image: -o-linear-gradient(top, #dddddd, #bbbbbb);
|
||||
background-image: linear-gradient(to bottom, #dddddd, #bbbbbb);
|
||||
border: 1px solid #bbb;
|
||||
border-bottom: 1px solid #999;
|
||||
cursor: pointer;
|
||||
text-shadow: 0 1px 0 #ddd;
|
||||
}
|
||||
.sp-container button:active {
|
||||
border: 1px solid #aaa;
|
||||
border-bottom: 1px solid #888;
|
||||
-webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
|
||||
-moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
|
||||
-ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
|
||||
-o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
|
||||
box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
|
||||
}
|
||||
.sp-cancel
|
||||
{
|
||||
font-size: 11px;
|
||||
color: #d93f3f !important;
|
||||
margin:0;
|
||||
padding:2px;
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
text-decoration:none;
|
||||
|
||||
}
|
||||
.sp-cancel:hover
|
||||
{
|
||||
color: #d93f3f !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
.sp-palette span:hover, .sp-palette span.sp-thumb-active
|
||||
{
|
||||
border-color: #000;
|
||||
}
|
||||
|
||||
.sp-preview, .sp-alpha, .sp-thumb-el
|
||||
{
|
||||
position:relative;
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
|
||||
}
|
||||
.sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner
|
||||
{
|
||||
display:block;
|
||||
position:absolute;
|
||||
top:0;left:0;bottom:0;right:0;
|
||||
}
|
||||
|
||||
.sp-palette .sp-thumb-inner
|
||||
{
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner
|
||||
{
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=);
|
||||
}
|
||||
|
||||
.sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner
|
||||
{
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=);
|
||||
}
|
||||
|
||||
.sp-clear-display {
|
||||
background-repeat:no-repeat;
|
||||
background-position: center;
|
||||
background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==);
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
<title>Grafana</title>
|
||||
<link rel="stylesheet" href="css/bootstrap.dark.min.css" title="Light">
|
||||
<link rel="stylesheet" href="css/timepicker.css">
|
||||
<link rel="stylesheet" href="css/spectrum.css">
|
||||
<link rel="stylesheet" href="css/animate.min.css">
|
||||
<link rel="stylesheet" href="css/normalize.min.css">
|
||||
<!-- load the root require context -->
|
||||
|
||||
26
src/test/specs/ctrl-specs.js
Normal file
26
src/test/specs/ctrl-specs.js
Normal file
@@ -0,0 +1,26 @@
|
||||
define([
|
||||
'angular',
|
||||
'angularMocks',
|
||||
'panels/graphite/module'
|
||||
], function(angular) {
|
||||
|
||||
/* describe('controller', function() {
|
||||
var scope, metricCtrl;
|
||||
|
||||
beforeEach(function() {
|
||||
angular.mock.inject(function($rootScope, $controller) {
|
||||
scope = $rootScope.$new();
|
||||
metricCtrl = $controller('kibana.panels.graphite.graphite', {
|
||||
$scope: scope
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should work', function() {
|
||||
metricCtrl.toggleYAxis({alias:'myAlias'});
|
||||
scope.panel.aliasYAxis['myAlias'].should.be(2);
|
||||
});
|
||||
|
||||
});*/
|
||||
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
define([
|
||||
'app/services/graphite/gfunc'
|
||||
'services/graphite/gfunc'
|
||||
], function(gfunc) {
|
||||
|
||||
describe('when creating func instance from func names', function() {
|
||||
@@ -60,7 +60,7 @@ define([
|
||||
|
||||
it('should return function categories', function() {
|
||||
var catIndex = gfunc.getCategories();
|
||||
expect(catIndex.Special.length).to.equal(8);
|
||||
expect(catIndex.Special.length).to.be.greaterThan(8);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
define([
|
||||
'app/services/graphite/lexer'
|
||||
'services/graphite/lexer'
|
||||
], function(Lexer) {
|
||||
|
||||
describe('when lexing graphite expression', function() {
|
||||
@@ -21,6 +21,21 @@ define([
|
||||
expect(tokens[4].value).to.be('se1-server-*');
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with dash2', function() {
|
||||
var lexer = new Lexer('net.192-168-1-1.192-168-1-9.ping_value.*');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].value).to.be('net');
|
||||
expect(tokens[2].value).to.be('192-168-1-1');
|
||||
});
|
||||
|
||||
it('simple function2', function() {
|
||||
var lexer = new Lexer('offset(test.metric, -100)');
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
expect(tokens[6].type).to.be('number');
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with curly braces', function() {
|
||||
var lexer = new Lexer('metric.se1-{first, second}.count');
|
||||
var tokens = lexer.tokenize();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
define([
|
||||
'app/services/graphite/parser'
|
||||
'services/graphite/parser'
|
||||
], function(Parser) {
|
||||
|
||||
describe('when parsing', function() {
|
||||
@@ -49,6 +49,14 @@ define([
|
||||
expect(rootNode.params.length).to.be(1);
|
||||
});
|
||||
|
||||
it('simple function2', function() {
|
||||
var parser = new Parser('offset(test.metric, -100)');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params[0].type).to.be('metric');
|
||||
expect(rootNode.params[1].type).to.be('number');
|
||||
});
|
||||
|
||||
it('simple function with string arg', function() {
|
||||
var parser = new Parser("randomWalk('test')");
|
||||
var rootNode = parser.getAst();
|
||||
@@ -125,6 +133,13 @@ define([
|
||||
expect(rootNode.pos).to.be(11);
|
||||
});
|
||||
|
||||
it('handle issue #69', function() {
|
||||
var parser = new Parser('cactiStyle(offset(scale(net.192-168-1-1.192-168-1-9.ping_value.*,0.001),-100))');
|
||||
var rootNode = parser.getAst();
|
||||
expect(rootNode.type).to.be('function');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,22 +1,111 @@
|
||||
require.config({
|
||||
baseUrl: 'base',
|
||||
baseUrl: 'base/app',
|
||||
|
||||
paths: {
|
||||
underscore: 'app/components/underscore.extended',
|
||||
'underscore-src': 'vendor/underscore',
|
||||
specs: '../test/specs',
|
||||
config: '../config.sample',
|
||||
kbn: 'components/kbn',
|
||||
|
||||
settings: 'components/settings',
|
||||
crypto: '../vendor/crypto.min',
|
||||
underscore: 'components/underscore.extended',
|
||||
'underscore-src': '../vendor/underscore',
|
||||
|
||||
moment: '../vendor/moment',
|
||||
chromath: '../vendor/chromath',
|
||||
|
||||
angular: '../vendor/angular/angular',
|
||||
angularMocks: '../vendor/angular/angular-mocks',
|
||||
'angular-dragdrop': '../vendor/angular/angular-dragdrop',
|
||||
'angular-strap': '../vendor/angular/angular-strap',
|
||||
'angular-sanitize': '../vendor/angular/angular-sanitize',
|
||||
timepicker: '../vendor/angular/timepicker',
|
||||
datepicker: '../vendor/angular/datepicker',
|
||||
bindonce: '../vendor/angular/bindonce',
|
||||
crypto: '../vendor/crypto.min',
|
||||
spectrum: '../vendor/spectrum',
|
||||
|
||||
jquery: '../vendor/jquery/jquery-1.8.0',
|
||||
|
||||
bootstrap: '../vendor/bootstrap/bootstrap',
|
||||
bindonce: '../vendor/angular/bindonce',
|
||||
|
||||
'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3',
|
||||
|
||||
'extend-jquery': 'components/extend-jquery',
|
||||
|
||||
'jquery.flot': '../vendor/jquery/jquery.flot',
|
||||
'jquery.flot.pie': '../vendor/jquery/jquery.flot.pie',
|
||||
'jquery.flot.events': '../vendor/jquery/jquery.flot.events',
|
||||
'jquery.flot.selection': '../vendor/jquery/jquery.flot.selection',
|
||||
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
|
||||
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
|
||||
'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
|
||||
'jquery.flot.byte': '../vendor/jquery/jquery.flot.byte',
|
||||
|
||||
modernizr: '../vendor/modernizr-2.6.1',
|
||||
elasticjs: '../vendor/elasticjs/elastic-angular-client',
|
||||
},
|
||||
|
||||
shim: {
|
||||
underscore: {
|
||||
exports: '_'
|
||||
},
|
||||
|
||||
bootstrap: {
|
||||
deps: ['jquery']
|
||||
},
|
||||
|
||||
modernizr: {
|
||||
exports: 'Modernizr'
|
||||
},
|
||||
|
||||
angular: {
|
||||
deps: ['jquery', 'config'],
|
||||
exports: 'angular'
|
||||
},
|
||||
|
||||
angularMocks: {
|
||||
deps: ['angular'],
|
||||
},
|
||||
|
||||
crypto: {
|
||||
exports: 'Crypto'
|
||||
},
|
||||
|
||||
'jquery-ui': ['jquery'],
|
||||
'jquery.flot': ['jquery'],
|
||||
'jquery.flot.byte': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.pie': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.events': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.selection':['jquery', 'jquery.flot'],
|
||||
'jquery.flot.stack': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
|
||||
'jquery.flot.time': ['jquery', 'jquery.flot'],
|
||||
|
||||
'angular-sanitize': ['angular'],
|
||||
'angular-cookies': ['angular'],
|
||||
'angular-dragdrop': ['jquery','jquery-ui','angular'],
|
||||
'angular-loader': ['angular'],
|
||||
'angular-mocks': ['angular'],
|
||||
'angular-resource': ['angular'],
|
||||
'angular-route': ['angular'],
|
||||
'angular-touch': ['angular'],
|
||||
'bindonce': ['angular'],
|
||||
'angular-strap': ['angular', 'bootstrap','timepicker', 'datepicker'],
|
||||
|
||||
timepicker: ['jquery', 'bootstrap'],
|
||||
datepicker: ['jquery', 'bootstrap'],
|
||||
|
||||
elasticjs: ['angular', '../vendor/elasticjs/elastic'],
|
||||
}
|
||||
});
|
||||
|
||||
require([
|
||||
'test/specs/lexer-specs',
|
||||
'test/specs/parser-specs',
|
||||
'test/specs/gfunc-specs',
|
||||
'specs/lexer-specs',
|
||||
'specs/parser-specs',
|
||||
'specs/gfunc-specs',
|
||||
'specs/ctrl-specs',
|
||||
], function () {
|
||||
window.__karma__.start();
|
||||
});
|
||||
1886
src/vendor/angular/angular-mocks.js
vendored
Normal file
1886
src/vendor/angular/angular-mocks.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
225
src/vendor/jquery/jquery.flot.fillbetween.js
vendored
Normal file
225
src/vendor/jquery/jquery.flot.fillbetween.js
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
/* Flot plugin for computing bottoms for filled line and bar charts.
|
||||
|
||||
Copyright (c) 2007-2013 IOLA and Ole Laursen.
|
||||
Licensed under the MIT license.
|
||||
|
||||
The case: you've got two series that you want to fill the area between. In Flot
|
||||
terms, you need to use one as the fill bottom of the other. You can specify the
|
||||
bottom of each data point as the third coordinate manually, or you can use this
|
||||
plugin to compute it for you.
|
||||
|
||||
In order to name the other series, you need to give it an id, like this:
|
||||
|
||||
var dataset = [
|
||||
{ data: [ ... ], id: "foo" } , // use default bottom
|
||||
{ data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
|
||||
];
|
||||
|
||||
$.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }});
|
||||
|
||||
As a convenience, if the id given is a number that doesn't appear as an id in
|
||||
the series, it is interpreted as the index in the array instead (so fillBetween:
|
||||
0 can also mean the first series).
|
||||
|
||||
Internally, the plugin modifies the datapoints in each series. For line series,
|
||||
extra data points might be inserted through interpolation. Note that at points
|
||||
where the bottom line is not defined (due to a null point or start/end of line),
|
||||
the current line will show a gap too. The algorithm comes from the
|
||||
jquery.flot.stack.js plugin, possibly some code could be shared.
|
||||
|
||||
*/
|
||||
|
||||
(function ( $ ) {
|
||||
|
||||
var options = {
|
||||
series: {
|
||||
fillBetween: null // or number
|
||||
}
|
||||
};
|
||||
|
||||
function init( plot ) {
|
||||
|
||||
function findBottomSeries( s, allseries ) {
|
||||
|
||||
var i;
|
||||
|
||||
for ( i = 0; i < allseries.length; ++i ) {
|
||||
if ( allseries[ i ].id === s.fillBetween ) {
|
||||
return allseries[ i ];
|
||||
}
|
||||
}
|
||||
|
||||
if ( typeof s.fillBetween === "number" ) {
|
||||
if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) {
|
||||
return null;
|
||||
}
|
||||
return allseries[ s.fillBetween ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function computeFillBottoms( plot, s, datapoints ) {
|
||||
if ( s.fillBetween == null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var other = findBottomSeries( s, plot.getData() );
|
||||
|
||||
if ( !other ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ps = datapoints.pointsize,
|
||||
points = datapoints.points,
|
||||
otherps = other.datapoints.pointsize,
|
||||
otherpoints = other.datapoints.points,
|
||||
newpoints = [],
|
||||
px, py, intery, qx, qy, bottom,
|
||||
withlines = s.lines.show,
|
||||
withbottom = ps > 2 && datapoints.format[2].y,
|
||||
withsteps = withlines && s.lines.steps,
|
||||
fromgap = true,
|
||||
i = 0,
|
||||
j = 0,
|
||||
l, m;
|
||||
|
||||
while ( true ) {
|
||||
|
||||
if ( i >= points.length ) {
|
||||
break;
|
||||
}
|
||||
|
||||
l = newpoints.length;
|
||||
|
||||
if ( points[ i ] == null ) {
|
||||
|
||||
// copy gaps
|
||||
|
||||
for ( m = 0; m < ps; ++m ) {
|
||||
newpoints.push( points[ i + m ] );
|
||||
}
|
||||
|
||||
i += ps;
|
||||
|
||||
} else if ( j >= otherpoints.length ) {
|
||||
|
||||
// for lines, we can't use the rest of the points
|
||||
|
||||
if ( !withlines ) {
|
||||
for ( m = 0; m < ps; ++m ) {
|
||||
newpoints.push( points[ i + m ] );
|
||||
}
|
||||
}
|
||||
|
||||
i += ps;
|
||||
|
||||
} else if ( otherpoints[ j ] == null ) {
|
||||
|
||||
// oops, got a gap
|
||||
|
||||
for ( m = 0; m < ps; ++m ) {
|
||||
newpoints.push( null );
|
||||
}
|
||||
|
||||
fromgap = true;
|
||||
j += otherps;
|
||||
|
||||
} else {
|
||||
|
||||
// cases where we actually got two points
|
||||
|
||||
px = points[ i ];
|
||||
py = points[ i + 1 ];
|
||||
qx = otherpoints[ j ];
|
||||
qy = otherpoints[ j + 1 ];
|
||||
bottom = 0;
|
||||
|
||||
if ( px === qx ) {
|
||||
|
||||
for ( m = 0; m < ps; ++m ) {
|
||||
newpoints.push( points[ i + m ] );
|
||||
}
|
||||
|
||||
//newpoints[ l + 1 ] += qy;
|
||||
bottom = qy;
|
||||
|
||||
i += ps;
|
||||
j += otherps;
|
||||
|
||||
} else if ( px > qx ) {
|
||||
|
||||
// we got past point below, might need to
|
||||
// insert interpolated extra point
|
||||
|
||||
if ( withlines && i > 0 && points[ i - ps ] != null ) {
|
||||
intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px );
|
||||
newpoints.push( qx );
|
||||
newpoints.push( intery );
|
||||
for ( m = 2; m < ps; ++m ) {
|
||||
newpoints.push( points[ i + m ] );
|
||||
}
|
||||
bottom = qy;
|
||||
}
|
||||
|
||||
j += otherps;
|
||||
|
||||
} else { // px < qx
|
||||
|
||||
// if we come from a gap, we just skip this point
|
||||
|
||||
if ( fromgap && withlines ) {
|
||||
i += ps;
|
||||
continue;
|
||||
}
|
||||
|
||||
for ( m = 0; m < ps; ++m ) {
|
||||
newpoints.push( points[ i + m ] );
|
||||
}
|
||||
|
||||
// we might be able to interpolate a point below,
|
||||
// this can give us a better y
|
||||
|
||||
if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) {
|
||||
bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx );
|
||||
}
|
||||
|
||||
//newpoints[l + 1] += bottom;
|
||||
|
||||
i += ps;
|
||||
}
|
||||
|
||||
fromgap = false;
|
||||
|
||||
if ( l !== newpoints.length && withbottom ) {
|
||||
newpoints[ l + 2 ] = bottom;
|
||||
}
|
||||
}
|
||||
|
||||
// maintain the line steps invariant
|
||||
|
||||
if ( withsteps && l !== newpoints.length && l > 0 &&
|
||||
newpoints[ l ] !== null &&
|
||||
newpoints[ l ] !== newpoints[ l - ps ] &&
|
||||
newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) {
|
||||
for (m = 0; m < ps; ++m) {
|
||||
newpoints[ l + ps + m ] = newpoints[ l + m ];
|
||||
}
|
||||
newpoints[ l + 1 ] = newpoints[ l - ps + 1 ];
|
||||
}
|
||||
}
|
||||
|
||||
datapoints.points = newpoints;
|
||||
}
|
||||
|
||||
plot.hooks.processDatapoints.push( computeFillBottoms );
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: "fillbetween",
|
||||
version: "1.0"
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
2032
src/vendor/spectrum.js
vendored
Normal file
2032
src/vendor/spectrum.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
503
src/vendor/tagsinput/bootstrap-tagsinput.js
vendored
Normal file
503
src/vendor/tagsinput/bootstrap-tagsinput.js
vendored
Normal file
@@ -0,0 +1,503 @@
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
var defaultOptions = {
|
||||
tagClass: function(item) {
|
||||
return 'label label-info';
|
||||
},
|
||||
itemValue: function(item) {
|
||||
return item ? item.toString() : item;
|
||||
},
|
||||
itemText: function(item) {
|
||||
return this.itemValue(item);
|
||||
},
|
||||
freeInput: true,
|
||||
maxTags: undefined,
|
||||
confirmKeys: [13],
|
||||
onTagExists: function(item, $tag) {
|
||||
$tag.hide().fadeIn();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor function
|
||||
*/
|
||||
function TagsInput(element, options) {
|
||||
this.itemsArray = [];
|
||||
|
||||
this.$element = $(element);
|
||||
this.$element.hide();
|
||||
|
||||
this.isSelect = (element.tagName === 'SELECT');
|
||||
this.multiple = (this.isSelect && element.hasAttribute('multiple'));
|
||||
this.objectItems = options && options.itemValue;
|
||||
this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : '';
|
||||
this.inputSize = Math.max(1, this.placeholderText.length);
|
||||
|
||||
this.$container = $('<div class="bootstrap-tagsinput"></div>');
|
||||
this.$input = $('<input size="' + this.inputSize + '" type="text" placeholder="' + this.placeholderText + '"/>').appendTo(this.$container);
|
||||
|
||||
this.$element.after(this.$container);
|
||||
|
||||
this.build(options);
|
||||
}
|
||||
|
||||
TagsInput.prototype = {
|
||||
constructor: TagsInput,
|
||||
|
||||
/**
|
||||
* Adds the given item as a new tag. Pass true to dontPushVal to prevent
|
||||
* updating the elements val()
|
||||
*/
|
||||
add: function(item, dontPushVal) {
|
||||
var self = this;
|
||||
|
||||
if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags)
|
||||
return;
|
||||
|
||||
// Ignore falsey values, except false
|
||||
if (item !== false && !item)
|
||||
return;
|
||||
|
||||
// Throw an error when trying to add an object while the itemValue option was not set
|
||||
if (typeof item === "object" && !self.objectItems)
|
||||
throw("Can't add objects when itemValue option is not set");
|
||||
|
||||
// Ignore strings only containg whitespace
|
||||
if (item.toString().match(/^\s*$/))
|
||||
return;
|
||||
|
||||
// If SELECT but not multiple, remove current tag
|
||||
if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
|
||||
self.remove(self.itemsArray[0]);
|
||||
|
||||
if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
|
||||
var items = item.split(',');
|
||||
if (items.length > 1) {
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
this.add(items[i], true);
|
||||
}
|
||||
|
||||
if (!dontPushVal)
|
||||
self.pushVal();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var itemValue = self.options.itemValue(item),
|
||||
itemText = self.options.itemText(item),
|
||||
tagClass = self.options.tagClass(item);
|
||||
|
||||
// Ignore items allready added
|
||||
var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0];
|
||||
if (existing) {
|
||||
// Invoke onTagExists
|
||||
if (self.options.onTagExists) {
|
||||
var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; });
|
||||
self.options.onTagExists(item, $existingTag);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// register item in internal array and map
|
||||
self.itemsArray.push(item);
|
||||
|
||||
// add a tag element
|
||||
var $tag = $('<span class="tag ' + htmlEncode(tagClass) + '">' + htmlEncode(itemText) + '<span data-role="remove"></span></span>');
|
||||
$tag.data('item', item);
|
||||
self.findInputWrapper().before($tag);
|
||||
$tag.after(' ');
|
||||
|
||||
// add <option /> if item represents a value not present in one of the <select />'s options
|
||||
if (self.isSelect && !$('option[value="' + escape(itemValue) + '"]',self.$element)[0]) {
|
||||
var $option = $('<option selected>' + htmlEncode(itemText) + '</option>');
|
||||
$option.data('item', item);
|
||||
$option.attr('value', itemValue);
|
||||
self.$element.append($option);
|
||||
}
|
||||
|
||||
if (!dontPushVal)
|
||||
self.pushVal();
|
||||
|
||||
// Add class when reached maxTags
|
||||
if (self.options.maxTags === self.itemsArray.length)
|
||||
self.$container.addClass('bootstrap-tagsinput-max');
|
||||
|
||||
self.$element.trigger($.Event('itemAdded', { item: item }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the given item. Pass true to dontPushVal to prevent updating the
|
||||
* elements val()
|
||||
*/
|
||||
remove: function(item, dontPushVal) {
|
||||
var self = this;
|
||||
|
||||
if (self.objectItems) {
|
||||
if (typeof item === "object")
|
||||
item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == self.options.itemValue(item); } )[0];
|
||||
else
|
||||
item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == item; } )[0];
|
||||
}
|
||||
|
||||
if (item) {
|
||||
$('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
|
||||
$('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
|
||||
self.itemsArray.splice($.inArray(item, self.itemsArray), 1);
|
||||
}
|
||||
|
||||
if (!dontPushVal)
|
||||
self.pushVal();
|
||||
|
||||
// Remove class when reached maxTags
|
||||
if (self.options.maxTags > self.itemsArray.length)
|
||||
self.$container.removeClass('bootstrap-tagsinput-max');
|
||||
|
||||
self.$element.trigger($.Event('itemRemoved', { item: item }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all items
|
||||
*/
|
||||
removeAll: function() {
|
||||
var self = this;
|
||||
|
||||
$('.tag', self.$container).remove();
|
||||
$('option', self.$element).remove();
|
||||
|
||||
while(self.itemsArray.length > 0)
|
||||
self.itemsArray.pop();
|
||||
|
||||
self.pushVal();
|
||||
|
||||
if (self.options.maxTags && !this.isEnabled())
|
||||
this.enable();
|
||||
},
|
||||
|
||||
/**
|
||||
* Refreshes the tags so they match the text/value of their corresponding
|
||||
* item.
|
||||
*/
|
||||
refresh: function() {
|
||||
var self = this;
|
||||
$('.tag', self.$container).each(function() {
|
||||
var $tag = $(this),
|
||||
item = $tag.data('item'),
|
||||
itemValue = self.options.itemValue(item),
|
||||
itemText = self.options.itemText(item),
|
||||
tagClass = self.options.tagClass(item);
|
||||
|
||||
// Update tag's class and inner text
|
||||
$tag.attr('class', null);
|
||||
$tag.addClass('tag ' + htmlEncode(tagClass));
|
||||
$tag.contents().filter(function() {
|
||||
return this.nodeType == 3;
|
||||
})[0].nodeValue = htmlEncode(itemText);
|
||||
|
||||
if (self.isSelect) {
|
||||
var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
|
||||
option.attr('value', itemValue);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the items added as tags
|
||||
*/
|
||||
items: function() {
|
||||
return this.itemsArray;
|
||||
},
|
||||
|
||||
/**
|
||||
* Assembly value by retrieving the value of each item, and set it on the
|
||||
* element.
|
||||
*/
|
||||
pushVal: function() {
|
||||
var self = this,
|
||||
val = $.map(self.items(), function(item) {
|
||||
return self.options.itemValue(item).toString();
|
||||
});
|
||||
|
||||
self.$element.val(val, true).trigger('change');
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes the tags input behaviour on the element
|
||||
*/
|
||||
build: function(options) {
|
||||
var self = this;
|
||||
|
||||
self.options = $.extend({}, defaultOptions, options);
|
||||
var typeahead = self.options.typeahead || {};
|
||||
|
||||
// When itemValue is set, freeInput should always be false
|
||||
if (self.objectItems)
|
||||
self.options.freeInput = false;
|
||||
|
||||
makeOptionItemFunction(self.options, 'itemValue');
|
||||
makeOptionItemFunction(self.options, 'itemText');
|
||||
makeOptionItemFunction(self.options, 'tagClass');
|
||||
|
||||
// for backwards compatibility, self.options.source is deprecated
|
||||
if (self.options.source)
|
||||
typeahead.source = self.options.source;
|
||||
|
||||
if (typeahead.source && $.fn.typeahead) {
|
||||
makeOptionFunction(typeahead, 'source');
|
||||
|
||||
self.$input.typeahead({
|
||||
source: function (query, process) {
|
||||
function processItems(items) {
|
||||
var texts = [];
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var text = self.options.itemText(items[i]);
|
||||
map[text] = items[i];
|
||||
texts.push(text);
|
||||
}
|
||||
process(texts);
|
||||
}
|
||||
|
||||
this.map = {};
|
||||
var map = this.map,
|
||||
data = typeahead.source(query);
|
||||
|
||||
if ($.isFunction(data.success)) {
|
||||
// support for Angular promises
|
||||
data.success(processItems);
|
||||
} else {
|
||||
// support for functions and jquery promises
|
||||
$.when(data)
|
||||
.then(processItems);
|
||||
}
|
||||
},
|
||||
updater: function (text) {
|
||||
self.add(this.map[text]);
|
||||
},
|
||||
matcher: function (text) {
|
||||
return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
|
||||
},
|
||||
sorter: function (texts) {
|
||||
return texts.sort();
|
||||
},
|
||||
highlighter: function (text) {
|
||||
var regex = new RegExp( '(' + this.query + ')', 'gi' );
|
||||
return text.replace( regex, "<strong>$1</strong>" );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.$container.on('click', $.proxy(function(event) {
|
||||
self.$input.focus();
|
||||
}, self));
|
||||
|
||||
self.$container.on('keydown', 'input', $.proxy(function(event) {
|
||||
var $input = $(event.target),
|
||||
$inputWrapper = self.findInputWrapper();
|
||||
|
||||
switch (event.which) {
|
||||
// BACKSPACE
|
||||
case 8:
|
||||
if (doGetCaretPosition($input[0]) === 0) {
|
||||
var prev = $inputWrapper.prev();
|
||||
if (prev) {
|
||||
self.remove(prev.data('item'));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// DELETE
|
||||
case 46:
|
||||
if (doGetCaretPosition($input[0]) === 0) {
|
||||
var next = $inputWrapper.next();
|
||||
if (next) {
|
||||
self.remove(next.data('item'));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// LEFT ARROW
|
||||
case 37:
|
||||
// Try to move the input before the previous tag
|
||||
var $prevTag = $inputWrapper.prev();
|
||||
if ($input.val().length === 0 && $prevTag[0]) {
|
||||
$prevTag.before($inputWrapper);
|
||||
$input.focus();
|
||||
}
|
||||
break;
|
||||
// RIGHT ARROW
|
||||
case 39:
|
||||
// Try to move the input after the next tag
|
||||
var $nextTag = $inputWrapper.next();
|
||||
if ($input.val().length === 0 && $nextTag[0]) {
|
||||
$nextTag.after($inputWrapper);
|
||||
$input.focus();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// When key corresponds one of the confirmKeys, add current input
|
||||
// as a new tag
|
||||
if (self.options.freeInput && $.inArray(event.which, self.options.confirmKeys) >= 0) {
|
||||
self.add($input.val());
|
||||
$input.val('');
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// Reset internal input's size
|
||||
$input.attr('size', Math.max(this.inputSize, $input.val().length));
|
||||
}, self));
|
||||
|
||||
// Remove icon clicked
|
||||
self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
|
||||
self.remove($(event.target).closest('.tag').data('item'));
|
||||
}, self));
|
||||
|
||||
// Only add existing value as tags when using strings as tags
|
||||
if (self.options.itemValue === defaultOptions.itemValue) {
|
||||
if (self.$element[0].tagName === 'INPUT') {
|
||||
self.add(self.$element.val());
|
||||
} else {
|
||||
$('option', self.$element).each(function() {
|
||||
self.add($(this).attr('value'), true);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all tagsinput behaviour and unregsiter all event handlers
|
||||
*/
|
||||
destroy: function() {
|
||||
var self = this;
|
||||
|
||||
// Unbind events
|
||||
self.$container.off('keypress', 'input');
|
||||
self.$container.off('click', '[role=remove]');
|
||||
|
||||
self.$container.remove();
|
||||
self.$element.removeData('tagsinput');
|
||||
self.$element.show();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets focus on the tagsinput
|
||||
*/
|
||||
focus: function() {
|
||||
this.$input.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the internal input element
|
||||
*/
|
||||
input: function() {
|
||||
return this.$input;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the element which is wrapped around the internal input. This
|
||||
* is normally the $container, but typeahead.js moves the $input element.
|
||||
*/
|
||||
findInputWrapper: function() {
|
||||
var elt = this.$input[0],
|
||||
container = this.$container[0];
|
||||
while(elt && elt.parentNode !== container)
|
||||
elt = elt.parentNode;
|
||||
|
||||
return $(elt);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Register JQuery plugin
|
||||
*/
|
||||
$.fn.tagsinput = function(arg1, arg2) {
|
||||
var results = [];
|
||||
|
||||
this.each(function() {
|
||||
var tagsinput = $(this).data('tagsinput');
|
||||
|
||||
// Initialize a new tags input
|
||||
if (!tagsinput) {
|
||||
tagsinput = new TagsInput(this, arg1);
|
||||
$(this).data('tagsinput', tagsinput);
|
||||
results.push(tagsinput);
|
||||
|
||||
if (this.tagName === 'SELECT') {
|
||||
$('option', $(this)).attr('selected', 'selected');
|
||||
}
|
||||
|
||||
// Init tags from $(this).val()
|
||||
$(this).val($(this).val());
|
||||
} else {
|
||||
// Invoke function on existing tags input
|
||||
var retVal = tagsinput[arg1](arg2);
|
||||
if (retVal !== undefined)
|
||||
results.push(retVal);
|
||||
}
|
||||
});
|
||||
|
||||
if ( typeof arg1 == 'string') {
|
||||
// Return the results from the invoked function calls
|
||||
return results.length > 1 ? results : results[0];
|
||||
} else {
|
||||
return results;
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.tagsinput.Constructor = TagsInput;
|
||||
|
||||
/**
|
||||
* Most options support both a string or number as well as a function as
|
||||
* option value. This function makes sure that the option with the given
|
||||
* key in the given options is wrapped in a function
|
||||
*/
|
||||
function makeOptionItemFunction(options, key) {
|
||||
if (typeof options[key] !== 'function') {
|
||||
var propertyName = options[key];
|
||||
options[key] = function(item) { return item[propertyName]; };
|
||||
}
|
||||
}
|
||||
function makeOptionFunction(options, key) {
|
||||
if (typeof options[key] !== 'function') {
|
||||
var value = options[key];
|
||||
options[key] = function() { return value; };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* HtmlEncodes the given value
|
||||
*/
|
||||
var htmlEncodeContainer = $('<div />');
|
||||
function htmlEncode(value) {
|
||||
if (value) {
|
||||
return htmlEncodeContainer.text(value).html();
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the caret in the given input field
|
||||
* http://flightschool.acylt.com/devnotes/caret-position-woes/
|
||||
*/
|
||||
function doGetCaretPosition(oField) {
|
||||
var iCaretPos = 0;
|
||||
if (document.selection) {
|
||||
oField.focus ();
|
||||
var oSel = document.selection.createRange();
|
||||
oSel.moveStart ('character', -oField.value.length);
|
||||
iCaretPos = oSel.text.length;
|
||||
} else if (oField.selectionStart || oField.selectionStart == '0') {
|
||||
iCaretPos = oField.selectionStart;
|
||||
}
|
||||
return (iCaretPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize tagsinput behaviour on inputs and selects which have
|
||||
* data-role=tagsinput
|
||||
*/
|
||||
$(function() {
|
||||
$("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
|
||||
});
|
||||
})(window.jQuery);
|
||||
@@ -4,7 +4,7 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('build', [
|
||||
'jshint:source',
|
||||
'clean:on_start',
|
||||
'less:dist',
|
||||
'less:src',
|
||||
'copy:everything_but_less_to_temp',
|
||||
'htmlmin:build',
|
||||
'cssmin:build',
|
||||
@@ -32,6 +32,10 @@ module.exports = function(grunt) {
|
||||
{
|
||||
pattern: /@REV@/,
|
||||
replacement: desc.object
|
||||
},
|
||||
{
|
||||
pattern: /@grafanaVersion@/,
|
||||
replacement: 'v<%= pkg.version %>'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ module.exports = function(config) {
|
||||
everything_but_less_to_temp: {
|
||||
cwd: '<%= srcDir %>',
|
||||
expand: true,
|
||||
src: ['**/*', '!**/*.less'],
|
||||
src: ['**/*', '!**/*.less', '!config.js'],
|
||||
dest: '<%= tempDir %>'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ module.exports = function(config) {
|
||||
// Compile in place when not building
|
||||
src:{
|
||||
options: {
|
||||
paths: ["<%= srcDir %>/vendor/bootstrap/less"],
|
||||
paths: ["<%= srcDir %>/vendor/bootstrap/less", "<%= srcDir %>/css/less"],
|
||||
yuicompress:true
|
||||
},
|
||||
files: {
|
||||
|
||||
@@ -12,6 +12,8 @@ module.exports = function(config,grunt) {
|
||||
optimizeCss: 'none',
|
||||
optimizeAllPluginResources: false,
|
||||
|
||||
paths: { config: '../config.sample' }, // fix, fallbacks need to be specified
|
||||
|
||||
removeCombined: true,
|
||||
findNestedDependencies: true,
|
||||
normalizeDirDefines: 'all',
|
||||
|
||||
@@ -2,7 +2,7 @@ module.exports = function(config) {
|
||||
return {
|
||||
dest: {
|
||||
expand: true,
|
||||
src: ['**/*.js', '!config.js', '!app/dashboards/*.js', '!app/dashboards/**/*.js',],
|
||||
src: ['**/*.js', '!config.sample.js', '!app/dashboards/*.js', '!app/dashboards/**/*.js',],
|
||||
dest: '<%= destDir %>',
|
||||
cwd: '<%= destDir %>',
|
||||
options: {
|
||||
|
||||
Reference in New Issue
Block a user