Compare commits
137 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 | ||
|
|
0015f81645 | ||
|
|
ed76335c96 | ||
|
|
e0a35a3958 | ||
|
|
d9ec9ed1ef | ||
|
|
0032f934c9 | ||
|
|
8816e18027 | ||
|
|
e8be950752 | ||
|
|
aea4b37760 | ||
|
|
1aff1a0000 | ||
|
|
df54c17dca | ||
|
|
73c5883f0d | ||
|
|
e3561ce555 | ||
|
|
a5feb639d9 | ||
|
|
ac33286c8c | ||
|
|
19d5d32229 | ||
|
|
d90f2537a4 | ||
|
|
487aa1f2c3 | ||
|
|
be9ab15c4e | ||
|
|
7d4f0d24d5 | ||
|
|
bafa334b94 | ||
|
|
80c5f99f3b | ||
|
|
212bf5df98 | ||
|
|
32189afc67 | ||
|
|
0fa6b34f1a | ||
|
|
4d6bb8b6be | ||
|
|
82da75bc0c | ||
|
|
beda378314 | ||
|
|
6734864acd | ||
|
|
9f548870d0 | ||
|
|
d2bae43d26 | ||
|
|
e77e43faab | ||
|
|
224d2f92c3 | ||
|
|
250e354659 | ||
|
|
2e59587c8e | ||
|
|
6f4520254b | ||
|
|
b939c02da0 | ||
|
|
9e900d5885 | ||
|
|
38d3450160 | ||
|
|
2c5bfec089 | ||
|
|
a56a2b0057 | ||
|
|
89224401a9 | ||
|
|
1c17c8661a | ||
|
|
4ed65891c2 | ||
|
|
49e131ce21 | ||
|
|
06f4b017d6 | ||
|
|
f52450aef4 | ||
|
|
6476843e95 | ||
|
|
67d11dffb9 | ||
|
|
58dbb01e76 | ||
|
|
81e9a483bc | ||
|
|
5688f792cb | ||
|
|
3ffc04be4d | ||
|
|
55e586c2c6 | ||
|
|
76b535a2e4 | ||
|
|
6e27f97bc9 | ||
|
|
d9ada8d94e | ||
|
|
b3d67c3ed4 | ||
|
|
459b0dc5bd | ||
|
|
aefd95890e | ||
|
|
36d66494d3 | ||
|
|
a8ba77f40c | ||
|
|
0c0ca895a8 | ||
|
|
3d35595397 | ||
|
|
533e176566 | ||
|
|
915f2a4914 | ||
|
|
d21bd796f8 | ||
|
|
51186397d7 | ||
|
|
e2dd201e4a | ||
|
|
f0c8b70f47 | ||
|
|
d259f14267 | ||
|
|
3b6bf80ea4 | ||
|
|
0e4dc5a888 | ||
|
|
fc713be9ce | ||
|
|
a6df1a9320 | ||
|
|
62edf0d266 | ||
|
|
2ee2266865 | ||
|
|
0c1df4250e | ||
|
|
61d6654547 | ||
|
|
1170493ebe | ||
|
|
1742c03b23 | ||
|
|
00777ea14a | ||
|
|
0524427a3b | ||
|
|
8a469863d4 | ||
|
|
73b96ef4ca | ||
|
|
ad4c0ab5b6 | ||
|
|
203b8df7f8 | ||
|
|
185d713a0e | ||
|
|
a089cd6e35 | ||
|
|
8101f01de9 | ||
|
|
700b6d9450 | ||
|
|
b0d016f5e1 | ||
|
|
c3ceb90982 | ||
|
|
0097e5fecd | ||
|
|
cd5b17b002 |
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
|
||||
5
.travis.yml
Normal file
5
.travis.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_script:
|
||||
- npm install -g grunt-cli
|
||||
54
README.md
54
README.md
@@ -1,6 +1,8 @@
|
||||
# Grafana - Graphite Dashboard
|
||||
[Grafana](http://grafana.org) [](https://travis-ci.org/torkelo/grafana)
|
||||
=================
|
||||
A beautiful, easy to use and feature rich Graphite dashboard replacement and graph editor. Visit [grafana.org](http://grafana.org) for screenshots and an overview.
|
||||
|
||||
A beautifully, easy to use and feature rich Graphite dashboard replacement and graph editor. Visit [grafana.org](http://grafana.org) for screenshots and feature lists.
|
||||

|
||||
|
||||
# Features
|
||||
## Graphite Target Editor
|
||||
@@ -11,7 +13,7 @@ A beautifully, easy to use and feature rich Graphite dashboard replacement and g
|
||||
- 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.
|
||||
@@ -21,7 +23,7 @@ A beautifully, easy to use and feature rich Graphite dashboard replacement and g
|
||||
- Smart Y-axis formating
|
||||
- Series toggles & color selector
|
||||
- Axis labels
|
||||
- Fullscreen views and more...
|
||||
- Fullscreen views and more...
|
||||
|
||||
## Dashboards
|
||||
- Create and edit dashboards
|
||||
@@ -31,15 +33,25 @@ A beautifully, easy to use and feature rich Graphite dashboard replacement and g
|
||||
- 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.
|
||||
|
||||
# Installation
|
||||
- Download and extract the [latest release](https://github.com/asimov-deploy/asimov-deploy-winagent/releases/latest).
|
||||
- Edit config.js , the change graphiteUrl and elasticsearch to the correct urls. The urls entered here must be reachable by your browser.
|
||||
- Point your browser to the installation.
|
||||
- Download and extract the [latest release](https://github.com/torkelo/grafana/releases).
|
||||
- Edit config.js, then change graphiteUrl and elasticsearch to point to the correct urls. The urls entered here must be reachable by your browser.
|
||||
- Point your browser to the installation.
|
||||
|
||||
To run from master:
|
||||
- Clone this repository
|
||||
- Start a web server in src folder
|
||||
- Or create a optimized & minified build:
|
||||
- npm install (requires nodejs)
|
||||
- grunt build
|
||||
|
||||
When you have Grafana up an running, read the [Getting started](https://github.com/torkelo/grafana/wiki/Getting-started) guide for
|
||||
an introduction on how to use Grafana.
|
||||
|
||||
# Graphite server config
|
||||
If you haven't used an alternative dashboard for graphite before you need to enable cross-domain origin request. For Apache 2.x:
|
||||
@@ -47,10 +59,14 @@ If you haven't used an alternative dashboard for graphite before you need to ena
|
||||
Header set Access-Control-Allow-Origin "*"
|
||||
Header set Access-Control-Allow-Methods "GET, OPTIONS"
|
||||
Header set Access-Control-Allow-Headers "origin, authorization, accept"
|
||||
```
|
||||
|
||||
If your Graphite web is proteced by basic authentication, you have to enable the HTTP verb OPTIONS. This looks like the following for Apache:
|
||||
```
|
||||
|
||||
If your Graphite web is proteced by basic authentication, you have to enable the HTTP verb OPTIONS, origin
|
||||
(no wildcards are allowed in this case) and add Access-Control-Allow-Credentials. This looks like the following for Apache:
|
||||
```
|
||||
Header set Access-Control-Allow-Origin "http://mygrafana.com:5656"
|
||||
Header set Access-Control-Allow-Credentials true
|
||||
|
||||
<Location />
|
||||
AuthName "graphs restricted"
|
||||
AuthType Basic
|
||||
@@ -65,18 +81,22 @@ If your Graphite web is proteced by basic authentication, you have to enable the
|
||||
- Improve and refine the target parser and editing
|
||||
- Improve graphite import feature
|
||||
- Refine and simplify common tasks
|
||||
- More panel types (not just graphs)
|
||||
- More panel types (not just graphs)
|
||||
- Use elasticsearch to search for metrics
|
||||
- Improve template support
|
||||
- Add support for other time series databases like InfluxDB
|
||||
- Improve template support
|
||||
- Annotate graph by querying ElasticSearch for events (or other event sources)
|
||||
- Add support for other time series databases like InfluxDB
|
||||
|
||||
# Contribute
|
||||
If you have any idea for improvement or found a bug do not hesitate to open an issue. And if you have time clone this repo and submit a pull request and help me make Grafana the kickass metrics & devops dashboard we all dream about!
|
||||
If you have any idea for an improvement or found a bug do not hesitate to open an issue. And if you have time clone this repo and submit a pull request and help me make Grafana the kickass metrics & devops dashboard we all dream about!
|
||||
|
||||

|
||||
Clone repository:
|
||||
- npm install
|
||||
- grunt server (starts development web server in src folder)
|
||||
- grunt (runs jshint and less -> css compilation)
|
||||
|
||||
# Notice
|
||||
This software is based on the great log dashboard [kibana](https://github.com/elasticsearch/kibana).
|
||||
|
||||
# License
|
||||
Grafana is distributed under Apache 2.0 License.
|
||||
Grafana is distributed under Apache 2.0 License.
|
||||
12
package.json
12
package.json
@@ -4,7 +4,7 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "0.1",
|
||||
"version": "1.2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/torkelo/grafana.git"
|
||||
@@ -43,7 +43,15 @@
|
||||
"karma": "~0.10.9",
|
||||
"grunt-karma": "~0.6.2",
|
||||
"karma-mocha": "~0.1.1",
|
||||
"karma-expect": "~1.0.0"
|
||||
"karma-expect": "~1.0.0",
|
||||
"grunt-cli": "~0.1.13"
|
||||
},
|
||||
"engines": {
|
||||
"node": "0.10.x",
|
||||
"npm": "1.2.x"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt"
|
||||
},
|
||||
"license": "Apache License"
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -4,94 +4,6 @@ function($, _, moment) {
|
||||
|
||||
var kbn = {};
|
||||
|
||||
kbn.get_object_fields = function(obj) {
|
||||
var field_array = [];
|
||||
obj = kbn.flatten_json(obj._source);
|
||||
for (var field in obj) {
|
||||
field_array.push(field);
|
||||
}
|
||||
return field_array.sort();
|
||||
};
|
||||
|
||||
kbn.get_all_fields = function(data,flat) {
|
||||
return _.uniq(_.without(_.reduce(data,function(memo,hit) {
|
||||
return flat ? memo.concat(_.keys(kbn.flatten_json(hit._source))) : memo.concat(_.keys(hit._source));
|
||||
},[]),'$$hashkey'));
|
||||
};
|
||||
|
||||
kbn.has_field = function(obj,field) {
|
||||
var obj_fields = kbn.get_object_fields(obj);
|
||||
if (_.inArray(obj_fields,field) < 0) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
kbn.get_related_fields = function(docs,field) {
|
||||
var field_array = [];
|
||||
_.each(docs, function(doc) {
|
||||
var keys = _.keys(doc);
|
||||
if(_.contains(keys,field)) {
|
||||
field_array = field_array.concat(keys);
|
||||
}
|
||||
});
|
||||
var counts = _.countBy(_.without(field_array,field),function(field){return field;});
|
||||
return _.map(counts, function(num, key){return {name:key,count:num};});
|
||||
};
|
||||
|
||||
kbn.recurse_field_dots = function(object,field) {
|
||||
var value = null;
|
||||
var nested;
|
||||
if (typeof object[field] !== 'undefined') {
|
||||
value = object[field];
|
||||
}
|
||||
else if (nested = field.match(/(.*?)\.(.*)/)) {
|
||||
if(typeof object[nested[1]] !== 'undefined') {
|
||||
value = (typeof object[nested[1]][nested[2]] !== 'undefined') ?
|
||||
object[nested[1]][nested[2]] : kbn.recurse_field_dots(
|
||||
object[nested[1]],nested[2]);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
kbn.top_field_values = function(docs,field,count,grouped) {
|
||||
var all_values = _.pluck(docs,field),
|
||||
groups = {},
|
||||
counts,
|
||||
hasArrays;
|
||||
// manually grouping into pairs allows us to keep the original value,
|
||||
_.each(all_values, function (value) {
|
||||
var k;
|
||||
if(_.isArray(value)) {
|
||||
hasArrays = true;
|
||||
}
|
||||
if(_.isArray(value) && !grouped) {
|
||||
k = value;
|
||||
} else {
|
||||
k = _.isUndefined(value) ? '' : [value.toString()];
|
||||
}
|
||||
_.each(k, function(key) {
|
||||
if (_.has(groups, key)) {
|
||||
groups[key][1] ++;
|
||||
} else {
|
||||
groups[key] = [(grouped ? value : key), 1];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
counts = _.values(groups).sort(function(a, b) {
|
||||
return a[1] - b[1];
|
||||
}).reverse().slice(0,count);
|
||||
|
||||
return {
|
||||
counts: counts,
|
||||
hasArrays : hasArrays
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate a graph interval
|
||||
*
|
||||
@@ -593,5 +505,17 @@ function($, _, moment) {
|
||||
return (size.toFixed(decimals) + ext);
|
||||
};
|
||||
|
||||
kbn.msFormat = function(size) {
|
||||
if (size < 1000) {
|
||||
return size.toFixed(0) + " ms";
|
||||
}
|
||||
else if (size < 60000) {
|
||||
return (size / 1000).toFixed(1) + " s";
|
||||
}
|
||||
else {
|
||||
return (size / 60000).toFixed(1) + " min";
|
||||
}
|
||||
};
|
||||
|
||||
return kbn;
|
||||
});
|
||||
@@ -5,7 +5,7 @@ require.config({
|
||||
baseUrl: 'app',
|
||||
// urlArgs: 'r=@REV@',
|
||||
paths: {
|
||||
config: '../config',
|
||||
config: ['../config', '../config.sample'],
|
||||
settings: 'components/settings',
|
||||
kbn: 'components/kbn',
|
||||
|
||||
@@ -21,6 +21,8 @@ require.config({
|
||||
timepicker: '../vendor/angular/timepicker',
|
||||
datepicker: '../vendor/angular/datepicker',
|
||||
bindonce: '../vendor/angular/bindonce',
|
||||
crypto: '../vendor/crypto.min',
|
||||
spectrum: '../vendor/spectrum',
|
||||
|
||||
underscore: 'components/underscore.extended',
|
||||
'underscore-src': '../vendor/underscore',
|
||||
@@ -43,12 +45,22 @@ 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'
|
||||
},
|
||||
|
||||
angular: {
|
||||
deps: ['jquery','config'],
|
||||
exports: 'angular'
|
||||
@@ -94,6 +106,7 @@ require.config({
|
||||
|
||||
elasticjs: ['angular', '../vendor/elasticjs/elastic'],
|
||||
|
||||
'bootstrap-tagsinput': ['jquery'],
|
||||
},
|
||||
waitSeconds: 60,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
define(['underscore'],
|
||||
function (_) {
|
||||
define([
|
||||
'underscore',
|
||||
'crypto',
|
||||
],
|
||||
function (_, crypto) {
|
||||
"use strict";
|
||||
|
||||
return function Settings (options) {
|
||||
@@ -10,11 +13,13 @@ function (_) {
|
||||
* @type {Object}
|
||||
*/
|
||||
var defaults = {
|
||||
elasticsearch : "http://"+window.location.hostname+":9200",
|
||||
graphiteUrl : "http://"+window.location.hostname+":8080",
|
||||
panel_names : [],
|
||||
default_route : '/dashboard/file/default.json',
|
||||
grafana_index : 'grafana-dash'
|
||||
elasticsearch : "http://"+window.location.hostname+":9200",
|
||||
graphiteUrl : "http://"+window.location.hostname+":8080",
|
||||
panel_names : [],
|
||||
default_route : '/dashboard/file/default.json',
|
||||
grafana_index : 'grafana-dash',
|
||||
elasticsearch_all_disabled : false,
|
||||
timezoneOffset : null,
|
||||
};
|
||||
|
||||
// This initializes a new hash on purpose, to avoid adding parameters to
|
||||
@@ -24,6 +29,19 @@ function (_) {
|
||||
settings[key] = typeof options[key] !== 'undefined' ? options[key] : defaults[key];
|
||||
});
|
||||
|
||||
var basicAuth = function(url) {
|
||||
var passwordAt = url.indexOf('@');
|
||||
if (passwordAt > 0) {
|
||||
var userStart = url.indexOf('//') + 2;
|
||||
var userAndPassword = url.substring(userStart, passwordAt);
|
||||
var bytes = crypto.charenc.Binary.stringToBytes(userAndPassword);
|
||||
var base64 = crypto.util.bytesToBase64(bytes);
|
||||
return base64;
|
||||
}
|
||||
};
|
||||
|
||||
settings.graphiteBasicAuth = basicAuth(settings.graphiteUrl);
|
||||
settings.elasticsearchBasicAuth = basicAuth(settings.elasticsearch);
|
||||
return settings;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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, esVersion, 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;
|
||||
@@ -52,14 +54,13 @@ function (angular, $, config, _) {
|
||||
$scope._ = _;
|
||||
$scope.dashboard = dashboard;
|
||||
$scope.dashAlerts = alertSrv;
|
||||
$scope.esVersion = esVersion;
|
||||
|
||||
// Clear existing alerts
|
||||
alertSrv.clearAll();
|
||||
|
||||
$scope.reset_row();
|
||||
|
||||
$scope.ejs = ejsResource(config.elasticsearch);
|
||||
$scope.ejs = ejsResource(config.elasticsearch, config.elasticsearchBasicAuth);
|
||||
|
||||
$scope.bindKeyboardShortcuts();
|
||||
};
|
||||
@@ -73,6 +74,12 @@ function (angular, $, config, _) {
|
||||
$rootScope.fullscreen = false;
|
||||
});
|
||||
|
||||
$rootScope.$on('dashboard-saved', function() {
|
||||
if ($rootScope.fullscreen) {
|
||||
$rootScope.$emit('panel-fullscreen-exit');
|
||||
}
|
||||
});
|
||||
|
||||
keyboardManager.bind('ctrl+f', function(evt) {
|
||||
$rootScope.$emit('open-search', evt);
|
||||
}, { inputDisabled: true });
|
||||
|
||||
@@ -65,17 +65,18 @@ function (angular, _, moment) {
|
||||
type,
|
||||
($scope.elasticsearch.title || dashboard.current.title),
|
||||
($scope.loader.save_temp_ttl_enable ? ttl : false)
|
||||
).then(
|
||||
function(result) {
|
||||
if(!_.isUndefined(result._id)) {
|
||||
alertSrv.set('Dashboard Saved','This dashboard has been saved to Elasticsearch as "' +
|
||||
result._id + '"','success',5000);
|
||||
if(type === 'temp') {
|
||||
$scope.share = dashboard.share_link(dashboard.current.title,'temp',result._id);
|
||||
}
|
||||
} else {
|
||||
).then(function(result) {
|
||||
if(_.isUndefined(result._id)) {
|
||||
alertSrv.set('Save failed','Dashboard could not be saved to Elasticsearch','error',5000);
|
||||
return;
|
||||
}
|
||||
|
||||
alertSrv.set('Dashboard Saved', 'This dashboard has been saved to Elasticsearch as "' + result._id + '"','success', 5000);
|
||||
if(type === 'temp') {
|
||||
$scope.share = dashboard.share_link(dashboard.current.title,'temp',result._id);
|
||||
}
|
||||
|
||||
$rootScope.$emit('dashboard-saved');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -102,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) {
|
||||
|
||||
@@ -65,11 +65,13 @@ function (angular, app, _) {
|
||||
_.each(state.graphs, function(graph) {
|
||||
if (currentRow.panels.length === graphsPerRow) {
|
||||
currentRow = angular.copy(rowTemplate);
|
||||
newDashboard.rows.push(currentRow);
|
||||
}
|
||||
|
||||
panel = {
|
||||
type: 'graphite',
|
||||
span: 12 / graphsPerRow,
|
||||
title: graph[1].title,
|
||||
targets: []
|
||||
};
|
||||
|
||||
@@ -87,4 +89,4 @@ function (angular, app, _) {
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,8 @@ function (angular, _, config, gfunc, Parser) {
|
||||
parseTarget();
|
||||
};
|
||||
|
||||
// The way parsing and the target editor works needs
|
||||
// to be rewritten to handle functions that take multiple series
|
||||
function parseTarget() {
|
||||
$scope.functions = [];
|
||||
$scope.segments = [];
|
||||
@@ -72,7 +74,13 @@ function (angular, _, config, gfunc, Parser) {
|
||||
throw { message: 'invalid number of parameters to method ' + func.def.name };
|
||||
}
|
||||
|
||||
func.params[index - 1] = astNode.value;
|
||||
if (index === 0) {
|
||||
func.params[index] = astNode.value;
|
||||
}
|
||||
else {
|
||||
func.params[index - 1] = astNode.value;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'metric':
|
||||
@@ -141,16 +149,7 @@ function (angular, _, config, gfunc, Parser) {
|
||||
}
|
||||
|
||||
function wrapFunction(target, func) {
|
||||
var targetWrapped = func.def.name + '(' + target;
|
||||
_.each(func.params, function(param) {
|
||||
if (_.isString(param)) {
|
||||
targetWrapped += ",'" + param + "'";
|
||||
}
|
||||
else {
|
||||
targetWrapped += "," + param;
|
||||
}
|
||||
});
|
||||
return targetWrapped + ')';
|
||||
return func.render(target);
|
||||
}
|
||||
|
||||
$scope.getAltSegments = function (index) {
|
||||
|
||||
@@ -10,7 +10,13 @@ function (angular, _, config) {
|
||||
|
||||
module.controller('MetricKeysCtrl', function($scope, $http, $q) {
|
||||
var elasticSearchUrlForMetricIndex = config.elasticsearch + '/' + config.grafana_metrics_index + '/';
|
||||
|
||||
var httpOptions = {};
|
||||
if (config.elasticsearchBasicAuth) {
|
||||
httpOptions.withCredentials = true;
|
||||
httpOptions.headers = {
|
||||
"Authorization": "Basic " + config.elasticsearchBasicAuth
|
||||
};
|
||||
}
|
||||
$scope.init = function () {
|
||||
$scope.metricPath = "prod.apps.api.boobarella.*";
|
||||
$scope.metricCounter = 0;
|
||||
@@ -77,7 +83,7 @@ function (angular, _, config) {
|
||||
function deleteIndex()
|
||||
{
|
||||
var deferred = $q.defer();
|
||||
$http.delete(elasticSearchUrlForMetricIndex)
|
||||
$http.delete(elasticSearchUrlForMetricIndex, httpOptions)
|
||||
.success(function() {
|
||||
deferred.resolve('ok');
|
||||
})
|
||||
@@ -124,7 +130,7 @@ function (angular, _, config) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, httpOptions);
|
||||
}
|
||||
|
||||
function receiveMetric(result) {
|
||||
|
||||
@@ -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,31 +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.toggleImport = function ($event) {
|
||||
$event.stopPropagation();
|
||||
|
||||
$scope.showImport = !$scope.showImport;
|
||||
$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.elasticsearch_dblist = function(queryStr) {
|
||||
$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;
|
||||
}
|
||||
|
||||
@@ -87,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: [] };
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -101,7 +147,8 @@ function (angular, _, config, $) {
|
||||
}
|
||||
|
||||
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
|
||||
$scope.elasticsearch_dblist("");
|
||||
$scope.query.query = 'title:';
|
||||
$scope.search();
|
||||
};
|
||||
|
||||
$scope.addMetricToCurrentDashboard = function (metricId) {
|
||||
@@ -120,10 +167,17 @@ function (angular, _, config, $) {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleImport = function ($event) {
|
||||
$event.stopPropagation();
|
||||
$scope.showImport = !$scope.showImport;
|
||||
};
|
||||
|
||||
$scope.newDashboard = function() {
|
||||
$location.url('/dashboard/file/empty.json');
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
@@ -1,16 +1,154 @@
|
||||
{
|
||||
"title": "New Dashboard",
|
||||
"title": "Welcome to Grafana!",
|
||||
"services": {
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-5m",
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rows": [],
|
||||
"rows": [
|
||||
{
|
||||
"title": "Welcome to Grafana",
|
||||
"height": "150px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"error": false,
|
||||
"span": 12,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "markdown",
|
||||
"content": "####Thank you for trying out Grafana! \n\nGeneral documentation is found in the readme and in the wiki section of the [Github Project](https://github.com/torkelo/grafana). If you encounter any problem or have an idea for an improvement do not hesitate to open a github issue. \n\nTips: \n\n- Ctrl+S saves the current dashboard\n- Ctrl+F Opens the dashboard finder (searches elastic search)\n- Ctrl+H Hide/show row controls \n- Click and drag graph title to move panel (only works when row controls are enabled)\n\nIf you do not see a graph in the panel bellow the browser cannot access your graphite installation. Please make sure that the graphiteUrl property in config.js is correctly set and accessible.",
|
||||
"style": {},
|
||||
"title": "Welcome to Grafana"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "test",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 12,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": ["short", "short"],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 1,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": true,
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "randomWalk('random walk')"
|
||||
},
|
||||
{
|
||||
"target": "randomWalk('random walk2')"
|
||||
},
|
||||
{
|
||||
"target": "randomWalk('random walk3')"
|
||||
}
|
||||
],
|
||||
"aliasColors": {},
|
||||
"aliasYAxis": {},
|
||||
"title": "Graphite test"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
}
|
||||
],
|
||||
"editable": true,
|
||||
"failover": false,
|
||||
"panel_hints": true
|
||||
"panel_hints": true,
|
||||
"style": "dark",
|
||||
"pulldowns": [
|
||||
{
|
||||
"type": "filtering",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": false
|
||||
}
|
||||
],
|
||||
"nav": [
|
||||
{
|
||||
"type": "timepicker",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": true,
|
||||
"status": "Stable",
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
],
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"now": true
|
||||
}
|
||||
],
|
||||
"loader": {
|
||||
"save_gist": false,
|
||||
"save_elasticsearch": true,
|
||||
"save_local": true,
|
||||
"save_default": true,
|
||||
"save_temp": true,
|
||||
"save_temp_ttl_enable": true,
|
||||
"save_temp_ttl": "30d",
|
||||
"load_gist": false,
|
||||
"load_elasticsearch": true,
|
||||
"load_elasticsearch_size": 20,
|
||||
"load_local": false,
|
||||
"hide": false
|
||||
},
|
||||
"refresh": false
|
||||
}
|
||||
83
src/app/dashboards/empty.json
Normal file
83
src/app/dashboards/empty.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"title": "New Dashboard",
|
||||
"services": {
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [],
|
||||
"notice": false
|
||||
}
|
||||
],
|
||||
"editable": true,
|
||||
"failover": false,
|
||||
"panel_hints": true,
|
||||
"style": "dark",
|
||||
"pulldowns": [
|
||||
{
|
||||
"type": "filtering",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": false
|
||||
}
|
||||
],
|
||||
"nav": [
|
||||
{
|
||||
"type": "timepicker",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": true,
|
||||
"status": "Stable",
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
],
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"now": true
|
||||
}
|
||||
],
|
||||
"loader": {
|
||||
"save_gist": false,
|
||||
"save_elasticsearch": true,
|
||||
"save_local": true,
|
||||
"save_default": true,
|
||||
"save_temp": true,
|
||||
"save_temp_ttl_enable": true,
|
||||
"save_temp_ttl": "30d",
|
||||
"load_gist": false,
|
||||
"load_elasticsearch": true,
|
||||
"load_elasticsearch_size": 20,
|
||||
"load_local": false,
|
||||
"hide": false
|
||||
},
|
||||
"refresh": false
|
||||
}
|
||||
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, _) {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -8,6 +8,8 @@ define([
|
||||
'./ngModelOnBlur',
|
||||
'./tip',
|
||||
'./confirmClick',
|
||||
'./esVersion',
|
||||
'./configModal'
|
||||
'./configModal',
|
||||
'./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);
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -21,7 +21,6 @@ function (angular,_) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Create a temp scope so we can discard changes to it if needed
|
||||
var tmpScope = scope.$new();
|
||||
tmpScope.panel = angular.copy(scope.panel);
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
Only show an element if it meets an Elasticsearch version requirement
|
||||
*/
|
||||
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('esVersion', function(esVersion) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, elem, attr) {
|
||||
if(!esVersion.is(attr.esVersion)) {
|
||||
elem.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
348
src/app/directives/grafanaGraph.js
Normal file
348
src/app/directives/grafanaGraph.js
Normal file
@@ -0,0 +1,348 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'kbn',
|
||||
'moment',
|
||||
'underscore'
|
||||
],
|
||||
function (angular, $, kbn, moment, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.directives');
|
||||
|
||||
module.directive('grafanaGraph', function(filterSrv, $rootScope, dashboard) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
template: '<div> </div>',
|
||||
link: function(scope, elem) {
|
||||
var data, plot;
|
||||
var hiddenData = {};
|
||||
|
||||
scope.$on('refresh',function() {
|
||||
if (scope.otherPanelInFullscreenMode()) { return; }
|
||||
|
||||
scope.get_data();
|
||||
});
|
||||
|
||||
scope.$on('toggleLegend', function(e, alias) {
|
||||
if (hiddenData[alias]) {
|
||||
data.push(hiddenData[alias]);
|
||||
delete hiddenData[alias];
|
||||
}
|
||||
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Receive render events
|
||||
scope.$on('render',function(event, d) {
|
||||
data = d || data;
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function() {
|
||||
render_panel();
|
||||
});
|
||||
|
||||
function setElementHeight() {
|
||||
try {
|
||||
elem.css({ height: scope.height || scope.panel.height || scope.row.height });
|
||||
return true;
|
||||
} catch(e) { // IE throws errors sometimes
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
if (!data) { return; }
|
||||
if (scope.otherPanelInFullscreenMode()) { return; }
|
||||
if (!setElementHeight()) { return; }
|
||||
|
||||
if (_.isString(data)) {
|
||||
render_panel_as_graphite_png(data);
|
||||
return;
|
||||
}
|
||||
|
||||
var panel = scope.panel;
|
||||
|
||||
_.each(_.keys(scope.hiddenSeries), function(seriesAlias) {
|
||||
var dataSeries = _.find(data, function(series) {
|
||||
return series.info.alias === seriesAlias;
|
||||
});
|
||||
if (dataSeries) {
|
||||
hiddenData[dataSeries.info.alias] = dataSeries;
|
||||
data = _.without(data, dataSeries);
|
||||
}
|
||||
});
|
||||
|
||||
// Set barwidth based on specified interval
|
||||
var barwidth = kbn.interval_to_ms(scope.interval);
|
||||
|
||||
var stack = panel.stack ? true : null;
|
||||
|
||||
// Populate element
|
||||
var options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
stackpercent: panel.stack ? panel.percentage : false,
|
||||
stack: panel.percentage ? null : stack,
|
||||
lines: {
|
||||
show: panel.lines,
|
||||
zero: false,
|
||||
fill: panel.fill === 0 ? 0.001 : panel.fill/10,
|
||||
lineWidth: panel.linewidth,
|
||||
steps: panel.steppedLine
|
||||
},
|
||||
bars: {
|
||||
show: panel.bars,
|
||||
fill: 1,
|
||||
barWidth: barwidth/1.5,
|
||||
zero: false,
|
||||
lineWidth: 0
|
||||
},
|
||||
points: {
|
||||
show: panel.points,
|
||||
fill: 1,
|
||||
fillColor: false,
|
||||
radius: panel.pointradius
|
||||
},
|
||||
shadowSize: 1
|
||||
},
|
||||
yaxes: [],
|
||||
xaxis: {
|
||||
timezone: dashboard.current.timezone,
|
||||
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(),
|
||||
timeformat: time_format(scope.interval),
|
||||
label: "Datetime",
|
||||
ticks: elem.width()/100
|
||||
},
|
||||
grid: {
|
||||
backgroundColor: null,
|
||||
borderWidth: 0,
|
||||
hoverable: true,
|
||||
color: '#c8c8c8'
|
||||
},
|
||||
selection: {
|
||||
mode: "x",
|
||||
color: '#666'
|
||||
}
|
||||
};
|
||||
|
||||
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(panel.nullPointMode);
|
||||
data[i].data = _d;
|
||||
}
|
||||
|
||||
configureAxisOptions(data, options);
|
||||
|
||||
plot = $.plot(elem, data, options);
|
||||
|
||||
addAxisLabels();
|
||||
}
|
||||
|
||||
function render_panel_as_graphite_png(url) {
|
||||
url += '&width=' + elem.width();
|
||||
url += '&height=' + elem.css('height').replace('px', '');
|
||||
url += '&bgcolor=1f1f1f'; // @grayDarker & @kibanaPanelBackground
|
||||
url += '&fgcolor=BBBFC2'; // @textColor & @grayLighter
|
||||
url += scope.panel.stack ? '&areaMode=stacked' : '';
|
||||
url += scope.panel.fill !== 0 ? ('&areaAlpha=' + (scope.panel.fill/10).toFixed(1)) : '';
|
||||
url += scope.panel.linewidth !== 0 ? '&lineWidth=' + scope.panel.linewidth : '';
|
||||
url += scope.panel.legend ? '' : '&hideLegend=true';
|
||||
url += scope.panel.grid.min ? '&yMin=' + scope.panel.grid.min : '';
|
||||
url += scope.panel.grid.max ? '&yMax=' + scope.panel.grid.max : '';
|
||||
url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
|
||||
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
|
||||
|
||||
switch(scope.panel.y_formats[0]) {
|
||||
case 'bytes':
|
||||
url += '&yUnitSystem=binary';
|
||||
break;
|
||||
case 'short':
|
||||
url += '&yUnitSystem=si';
|
||||
break;
|
||||
case 'none':
|
||||
url += '&yUnitSystem=none';
|
||||
break;
|
||||
}
|
||||
|
||||
switch(scope.panel.nullPointMode) {
|
||||
case 'connected':
|
||||
url += '&lineMode=connected';
|
||||
break;
|
||||
case 'null':
|
||||
break; // graphite default lineMode
|
||||
case 'null as zero':
|
||||
url += "&drawNullAsZero=true";
|
||||
break;
|
||||
}
|
||||
|
||||
url += scope.panel.steppedLine ? '&lineMode=staircase' : '';
|
||||
|
||||
elem.html('<img src="' + url + '"></img>');
|
||||
}
|
||||
|
||||
function addAnnotations(options) {
|
||||
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() {
|
||||
if (scope.panel.leftYAxisLabel) {
|
||||
elem.css('margin-left', '10px');
|
||||
var yaxisLabel = $("<div class='axisLabel yaxisLabel'></div>")
|
||||
.text(scope.panel.leftYAxisLabel)
|
||||
.appendTo(elem);
|
||||
|
||||
yaxisLabel.css("margin-top", yaxisLabel.width() / 2 - 20);
|
||||
} else if (elem.css('margin-left')) {
|
||||
elem.css('margin-left', '');
|
||||
}
|
||||
}
|
||||
|
||||
function configureAxisOptions(data, options) {
|
||||
var defaults = {
|
||||
position: 'left',
|
||||
show: scope.panel['y-axis'],
|
||||
min: scope.panel.grid.min,
|
||||
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max,
|
||||
};
|
||||
|
||||
options.yaxes.push(defaults);
|
||||
|
||||
if (_.findWhere(data, {yaxis: 2})) {
|
||||
var secondY = _.clone(defaults);
|
||||
secondY.position = 'right';
|
||||
options.yaxes.push(secondY);
|
||||
configureAxisMode(options.yaxes[1], scope.panel.y_formats[1]);
|
||||
}
|
||||
|
||||
configureAxisMode(options.yaxes[0], scope.panel.y_formats[0]);
|
||||
}
|
||||
|
||||
function configureAxisMode(axis, format) {
|
||||
if (format === 'bytes') {
|
||||
axis.mode = "byte";
|
||||
}
|
||||
if (format === 'short') {
|
||||
axis.tickFormatter = function(val) {
|
||||
return kbn.shortFormat(val, 1);
|
||||
};
|
||||
}
|
||||
if (format === 'ms') {
|
||||
axis.tickFormatter = kbn.msFormat;
|
||||
}
|
||||
}
|
||||
|
||||
function time_format(interval) {
|
||||
var _int = kbn.interval_to_seconds(interval);
|
||||
if(_int >= 2628000) {
|
||||
return "%Y-%m";
|
||||
}
|
||||
if(_int >= 10000) {
|
||||
return "%m/%d";
|
||||
}
|
||||
if(_int >= 3600) {
|
||||
return "%m/%d %H:%M";
|
||||
}
|
||||
if(_int >= 700) {
|
||||
return "%a %H:%M";
|
||||
}
|
||||
|
||||
return "%H:%M";
|
||||
}
|
||||
|
||||
var $tooltip = $('<div>');
|
||||
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
var group, value, timestamp;
|
||||
if (item) {
|
||||
if (item.series.info.alias || scope.panel.tooltip.query_as_alias) {
|
||||
group = '<small style="font-size:0.9em;">' +
|
||||
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
|
||||
(item.series.info.alias || item.series.info.query)+
|
||||
'</small><br>';
|
||||
} else {
|
||||
group = kbn.query_color_dot(item.series.color, 15) + ' ';
|
||||
}
|
||||
value = (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') ?
|
||||
item.datapoint[1] - item.datapoint[2] :
|
||||
item.datapoint[1];
|
||||
if(item.series.info.y_format === 'bytes') {
|
||||
value = kbn.byteFormat(value, 2);
|
||||
}
|
||||
if(item.series.info.y_format === 'short') {
|
||||
value = kbn.shortFormat(value, 2);
|
||||
}
|
||||
if(item.series.info.y_format === 'ms') {
|
||||
value = kbn.msFormat(value);
|
||||
}
|
||||
timestamp = dashboard.current.timezone === 'browser' ?
|
||||
moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
|
||||
moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
|
||||
$tooltip
|
||||
.html(
|
||||
group + value + " @ " + timestamp
|
||||
)
|
||||
.place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
filterSrv.setTime({
|
||||
from : moment.utc(ranges.xaxis.from).toDate(),
|
||||
to : moment.utc(ranges.xaxis.to).toDate(),
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,45 +1,57 @@
|
||||
define([
|
||||
'angular'
|
||||
'angular',
|
||||
'jquery'
|
||||
],
|
||||
function (angular) {
|
||||
function (angular, $) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('kibana.directives')
|
||||
.directive('kibanaPanel', function($compile) {
|
||||
var container = '<div class="panelCont"></div>';
|
||||
|
||||
var editorTemplate =
|
||||
var container = '<div class="panel-container"></div>';
|
||||
var content = '<div class="panel-content"></div>';
|
||||
|
||||
'<div class="row-fluid panel-extra"><div class="panel-extra-container">' +
|
||||
var panelHeader =
|
||||
'<div class="panel-header">'+
|
||||
'<div class="row-fluid">' +
|
||||
'<div class="span12 alert-error panel-error" ng-hide="!panel.error">' +
|
||||
'<a class="close" ng-click="panel.error=false">×</a>' +
|
||||
'<i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}}' +
|
||||
'</div>' +
|
||||
'</div>\n' +
|
||||
|
||||
'<span class="row-button extra" ng-show="panelMeta.loading == true">' +
|
||||
'<span>'+
|
||||
'<div class="row-fluid panel-extra">' +
|
||||
'<div class="panel-extra-container">' +
|
||||
|
||||
'<span class="panel-loading" ng-show="panelMeta.loading == true">' +
|
||||
'<i class="icon-spinner icon-spin icon-large"></i>' +
|
||||
'</span>'+
|
||||
'</span>' +
|
||||
|
||||
'<span ng-if="panelMeta.menuItems" class="dropdown" ng-show="panel.title">' +
|
||||
'<span class="panel-text panel-title pointer" bs-dropdown="panelMeta.menuItems" tabindex="1" ' +
|
||||
'data-drag=true data-jqyoui-options="{revert: \'invalid\',helper:\'clone\'}"'+
|
||||
' jqyoui-draggable="'+
|
||||
'{'+
|
||||
'animate:false,'+
|
||||
'mutate:false,'+
|
||||
'index:{{$index}},'+
|
||||
'onStart:\'panelMoveStart\','+
|
||||
'onStop:\'panelMoveStop\''+
|
||||
'}" ng-model="row.panels" ' +
|
||||
'>' +
|
||||
'{{panel.title}}' +
|
||||
'</span>' +
|
||||
'</span>'+
|
||||
|
||||
'<span ng-if="!panelMeta.menuItems" class="panel-text panel-title" ng-show="panel.title">' +
|
||||
'{{panel.title}}' +
|
||||
'</span>'+
|
||||
'<span ng-if="panelMeta.menuItems" class="dropdown">' +
|
||||
'<span class="panel-text panel-title pointer" bs-dropdown="panelMeta.menuItems" tabindex="1" ' +
|
||||
'data-drag=true data-jqyoui-options="kbnJqUiDraggableOptions"'+
|
||||
' jqyoui-draggable="'+
|
||||
'{'+
|
||||
'animate:false,'+
|
||||
'mutate:false,'+
|
||||
'index:{{$index}},'+
|
||||
'onStart:\'panelMoveStart\','+
|
||||
'onStop:\'panelMoveStop\''+
|
||||
'}" ng-model="row.panels" ' +
|
||||
'>' +
|
||||
'{{panel.title || "No title"}}' +
|
||||
'</span>' +
|
||||
'</span>'+
|
||||
|
||||
'<span ng-if="!panelMeta.menuItems" config-modal class="panel-text panel-title pointer" ng-show="panel.title">' +
|
||||
'{{panel.title}}' +
|
||||
'</span>'+
|
||||
|
||||
'</div>'+
|
||||
'</div>\n'+
|
||||
'</div>';
|
||||
|
||||
'</div></div>';
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function($scope, elem, attr) {
|
||||
@@ -47,6 +59,14 @@ function (angular) {
|
||||
// load the module.js if we have any
|
||||
var newScope = $scope.$new();
|
||||
|
||||
$scope.kbnJqUiDraggableOptions = {
|
||||
revert: 'invalid',
|
||||
helper: function() {
|
||||
return $('<div style="width:200px;height:100px;background: rgba(100,100,100,0.50);"/>');
|
||||
},
|
||||
placeholder: 'keep'
|
||||
};
|
||||
|
||||
// compile the module and uncloack. We're done
|
||||
function loadModule($module) {
|
||||
$module.appendTo(elem);
|
||||
@@ -77,7 +97,9 @@ function (angular) {
|
||||
$controllers = $controllers.add($module.find('ngcontroller, [ng-controller], .ng-controller'));
|
||||
|
||||
if ($controllers.length) {
|
||||
$controllers.first().prepend(editorTemplate);
|
||||
$controllers.first().prepend(panelHeader);
|
||||
$controllers.first().find('.panel-header').nextAll().wrapAll(content);
|
||||
|
||||
$scope.require([
|
||||
'panels/'+nameAsPath+'/module'
|
||||
], function() {
|
||||
|
||||
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] === '*';
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,105 @@
|
||||
<div class="editor-row">
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Axes</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Left y-axis label</label>
|
||||
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-large" ng-model="panel.leftYAxisLabel">
|
||||
<label class="small">X-Axis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Right y-axis label</label>
|
||||
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-large" ng-model="panel.rightYAxisLabel">
|
||||
<label class="small">Y-Axis</label><input type="checkbox" ng-model="panel['y-axis']" ng-checked="panel['y-axis']" ng-change="render()">
|
||||
</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[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[1]" ng-options="f for f in ['none','short','bytes', 'ms']" ng-change="render()"></select>
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Left Y-axis label</label>
|
||||
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.leftYAxisLabel">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Right Y-axis label</label>
|
||||
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.rightYAxisLabel">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
|
||||
<div class="section">
|
||||
<h5>Grid</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min / <a ng-click="toggleGridMinMax('min')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.min)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.min" ng-change="render()" ng-model-onblur />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Max / <a ng-click="toggleGridMinMax('max')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.max)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.max" ng-change="render()" ng-model-onblur />
|
||||
</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">
|
||||
<label class="small">Show Legend</label><input type="checkbox" ng-model="panel.legend.show" ng-checked="panel.legend.show" ng-change="render();">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Include Values</label><input type="checkbox" ng-model="panel.legend.values" ng-checked="panel.legend.values" ng-change="render();">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" ng-if="panel.legend.values">
|
||||
<h5>Legend values</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min</label><input type="checkbox" ng-model="panel.legend.min" ng-checked="panel.legend.min" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Max</label><input type="checkbox" ng-model="panel.legend.max" ng-checked="panel.legend.max" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Current</label><input type="checkbox" ng-model="panel.legend.current" ng-checked="panel.legend.current" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Total</label><input type="checkbox" ng-model="panel.legend.total" ng-checked="panel.legend.total" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Avg</label><input type="checkbox" ng-model="panel.legend.avg" ng-checked="panel.legend.avg" ng-change="render();">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -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" />
|
||||
|
||||
@@ -72,7 +68,7 @@
|
||||
focus-me="segment.focus"
|
||||
ng-bind-html-unsafe="segment.html">
|
||||
</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<ul class="dropdown-menu scrollable" role="menu">
|
||||
<li ng-repeat="altSegment in altSegments" role="menuitem">
|
||||
<a href="javascript:void(0)" tabindex="1" ng-click="setSegment($index, $parent.$index)" ng-bind-html-unsafe="altSegment.html"></a>
|
||||
</li>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
<span ng-show="panel.legend"
|
||||
<span ng-show="panel.legend.show"
|
||||
ng-class="{'pull-right': series.yaxis === 2, 'hidden-series': hiddenSeries[series.alias]}"
|
||||
ng-repeat='series in legend'
|
||||
class="histogram-legend">
|
||||
<i class='icon-minus pointer'
|
||||
ng-style="{color: series.color}"
|
||||
ng-click="toggleSeries(series)">
|
||||
bs-popover="'colorPopup.html'"
|
||||
>
|
||||
</i>
|
||||
<span class='small histogram-legend-item'>
|
||||
<a bs-popover="'colorPopup.html'" data-unique="1" data-placement="{{series.yaxis === 2 ? 'bottomRight' : 'bottomLeft'}}">
|
||||
<a ng-click="toggleSeries(series)" data-unique="1" data-placement="{{series.yaxis === 2 ? 'bottomRight' : 'bottomLeft'}}">
|
||||
{{series.alias}}
|
||||
</a>
|
||||
<span ng-if="panel.legend.values">
|
||||
<span ng-show="panel.legend.current">
|
||||
Current: {{series.current}}
|
||||
</span>
|
||||
<span ng-show="panel.legend.min">
|
||||
Min: {{series.min}}
|
||||
</span>
|
||||
<span ng-show="panel.legend.max">
|
||||
Max: {{series.max}}
|
||||
</span>
|
||||
<span ng-show="panel.legend.total">
|
||||
Total: {{series.total}}
|
||||
</span>
|
||||
<span ng-show="panel.legend.avg">
|
||||
Avg: {{series.avg}}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<center><img ng-show='panel.loading && _.isUndefined(data)' src="img/load_big.gif"></center>
|
||||
|
||||
<div histogram-chart class="pointer histogram-chart" params="{{panel}}">
|
||||
<div grafana-graph class="pointer histogram-chart">
|
||||
</div>
|
||||
|
||||
<div ng-if="panel.legend" class="grafana-legend-container">
|
||||
@@ -24,9 +24,11 @@
|
||||
<div ng-repeat="tab in editorTabs" data-title="{{tab}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" ng-show="editorTabs[editor.index] == 'General'">
|
||||
<div ng-include src="'app/partials/panelgeneral.html'"></div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" ng-repeat="tab in panelMeta.fullEditorTabs" ng-show="editorTabs[editor.index] == tab.title">
|
||||
<div ng-include src="tab.src"></div>
|
||||
</div>
|
||||
|
||||
@@ -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 : [],
|
||||
@@ -46,19 +46,19 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
src:'app/panels/graphite/editor.html'
|
||||
},
|
||||
{
|
||||
title:'Axis labels',
|
||||
title:'Axes & Grid',
|
||||
src:'app/panels/graphite/axisEditor.html'
|
||||
},
|
||||
{
|
||||
title:'Style',
|
||||
title:'Display Styles',
|
||||
src:'app/panels/graphite/styleEditor.html'
|
||||
}
|
||||
],
|
||||
|
||||
menuItems: [
|
||||
{ text: 'View fullscreen', click: 'toggleFullscreen()' },
|
||||
{ text: 'Edit', click: 'openConfigureModal()' },
|
||||
{ text: 'Duplicate', click: 'duplicate()' },
|
||||
{ text: 'Edit', click: 'openConfigureModal()' },
|
||||
{ text: 'Fullscreen', click: 'toggleFullscreen()' },
|
||||
{ text: 'Duplicate', click: 'duplicate()' },
|
||||
{ text: 'Span', submenu: [
|
||||
{ text: '1', click: 'updateColumnSpan(1)' },
|
||||
{ text: '2', click: 'updateColumnSpan(2)' },
|
||||
@@ -82,6 +82,10 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
/** @scratch /panels/histogram/3
|
||||
* renderer:: sets client side (flot) or native graphite png renderer (png)
|
||||
*/
|
||||
renderer: 'flot',
|
||||
/** @scratch /panels/histogram/3
|
||||
* x-axis:: Show the x-axis
|
||||
*/
|
||||
@@ -95,53 +99,32 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
*/
|
||||
scale : 1,
|
||||
/** @scratch /panels/histogram/3
|
||||
* y_format:: 'none','bytes','short '
|
||||
* y_formats :: 'none','bytes','short', 'ms'
|
||||
*/
|
||||
y_format : 'none',
|
||||
y2_format : 'none',
|
||||
y_formats : ['short', 'short'],
|
||||
/** @scratch /panels/histogram/5
|
||||
* grid object:: Min and max y-axis values
|
||||
* grid.min::: Minimum y-axis value
|
||||
* grid.max::: Maximum y-axis value
|
||||
* grid.ma1::: Maximum y-axis value
|
||||
*/
|
||||
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)'
|
||||
},
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Annotations
|
||||
* annotate object:: A query can be specified, the results of which will be displayed as markers on
|
||||
* the chart. For example, for noting code deploys.
|
||||
* annotate.enable::: Should annotations, aka markers, be shown?
|
||||
* annotate.query::: Lucene query_string syntax query to use for markers.
|
||||
* annotate.size::: Max number of markers to show
|
||||
* annotate.field::: Field from documents to show
|
||||
* annotate.sort::: Sort array in format [field,order], For example [`@timestamp',`desc']
|
||||
*/
|
||||
|
||||
annotate : {
|
||||
enable : false,
|
||||
query : "*",
|
||||
size : 20,
|
||||
field : '_type',
|
||||
sort : ['_score','desc']
|
||||
},
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Interval options
|
||||
* auto_int:: Automatically scale intervals?
|
||||
*/
|
||||
auto_int : true,
|
||||
|
||||
/** @scratch /panels/histogram/3
|
||||
* resolution:: If auto_int is true, shoot for this many bars.
|
||||
*/
|
||||
resolution : 100,
|
||||
/** @scratch /panels/histogram/3
|
||||
* interval:: If auto_int is set to false, use this as the interval.
|
||||
*/
|
||||
interval : '5m',
|
||||
/** @scratch /panels/histogram/3
|
||||
* interval:: Array of possible intervals in the *View* selector. Example [`auto',`1s',`5m',`3h']
|
||||
*/
|
||||
intervals : ['auto','1s','1m','5m','10m','30m','1h','3h','12h','1d','1w','1y'],
|
||||
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Drawing options
|
||||
* lines:: Show line chart
|
||||
@@ -171,35 +154,20 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
* stack:: Stack multiple series
|
||||
*/
|
||||
stack : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* spyable:: Show inspect icon
|
||||
*/
|
||||
spyable : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* zoomlinks:: Show `Zoom Out' link
|
||||
*/
|
||||
zoomlinks : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* options:: Show quick view options section
|
||||
*/
|
||||
options : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* legend:: Display the legond
|
||||
*/
|
||||
legend : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* interactive:: Enable click-and-drag to zoom functionality
|
||||
*/
|
||||
interactive : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* legend_counts:: Show counts in legend
|
||||
*/
|
||||
legend_counts : true,
|
||||
legend: {
|
||||
show: true, // disable/enable legend
|
||||
values: false, // disable/enable legend values
|
||||
min: false,
|
||||
max: false,
|
||||
current: false,
|
||||
total: false,
|
||||
avg: false
|
||||
},
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Transformations
|
||||
* timezone:: Correct for browser timezone?. Valid values: browser, utc
|
||||
*/
|
||||
timezone : 'browser', // browser or utc
|
||||
/** @scratch /panels/histogram/3
|
||||
* percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple
|
||||
* queries
|
||||
@@ -211,6 +179,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
zerofill : true,
|
||||
|
||||
nullPointMode : 'connected',
|
||||
|
||||
steppedLine: false,
|
||||
|
||||
tooltip : {
|
||||
@@ -225,13 +194,26 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
};
|
||||
|
||||
_.defaults($scope.panel,_d);
|
||||
_.defaults($scope.panel.tooltip,_d.tooltip);
|
||||
_.defaults($scope.panel.annotate,_d.annotate);
|
||||
_.defaults($scope.panel.grid,_d.grid);
|
||||
_.defaults($scope.panel.tooltip, _d.tooltip);
|
||||
_.defaults($scope.panel.annotate, _d.annotate);
|
||||
_.defaults($scope.panel.grid, _d.grid);
|
||||
|
||||
// backward compatible stuff
|
||||
if (_.isBoolean($scope.panel.legend)) {
|
||||
$scope.panel.legend = { show: $scope.panel.legend };
|
||||
_.defaults($scope.panel.legend, _d.legend);
|
||||
}
|
||||
|
||||
if ($scope.panel.y_format) {
|
||||
$scope.panel.y_formats[0] = $scope.panel.y_format;
|
||||
delete $scope.panel.y_format;
|
||||
}
|
||||
if ($scope.panel.y2_format) {
|
||||
$scope.panel.y_formats[1] = $scope.panel.y2_format;
|
||||
delete $scope.panel.y2_format;
|
||||
}
|
||||
|
||||
$scope.init = function() {
|
||||
|
||||
// Hide view options by default
|
||||
$scope.fullscreen = false;
|
||||
$scope.options = false;
|
||||
@@ -243,20 +225,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.panel.tooltip.query_as_alias = true;
|
||||
|
||||
$scope.get_data();
|
||||
|
||||
};
|
||||
|
||||
$scope.set_interval = function(interval) {
|
||||
if(interval !== 'auto') {
|
||||
$scope.panel.auto_int = false;
|
||||
$scope.panel.interval = interval;
|
||||
} else {
|
||||
$scope.panel.auto_int = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.typeAheadSource = function () {
|
||||
return ["test", "asd", "testing2"];
|
||||
};
|
||||
|
||||
$scope.remove_panel_from_row = function(row, panel) {
|
||||
@@ -273,24 +241,17 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.interval_label = function(interval) {
|
||||
return $scope.panel.auto_int && interval === $scope.panel.interval ? interval+" (auto)" : interval;
|
||||
};
|
||||
|
||||
$scope.updateTimeRange = function () {
|
||||
var range = filterSrv.timeRange();
|
||||
var interval = filterSrv.timeRange();
|
||||
$scope.range = filterSrv.timeRange();
|
||||
$scope.rangeUnparsed = filterSrv.timeRange(false);
|
||||
|
||||
if ($scope.panel.auto_int) {
|
||||
if (range) {
|
||||
interval = kbn.secondsToHms(
|
||||
kbn.calculate_interval(range.from, range.to, $scope.panel.resolution, 0) / 1000
|
||||
);
|
||||
}
|
||||
$scope.interval = '10m';
|
||||
|
||||
if ($scope.range) {
|
||||
$scope.interval = kbn.secondsToHms(
|
||||
kbn.calculate_interval($scope.range.from, $scope.range.to, $scope.panel.resolution, 0) / 1000
|
||||
);
|
||||
}
|
||||
|
||||
$scope.interval = $scope.panel.interval = interval || '10m';
|
||||
$scope.range = range;
|
||||
};
|
||||
|
||||
$scope.colors = [
|
||||
@@ -320,11 +281,14 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.updateTimeRange();
|
||||
|
||||
var graphiteQuery = {
|
||||
range: $scope.range,
|
||||
range: $scope.rangeUnparsed,
|
||||
targets: $scope.panel.targets,
|
||||
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) {
|
||||
@@ -334,9 +298,15 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
$scope.receiveGraphiteData = function(results) {
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.legend = [];
|
||||
|
||||
// png renderer returns just a url
|
||||
if (_.isString(results)) {
|
||||
$scope.render(results);
|
||||
return;
|
||||
}
|
||||
|
||||
results = results.data;
|
||||
$scope.legend = [];
|
||||
var data = [];
|
||||
|
||||
_.each(results, function(targetData) {
|
||||
@@ -344,37 +314,30 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
var color = $scope.panel.aliasColors[alias] || $scope.colors[data.length];
|
||||
var yaxis = $scope.panel.aliasYAxis[alias] || 1;
|
||||
|
||||
var tsOpts = {
|
||||
interval: $scope.interval,
|
||||
start_date: $scope.range && $scope.range.from,
|
||||
end_date: $scope.range && $scope.range.to,
|
||||
};
|
||||
|
||||
var time_series = new timeSeries.ZeroFilled(tsOpts);
|
||||
|
||||
_.each(targetData.datapoints, function(valueArray) {
|
||||
if (valueArray[0]) {
|
||||
time_series.addValue(valueArray[1] * 1000, valueArray[0]);
|
||||
}
|
||||
});
|
||||
|
||||
var seriesInfo = {
|
||||
alias: alias,
|
||||
color: color,
|
||||
enable: true,
|
||||
yaxis: yaxis,
|
||||
y_format: $scope.panel.y_formats[yaxis - 1]
|
||||
};
|
||||
|
||||
$scope.legend.push(seriesInfo);
|
||||
|
||||
data.push({
|
||||
var series = new timeSeries.ZeroFilled({
|
||||
datapoints: targetData.datapoints,
|
||||
info: seriesInfo,
|
||||
time_series: time_series,
|
||||
});
|
||||
|
||||
$scope.legend.push(seriesInfo);
|
||||
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() {
|
||||
@@ -425,6 +388,10 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.enterFullscreenMode({edit: true});
|
||||
};
|
||||
|
||||
$scope.otherPanelInFullscreenMode = function() {
|
||||
return $rootScope.fullscreen && !$scope.fullscreen;
|
||||
};
|
||||
|
||||
$scope.render = function(data) {
|
||||
$scope.$emit('render', data);
|
||||
};
|
||||
@@ -438,7 +405,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.duplicate = function(addToRow) {
|
||||
addToRow = addToRow || $scope.row;
|
||||
var currentRowSpan = $scope.rowSpan(addToRow);
|
||||
if (currentRowSpan <= 8) {
|
||||
if (currentRowSpan <= 9) {
|
||||
addToRow.panels.push(angular.copy($scope.panel));
|
||||
}
|
||||
else {
|
||||
@@ -482,6 +449,11 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.toggleGridMinMax = function(key) {
|
||||
$scope.panel.grid[key] = _.toggle($scope.panel.grid[key], null, 0);
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.updateColumnSpan = function(span) {
|
||||
$scope.panel.span = span;
|
||||
$timeout($scope.render);
|
||||
@@ -489,268 +461,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
||||
|
||||
});
|
||||
|
||||
module.directive('histogramChart', function(filterSrv, $rootScope) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
template: '<div> </div>',
|
||||
link: function(scope, elem) {
|
||||
var data, plot;
|
||||
var hiddenData = {};
|
||||
|
||||
scope.$on('refresh',function() {
|
||||
if ($rootScope.fullscreen && !scope.fullscreen) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.get_data();
|
||||
});
|
||||
|
||||
scope.$on('toggleLegend', function(e, alias) {
|
||||
if (hiddenData[alias]) {
|
||||
data.push(hiddenData[alias]);
|
||||
delete hiddenData[alias];
|
||||
}
|
||||
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Receive render events
|
||||
scope.$on('render',function(event, d) {
|
||||
data = d || data;
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Re-render if the window is resized
|
||||
angular.element(window).bind('resize', function() {
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// IE doesn't work without this
|
||||
elem.css({height:scope.height || scope.row.height});
|
||||
|
||||
_.each(data, function(series) {
|
||||
series.label = series.info.alias;
|
||||
series.color = series.info.color;
|
||||
});
|
||||
|
||||
_.each(_.keys(scope.hiddenSeries), function(seriesAlias) {
|
||||
var dataSeries = _.find(data, function(series) {
|
||||
return series.info.alias === seriesAlias;
|
||||
});
|
||||
if (dataSeries) {
|
||||
hiddenData[dataSeries.info.alias] = dataSeries;
|
||||
data = _.without(data, dataSeries);
|
||||
}
|
||||
});
|
||||
|
||||
// Set barwidth based on specified interval
|
||||
var barwidth = kbn.interval_to_ms(scope.panel.interval);
|
||||
|
||||
var stack = scope.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,
|
||||
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
|
||||
},
|
||||
bars: {
|
||||
show: scope.panel.bars,
|
||||
fill: 1,
|
||||
barWidth: barwidth/1.5,
|
||||
zero: false,
|
||||
lineWidth: 0
|
||||
},
|
||||
points: {
|
||||
show: scope.panel.points,
|
||||
fill: 1,
|
||||
fillColor: false,
|
||||
radius: scope.panel.pointradius
|
||||
},
|
||||
shadowSize: 1
|
||||
},
|
||||
yaxes: [],
|
||||
xaxis: {
|
||||
timezone: scope.panel.timezone,
|
||||
show: scope.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(),
|
||||
timeformat: time_format(scope.panel.interval),
|
||||
label: "Datetime",
|
||||
ticks: elem.width()/100
|
||||
},
|
||||
grid: {
|
||||
backgroundColor: null,
|
||||
borderWidth: 0,
|
||||
hoverable: true,
|
||||
color: '#c8c8c8'
|
||||
}
|
||||
};
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
//xaxis: int // the x axis to attach events to
|
||||
};
|
||||
}
|
||||
|
||||
if(scope.panel.interactive) {
|
||||
options.selection = { mode: "x", color: '#666' };
|
||||
}
|
||||
|
||||
// when rendering stacked bars, we need to ensure each point that has data is zero-filled
|
||||
// so that the stacking happens in the proper order
|
||||
var required_times = [];
|
||||
if (data.length > 1) {
|
||||
required_times = Array.prototype.concat.apply([], _.map(data, function (query) {
|
||||
return query.time_series.getOrderedTimes();
|
||||
}));
|
||||
required_times = _.uniq(required_times.sort(function (a, b) {
|
||||
// decending numeric sort
|
||||
return a-b;
|
||||
}), true);
|
||||
}
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var _d = data[i].time_series.getFlotPairs(required_times, scope.panel.nullPointMode);
|
||||
data[i].yaxis = data[i].info.yaxis;
|
||||
data[i].data = _d;
|
||||
data[i].info.y_format = data[i].yaxis === 1 ? scope.panel.y_format : scope.panel.y2_format;
|
||||
}
|
||||
|
||||
configureAxisOptions(data, options);
|
||||
|
||||
plot = $.plot(elem, data, options);
|
||||
|
||||
if (scope.panel.leftYAxisLabel) {
|
||||
elem.css('margin-left', '10px');
|
||||
var yaxisLabel = $("<div class='axisLabel yaxisLabel'></div>")
|
||||
.text(scope.panel.leftYAxisLabel)
|
||||
.appendTo(elem);
|
||||
|
||||
yaxisLabel.css("margin-top", yaxisLabel.width() / 2 - 20);
|
||||
} else if (elem.css('margin-left')) {
|
||||
elem.css('margin-left', '');
|
||||
}
|
||||
}
|
||||
|
||||
function configureAxisOptions(data, options)
|
||||
{
|
||||
var defaults = {
|
||||
position: 'left',
|
||||
show: scope.panel['y-axis'],
|
||||
min: scope.panel.grid.min,
|
||||
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max
|
||||
};
|
||||
|
||||
options.yaxes.push(defaults);
|
||||
|
||||
if (_.findWhere(data, {yaxis: 2})) {
|
||||
var secondY = _.clone(defaults);
|
||||
secondY.position = 'right';
|
||||
options.yaxes.push(secondY);
|
||||
configureAxisMode(options.yaxes[1], scope.panel.y2_format);
|
||||
}
|
||||
|
||||
configureAxisMode(options.yaxes[0], scope.panel.y_format);
|
||||
}
|
||||
|
||||
function configureAxisMode(axis, format) {
|
||||
if (format === 'bytes') {
|
||||
axis.mode = "byte";
|
||||
}
|
||||
if (format === 'short') {
|
||||
axis.tickFormatter = function(val) {
|
||||
return kbn.shortFormat(val,0);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function time_format(interval) {
|
||||
var _int = kbn.interval_to_seconds(interval);
|
||||
if(_int >= 2628000) {
|
||||
return "%Y-%m";
|
||||
}
|
||||
if(_int >= 10000) {
|
||||
return "%Y-%m-%d";
|
||||
}
|
||||
if(_int >= 60) {
|
||||
return "%H:%M<br>%m-%d";
|
||||
}
|
||||
|
||||
return "%H:%M:%S";
|
||||
}
|
||||
|
||||
var $tooltip = $('<div>');
|
||||
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
var group, value, timestamp;
|
||||
if (item) {
|
||||
if (item.series.info.alias || scope.panel.tooltip.query_as_alias) {
|
||||
group = '<small style="font-size:0.9em;">' +
|
||||
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
|
||||
(item.series.info.alias || item.series.info.query)+
|
||||
'</small><br>';
|
||||
} else {
|
||||
group = kbn.query_color_dot(item.series.color, 15) + ' ';
|
||||
}
|
||||
value = (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') ?
|
||||
item.datapoint[1] - item.datapoint[2] :
|
||||
item.datapoint[1];
|
||||
if(item.series.info.y_format === 'bytes') {
|
||||
value = kbn.byteFormat(value,2);
|
||||
}
|
||||
if(item.series.info.y_format === 'short') {
|
||||
value = kbn.shortFormat(value,2);
|
||||
}
|
||||
timestamp = scope.panel.timezone === 'browser' ?
|
||||
moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
|
||||
moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
|
||||
$tooltip
|
||||
.html(
|
||||
group + value + " @ " + timestamp
|
||||
)
|
||||
.place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
filterSrv.setTime({
|
||||
from : moment.utc(ranges.xaxis.from).toDate(),
|
||||
to : moment.utc(ranges.xaxis.to).toDate(),
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Chart Options</h5>
|
||||
@@ -10,31 +12,8 @@
|
||||
<div class="editor-option">
|
||||
<label class="small">Points</label><input type="checkbox" ng-model="panel.points" ng-checked="panel.points" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Selectable</label><input type="checkbox" ng-model="panel.interactive" ng-checked="panel.interactive">
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h5>Axis</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">xAxis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">yAxis</label><input type="checkbox" ng-model="panel['y-axis']" ng-checked="panel['y-axis']" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.points">
|
||||
<label class="small">Point Radius</label>
|
||||
<select class="input-mini" ng-model="panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
|
||||
</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_format" ng-options="f for f in ['none','short','bytes']" 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.y2_format" ng-options="f for f in ['none','short','bytes']" ng-change="render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Line options</h5>
|
||||
<div class="editor-option" ng-show="panel.lines">
|
||||
@@ -45,12 +24,16 @@
|
||||
<label class="small">Line Width</label>
|
||||
<select class="input-mini" ng-model="panel.linewidth" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.points">
|
||||
<label class="small">Point Radius</label>
|
||||
<select class="input-mini" ng-model="panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Null point mode <tip>Define how null values should be drawn</tip></label>
|
||||
<select class="input-medium" ng-model="panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Stepped lines</label><input type="checkbox" ng-model="panel.steppedLine" ng-checked="panel.steppedLine" ng-change="render()">
|
||||
<label class="small">Staircase line</label><input type="checkbox" ng-model="panel.steppedLine" ng-checked="panel.steppedLine" ng-change="render()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
@@ -67,32 +50,17 @@
|
||||
<select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Legend<h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Legend</label><input type="checkbox" ng-model="panel.legend" ng-checked="panel.legend">
|
||||
</div>
|
||||
<div ng-show="panel.legend" class="editor-option">
|
||||
<label class="small">Query <tip>If no alias is set, show the query in the legend</tip></label><input type="checkbox" ng-model="panel.show_query" ng-checked="panel.show_query">
|
||||
</div>
|
||||
<div ng-show="panel.legend" class="editor-option">
|
||||
<label class="small">Counts</label><input type="checkbox" ng-model="panel.legend_counts" ng-checked="panel.legend_counts">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Grid<h5>
|
||||
<h5>Rendering</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min / <a href='' ng-click="panel.grid.min = _.toggle(panel.grid.min,null,0)">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.min)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.min"/>
|
||||
<label class="small">Flot <tip>client side</tip></label>
|
||||
<input type="radio" class="input-small" ng-model="panel.renderer" value="flot" ng-change="get_data()" />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Max / <a ref='' ng-click="panel.grid.max = _.toggle(panel.grid.max,null,0)">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.max)"></i></a></label>
|
||||
<input type="number" class="input-small" ng-model="panel.grid.max"/>
|
||||
<label class="small">Graphite PNG <tip>server side</tip></label>
|
||||
<input type="radio" class="input-small" ng-model="panel.renderer" value="png" ng-change="get_data()" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,229 +1,62 @@
|
||||
define([
|
||||
'underscore',
|
||||
'./interval'
|
||||
'underscore'
|
||||
],
|
||||
function (_, Interval) {
|
||||
function (_) {
|
||||
'use strict';
|
||||
|
||||
var ts = {};
|
||||
|
||||
// map compatable parseInt
|
||||
function base10Int(val) {
|
||||
return parseInt(val, 10);
|
||||
}
|
||||
|
||||
// trim the ms off of a time, but return it with empty ms.
|
||||
function getDatesTime(date) {
|
||||
return Math.floor(date.getTime() / 1000)*1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Certain graphs require 0 entries to be specified for them to render
|
||||
* properly (like the line graph). So with this we will caluclate all of
|
||||
* the expected time measurements, and fill the missing ones in with 0
|
||||
* @param {object} opts An object specifying some/all of the options
|
||||
*
|
||||
* OPTIONS:
|
||||
* @opt {string} interval The interval notion describing the expected spacing between
|
||||
* each data point.
|
||||
* @opt {date} start_date (optional) The start point for the time series, setting this and the
|
||||
* end_date will ensure that the series streches to resemble the entire
|
||||
* expected result
|
||||
* @opt {date} end_date (optional) The end point for the time series, see start_date
|
||||
*/
|
||||
ts.ZeroFilled = function (opts) {
|
||||
opts = _.defaults(opts, {
|
||||
interval: '10m',
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
});
|
||||
|
||||
// the expected differenece between readings.
|
||||
this.interval = new Interval(opts.interval);
|
||||
|
||||
// will keep all values here, keyed by their time
|
||||
this._data = {};
|
||||
this.start_time = opts.start_date && getDatesTime(opts.start_date);
|
||||
this.end_time = opts.end_date && getDatesTime(opts.end_date);
|
||||
this.opts = opts;
|
||||
this.datapoints = opts.datapoints;
|
||||
this.info = opts.info;
|
||||
this.label = opts.info.alias;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a row
|
||||
* @param {int} time The time for the value, in
|
||||
* @param {any} value The value at this time
|
||||
*/
|
||||
ts.ZeroFilled.prototype.addValue = function (time, value) {
|
||||
if (time instanceof Date) {
|
||||
time = getDatesTime(time);
|
||||
} else {
|
||||
time = base10Int(time);
|
||||
}
|
||||
if (!isNaN(time)) {
|
||||
this._data[time] = (_.isUndefined(value) ? 0 : value);
|
||||
}
|
||||
this._cached_times = null;
|
||||
};
|
||||
ts.ZeroFilled.prototype.getFlotPairs = function (fillStyle) {
|
||||
var result = [];
|
||||
|
||||
/**
|
||||
* Get an array of the times that have been explicitly set in the series
|
||||
* @param {array} include (optional) list of timestamps to include in the response
|
||||
* @return {array} An array of integer times.
|
||||
*/
|
||||
ts.ZeroFilled.prototype.getOrderedTimes = function (include) {
|
||||
var times = _.map(_.keys(this._data), base10Int);
|
||||
if (_.isArray(include)) {
|
||||
times = times.concat(include);
|
||||
}
|
||||
return _.uniq(times.sort(function (a, b) {
|
||||
// decending numeric sort
|
||||
return a - b;
|
||||
}), true);
|
||||
};
|
||||
this.color = this.info.color;
|
||||
this.yaxis = this.info.yaxis;
|
||||
|
||||
/**
|
||||
* return the rows in the format:
|
||||
* [ [time, value], [time, value], ... ]
|
||||
*
|
||||
* Heavy lifting is done by _get(Min|Default|All)FlotPairs()
|
||||
* @param {array} required_times An array of timestamps that must be in the resulting pairs
|
||||
* @return {array}
|
||||
*/
|
||||
ts.ZeroFilled.prototype.getFlotPairs = function (required_times, fillStyle) {
|
||||
var times = this.getOrderedTimes(required_times),
|
||||
strategy,
|
||||
pairs;
|
||||
this.info.total = 0;
|
||||
this.info.max = null;
|
||||
this.info.min = 212312321312;
|
||||
|
||||
if(fillStyle === 'null as zero') {
|
||||
strategy = this._getAllFlotPairs;
|
||||
} else if(fillStyle === 'null') {
|
||||
strategy = this._getNullFlotPairs;
|
||||
} else if(fillStyle === 'connected') {
|
||||
strategy = this._getNoZeroFlotPairs;
|
||||
} else {
|
||||
strategy = this._getMinFlotPairs;
|
||||
}
|
||||
|
||||
pairs = _.reduce(
|
||||
times, // what
|
||||
strategy, // how
|
||||
[], // where
|
||||
this // context
|
||||
);
|
||||
|
||||
// if the first or last pair is inside either the start or end time,
|
||||
// add those times to the series with null values so the graph will stretch to contain them.
|
||||
// Removing, flot 0.8.1's max/min params satisfy this
|
||||
/*
|
||||
if (this.start_time && (pairs.length === 0 || pairs[0][0] > this.start_time)) {
|
||||
pairs.unshift([this.start_time, null]);
|
||||
}
|
||||
if (this.end_time && (pairs.length === 0 || pairs[pairs.length - 1][0] < this.end_time)) {
|
||||
pairs.push([this.end_time, null]);
|
||||
}
|
||||
*/
|
||||
|
||||
return pairs;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Fill zero's on either side of the current time, unless there is already a measurement there or
|
||||
* we are looking at an edge.
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getMinFlotPairs = function (result, time, i, times) {
|
||||
var next, expected_next, prev, expected_prev;
|
||||
|
||||
// check for previous measurement
|
||||
if (i > 0) {
|
||||
prev = times[i - 1];
|
||||
expected_prev = this.interval.before(time);
|
||||
if (prev < expected_prev) {
|
||||
result.push([expected_prev, 0]);
|
||||
_.each(this.datapoints, function(valueArray) {
|
||||
var currentTime = valueArray[1];
|
||||
var currentValue = valueArray[0];
|
||||
if (currentValue === null) {
|
||||
if (fillStyle === 'connected') {
|
||||
return;
|
||||
}
|
||||
if (fillStyle === 'null as zero') {
|
||||
currentValue = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add the current time
|
||||
result.push([ time, this._data[time] || 0]);
|
||||
|
||||
// check for next measurement
|
||||
if (times.length > i) {
|
||||
next = times[i + 1];
|
||||
expected_next = this.interval.after(time);
|
||||
if (next > expected_next) {
|
||||
result.push([expected_next, 0]);
|
||||
if (_.isNumber(currentValue)) {
|
||||
this.info.total += currentValue;
|
||||
}
|
||||
|
||||
if (currentValue > this.info.max) {
|
||||
this.info.max = currentValue;
|
||||
}
|
||||
|
||||
if (currentValue < this.info.min) {
|
||||
this.info.min = currentValue;
|
||||
}
|
||||
|
||||
result.push([currentTime * 1000, currentValue]);
|
||||
}, this);
|
||||
|
||||
if (result.length) {
|
||||
this.info.avg = (this.info.total / result.length).toFixed(2);
|
||||
this.info.current = result[result.length-1][1];
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Fill zero's to the right of each time, until the next measurement is reached or we are at the
|
||||
* last measurement
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getAllFlotPairs = function (result, time, i, times) {
|
||||
var next, expected_next;
|
||||
|
||||
result.push([ times[i], this._data[times[i]] || 0 ]);
|
||||
next = times[i + 1];
|
||||
expected_next = this.interval.after(time);
|
||||
for(; times.length > i && next > expected_next; expected_next = this.interval.after(expected_next)) {
|
||||
result.push([expected_next, 0]);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Same as min, but fills with nulls
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getNullFlotPairs = function (result, time, i, times) {
|
||||
var next, expected_next, prev, expected_prev;
|
||||
|
||||
// check for previous measurement
|
||||
if (i > 0) {
|
||||
prev = times[i - 1];
|
||||
expected_prev = this.interval.before(time);
|
||||
if (prev < expected_prev) {
|
||||
result.push([expected_prev, null]);
|
||||
}
|
||||
}
|
||||
|
||||
// add the current time
|
||||
result.push([ time, this._data[time] || null]);
|
||||
|
||||
// check for next measurement
|
||||
if (times.length > i) {
|
||||
next = times[i + 1];
|
||||
expected_next = this.interval.after(time);
|
||||
if (next > expected_next) {
|
||||
result.push([expected_next, null]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* ** called as a reduce stragegy in getFlotPairs() **
|
||||
* Not fill zero's on either side of the current time, only the current time
|
||||
* @return {array} An array of points to plot with flot
|
||||
*/
|
||||
ts.ZeroFilled.prototype._getNoZeroFlotPairs = function (result, time) {
|
||||
|
||||
// add the current time
|
||||
if(this._data[time]){
|
||||
result.push([ time, this._data[time]]);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return ts;
|
||||
});
|
||||
@@ -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,23 +1,24 @@
|
||||
<!-- 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 class="row-fluid">
|
||||
<div class="row-fluid container" style="margin-top:10px; width:98%">
|
||||
<div>
|
||||
<div class="grafana-container container">
|
||||
<!-- Rows -->
|
||||
<div class="row-fluid kibana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.current.rows" ng-style="row_style(row)">
|
||||
<div class="kibana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.current.rows" ng-style="row_style(row)">
|
||||
<div class="row-control">
|
||||
<div class="row-fluid grafana-row" style="padding:0px;margin:0px;position:relative;">
|
||||
|
||||
<div class="row-close span12" ng-show="row.collapse" data-placement="bottom" >
|
||||
<div class="grafana-row" style="padding:0px;margin:0px;position:relative;">
|
||||
<div class="row-close" ng-show="row.collapse" data-placement="bottom" >
|
||||
<span class="row-button bgWarning" bs-modal="'app/partials/roweditor.html'" class="pointer">
|
||||
<i bs-tooltip="'Configure row'" data-placement="right" ng-show="row.editable" class="icon-cog pointer"></i>
|
||||
</span>
|
||||
@@ -43,33 +44,26 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row-fluid" style="padding-top:0px" ng-if="!row.collapse">
|
||||
|
||||
<div style="padding-top:0px" ng-if="!row.collapse">
|
||||
|
||||
<!-- Panels -->
|
||||
<div ng-repeat="(name, panel) in row.panels|filter:isPanel" ng-hide="panel.span == 0 || panel.hide" class="span{{panel.span}} panel nospace" style="min-height:{{row.height}}; position:relative" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:$index,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver(true)',onOut:'panelMoveOut'}">
|
||||
<!-- Error Panel -->
|
||||
<div class="row-fluid">
|
||||
<div class="span12 alert-error panel-error" ng-hide="!panel.error">
|
||||
<a class="close" ng-click="panel.error=false">×</a>
|
||||
<i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="(name, panel) in row.panels|filter:isPanel" ng-hide="panel.hide" class="panel nospace" ng-style="{'width':!panel.span?'100%':(panel.span/1.2)*10+'%'}" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:$index,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver(true)',onOut:'panelMoveOut'}" ng-class="{'dragInProgress':dashboard.panelDragging}">
|
||||
<!-- Content Panel -->
|
||||
<div class="row-fluid" style="position:relative" ng-class="{'dragInProgress':dashboard.panelDragging}" >
|
||||
<div style="position:relative">
|
||||
<kibana-panel type="panel.type" ng-cloak></kibana-panel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-hide="(12-rowSpan(row)) < 1 || !dashboard.current.panel_hints" class="panel span{{(12-rowSpan(row))}}" ng-class="{'dragInProgress':dashboard.panelDragging}" ng-style="{height:row.height}" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:row.panels.length,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver',onOut:'panelMoveOut'}">
|
||||
|
||||
<span bs-modal="'app/partials/roweditor.html'" ng-show="row.editable && !dashboard.panelDragging">
|
||||
<i ng-hide="rowSpan(row) == 0" class="pointer icon-plus-sign" ng-click="editor.index = 2" bs-tooltip="'Add a panel to this row'" data-placement="right"></i>
|
||||
<span ng-click="editor.index = 2" style="margin-top: 8px; margin-left: 3px" ng-show="rowSpan(row) == 0" class="btn btn-mini">Add panel to empty row</btn>
|
||||
</span>
|
||||
|
||||
<div ng-show="rowSpan(row) < 10 && dashboard.panelDragging" class="panel" style="margin:5px;width:30%;background:rgba(100,100,100,0.50)" ng-class="{'dragInProgress':dashboard.panelDragging}" ng-style="{height:row.height}" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:row.panels.length,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver',onOut:'panelMoveOut'}">
|
||||
</div>
|
||||
|
||||
<span bs-modal="'app/partials/roweditor.html'" ng-show="!dashboard.panelDragging && !dashboard.current.hideControls">
|
||||
<i ng-hide="rowSpan(row) >= 10" class="pointer icon-plus-sign" ng-click="editor.index = 2" bs-tooltip="'Add a panel to this row'" data-placement="right"></i>
|
||||
</span>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
<label class="small">Style</label><select class="input-small" ng-model="dashboard.current.style" ng-options="f for f in ['dark','light']"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small"> Editable </label><input type="checkbox" ng-model="dashboard.current.editable" ng-checked="dashboard.current.editable" />
|
||||
<label class="small">Time correction</label>
|
||||
<select ng-model="dashboard.current.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small"> Hints <tip>Show 'Add panel' hints in empty spaces</tip></label><input type="checkbox" ng-model="dashboard.current.panel_hints" ng-checked="dashboard.current.panel_hints" />
|
||||
@@ -29,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">
|
||||
@@ -132,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>
|
||||
@@ -140,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,5 +1,5 @@
|
||||
<div ng-controller="GraphiteImportCtrl" ng-init="init()">
|
||||
<h5>Import dashboards from graphite webb</h5>
|
||||
<h5>Import dashboards from graphite web</h5>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid" ng-if="editor.index == 0">
|
||||
<div class="span4">
|
||||
<div class="editor-row" ng-if="editor.index == 0">
|
||||
<div class="editor-option">
|
||||
<label class="small">Title</label><input type="text" class="input-medium" ng-model='row.title'></input>
|
||||
</div>
|
||||
<div class="span2">
|
||||
<div class="editor-option">
|
||||
<label class="small">Height</label><input type="text" class="input-mini" ng-model='row.height'></input>
|
||||
</div>
|
||||
<div class="span1">
|
||||
<div class="editor-option">
|
||||
<label class="small"> Editable </label><input type="checkbox" ng-model="row.editable" ng-checked="row.editable" />
|
||||
</div>
|
||||
<div class="span1">
|
||||
<div class="editor-option">
|
||||
<label class="small"> Collapsable </label><input type="checkbox" ng-model="row.collapsable" ng-checked="row.collapsable" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,15 +19,45 @@
|
||||
<ul class="dropdown-menu" id="grafana-search">
|
||||
<li ng-if="!showImport">
|
||||
<div class="grafana-search-panel">
|
||||
<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)" />
|
||||
<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"
|
||||
<div class="search-field-wrapper">
|
||||
<button class="btn btn-success pull-right" ng-click="toggleImport($event)">
|
||||
<i class="icon-download-alt"></i>
|
||||
Import
|
||||
</button>
|
||||
<button class="btn btn-success pull-right" ng-click="newDashboard()">
|
||||
<i class="icon-th-large"></i>
|
||||
New
|
||||
</button>
|
||||
<span class="position: relative;">
|
||||
<input type="text"
|
||||
placeholder="search dashboards, metrics, or graphs"
|
||||
xng-focus="giveSearchFocus"
|
||||
ng-keydown="keyDown($event)"
|
||||
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="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>
|
||||
@@ -42,19 +72,23 @@
|
||||
</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>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="pull-right" style="margin: 5px;">
|
||||
<a ng-click="toggleImport($event)">Import</a>
|
||||
</li>
|
||||
<!-- ng-show="dashboard.current.loader.load_gist || dashboard.current.loader.load_local" -->
|
||||
<li ng-if="showImport" style="margin: 20px;">
|
||||
<div class="editor-row">
|
||||
|
||||
@@ -5,7 +5,7 @@ define([
|
||||
'./timer',
|
||||
'./panelMove',
|
||||
'./graphite/graphiteSrv',
|
||||
'./esVersion',
|
||||
'./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,12 +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: {
|
||||
@@ -47,7 +49,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
};
|
||||
|
||||
// An elasticJS client to use
|
||||
var ejs = ejsResource(config.elasticsearch);
|
||||
var ejs = ejsResource(config.elasticsearch, config.elasticsearchBasicAuth);
|
||||
var gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
|
||||
|
||||
// Store a reference to this
|
||||
@@ -101,8 +103,8 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
$location.path(config.default_route);
|
||||
alertSrv.set('Saving to browser storage has been replaced',' with saving to Elasticsearch.'+
|
||||
' Click <a href="#/dashboard/local/deprecated">here</a> to load your old dashboard anyway.');
|
||||
} else if(!(_.isUndefined(window.localStorage.kibanaDashboardDefault))) {
|
||||
$location.path(window.localStorage.kibanaDashboardDefault);
|
||||
} else if(!(_.isUndefined(window.localStorage.grafanaDashboardDefault))) {
|
||||
$location.path(window.localStorage.grafanaDashboardDefault);
|
||||
} else {
|
||||
$location.path(config.default_route);
|
||||
}
|
||||
@@ -118,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;
|
||||
};
|
||||
|
||||
@@ -137,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() {
|
||||
@@ -196,7 +209,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
if(!_.isUndefined(window.localStorage['dashboard'])) {
|
||||
delete window.localStorage['dashboard'];
|
||||
}
|
||||
window.localStorage.kibanaDashboardDefault = route;
|
||||
window.localStorage.grafanaDashboardDefault = route;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -210,7 +223,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
|
||||
delete window.localStorage['dashboard'];
|
||||
}
|
||||
delete window.localStorage.kibanaDashboardDefault;
|
||||
delete window.localStorage.grafanaDashboardDefault;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -286,13 +299,21 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
};
|
||||
|
||||
this.elasticsearch_load = function(type,id) {
|
||||
return $http({
|
||||
var options = {
|
||||
url: config.elasticsearch + "/" + config.grafana_index + "/"+type+"/"+id+'?' + new Date().getTime(),
|
||||
method: "GET",
|
||||
transformResponse: function(response) {
|
||||
return renderTemplate(angular.fromJson(response)._source.dashboard, $routeParams);
|
||||
}
|
||||
}).error(function(data, status) {
|
||||
};
|
||||
if (config.elasticsearchBasicAuth) {
|
||||
options.withCredentials = true;
|
||||
options.headers = {
|
||||
"Authorization": "Basic " + config.elasticsearchBasicAuth
|
||||
};
|
||||
}
|
||||
return $http(options)
|
||||
.error(function(data, status) {
|
||||
if(status === 0) {
|
||||
alertSrv.set('Error',"Could not contact Elasticsearch at "+config.elasticsearch+
|
||||
". Please ensure that Elasticsearch is reachable from your system." ,'error');
|
||||
@@ -344,6 +365,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
user: 'guest',
|
||||
group: 'guest',
|
||||
title: save.title,
|
||||
tags: save.tags,
|
||||
dashboard: angular.toJson(save)
|
||||
});
|
||||
|
||||
@@ -377,22 +399,6 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
||||
);
|
||||
};
|
||||
|
||||
this.elasticsearch_list = function(query,count) {
|
||||
var request = ejs.Request().indices(config.grafana_index).types('dashboard');
|
||||
return request.query(
|
||||
ejs.QueryStringQuery(query || '*')
|
||||
).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;
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'config'
|
||||
],
|
||||
function (angular, _, config) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.services');
|
||||
|
||||
module.service('esVersion', function($http, alertSrv) {
|
||||
|
||||
this.versions = [];
|
||||
|
||||
// save a reference to this
|
||||
var self = this;
|
||||
|
||||
this.init = function() {
|
||||
getVersions();
|
||||
};
|
||||
|
||||
var getVersions = function() {
|
||||
var nodeInfo = $http({
|
||||
url: config.elasticsearch + '/_nodes',
|
||||
method: "GET"
|
||||
}).error(function(data, status) {
|
||||
if(status === 0) {
|
||||
alertSrv.set('Error',"Could not contact Elasticsearch at "+config.elasticsearch+
|
||||
". Please ensure that Elasticsearch is reachable from your system." ,'error');
|
||||
} else {
|
||||
alertSrv.set('Error',"Could not reach "+config.elasticsearch+"/_nodes. If you"+
|
||||
" are using a proxy, ensure it is configured correctly",'error');
|
||||
}
|
||||
});
|
||||
|
||||
return nodeInfo.then(function(p) {
|
||||
_.each(p.data.nodes, function(v) {
|
||||
self.versions.push(v.version.split('-')[0]);
|
||||
});
|
||||
self.versions = sortVersions(_.uniq(self.versions));
|
||||
});
|
||||
};
|
||||
|
||||
// Get the max version in this cluster
|
||||
this.max = function() {
|
||||
return _.last(self.versions);
|
||||
};
|
||||
|
||||
// Return the lowest version in the cluster
|
||||
this.min = function() {
|
||||
return _.first(self.versions);
|
||||
};
|
||||
|
||||
// Sort versions from lowest to highest
|
||||
var sortVersions = function(versions) {
|
||||
var _versions = _.clone(versions),
|
||||
_r = [];
|
||||
|
||||
while(_r.length < versions.length) {
|
||||
var _h = "0";
|
||||
/*jshint -W083 */
|
||||
_.each(_versions,function(v){
|
||||
if(self.compare(_h,v)) {
|
||||
_h = v;
|
||||
}
|
||||
});
|
||||
_versions = _.without(_versions,_h);
|
||||
_r.push(_h);
|
||||
}
|
||||
return _r.reverse();
|
||||
};
|
||||
|
||||
/*
|
||||
Takes a version string with one of the following optional comparison prefixes: >,>=,<.<=
|
||||
and evaluates if the cluster meets the requirement. If the prefix is omitted exact match
|
||||
is assumed
|
||||
*/
|
||||
this.is = function(equation) {
|
||||
var _v = equation,
|
||||
_cf;
|
||||
|
||||
if(_v.charAt(0) === '>') {
|
||||
_cf = _v.charAt(1) === '=' ? self.gte(_v.slice(2)) : self.gt(_v.slice(1));
|
||||
} else if (_v.charAt(0) === '<') {
|
||||
_cf = _v.charAt(1) === '=' ? self.lte(_v.slice(2)) : self.lt(_v.slice(1));
|
||||
} else {
|
||||
_cf = self.eq(_v);
|
||||
}
|
||||
|
||||
return _cf;
|
||||
};
|
||||
|
||||
// check if lowest version in cluster = `version`
|
||||
this.eq = function(version) {
|
||||
return version === self.min() ? true : false;
|
||||
};
|
||||
|
||||
// version > lowest version in cluster?
|
||||
this.gt = function(version) {
|
||||
return version === self.min() ? false : self.gte(version);
|
||||
};
|
||||
|
||||
// version < highest version in cluster?
|
||||
this.lt = function(version) {
|
||||
return version === self.max() ? false : self.lte(version);
|
||||
};
|
||||
|
||||
// Check if the lowest version in the cluster is >= to `version`
|
||||
this.gte = function(version) {
|
||||
return self.compare(version,self.min());
|
||||
};
|
||||
|
||||
// Check if the highest version in the cluster is <= to `version`
|
||||
this.lte = function(version) {
|
||||
return self.compare(self.max(),version);
|
||||
};
|
||||
|
||||
// Determine if a specific version is greater than or equal to another
|
||||
this.compare = function (required,installed) {
|
||||
var a = installed.split('.');
|
||||
var b = required.split('.');
|
||||
var i;
|
||||
|
||||
for (i = 0; i < a.length; ++i) {
|
||||
a[i] = Number(a[i]);
|
||||
}
|
||||
for (i = 0; i < b.length; ++i) {
|
||||
b[i] = Number(b[i]);
|
||||
}
|
||||
if (a.length === 2) {
|
||||
a[2] = 0;
|
||||
}
|
||||
|
||||
if (a[0] > b[0]){return true;}
|
||||
if (a[0] < b[0]){return false;}
|
||||
|
||||
if (a[1] > b[1]){return true;}
|
||||
if (a[1] < b[1]){return false;}
|
||||
|
||||
if (a[2] > b[2]){return true;}
|
||||
if (a[2] < b[2]){return false;}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
this.init();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -31,13 +31,6 @@ function (_) {
|
||||
defaultParams: [1],
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: "alias",
|
||||
category: categories.Special,
|
||||
params: [ { name: "alias", type: 'string' } ],
|
||||
defaultParams: ['alias']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: "holtWintersForecast",
|
||||
category: categories.Calculate,
|
||||
@@ -69,6 +62,20 @@ function (_) {
|
||||
category: categories.Combine,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: "alias",
|
||||
category: categories.Special,
|
||||
params: [ { name: "alias", type: 'string' } ],
|
||||
defaultParams: ['alias']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: "aliasSub",
|
||||
category: categories.Special,
|
||||
params: [ { name: "search", type: 'string' }, { name: "replace", type: 'string' } ],
|
||||
defaultParams: ['', '']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: "groupByNode",
|
||||
category: categories.Special,
|
||||
@@ -94,6 +101,40 @@ function (_) {
|
||||
defaultParams: [3]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'sortByName',
|
||||
category: categories.Special
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'aliasByMetric',
|
||||
category: categories.Special,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'randomWalk',
|
||||
category: categories.Special,
|
||||
params: [ { name: "name", type: "string", } ],
|
||||
defaultParams: ['randomWalk']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'countSeries',
|
||||
category: categories.Special
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'constantLine',
|
||||
category: categories.Special,
|
||||
params: [ { name: "value", type: "int", } ],
|
||||
defaultParams: [10]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'cactiStyle',
|
||||
category: categories.Special,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'scale',
|
||||
category: categories.Transform,
|
||||
@@ -101,16 +142,30 @@ function (_) {
|
||||
defaultParams: [1]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'offset',
|
||||
category: categories.Transform,
|
||||
params: [ { name: "amount", type: "int", } ],
|
||||
defaultParams: [10]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'integral',
|
||||
category: categories.Transform,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'derivate',
|
||||
name: 'derivative',
|
||||
category: categories.Transform,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'nonNegativeDerivative',
|
||||
category: categories.Transform,
|
||||
params: [ { name: "max value or 0", type: "int", } ],
|
||||
defaultParams: [0]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'timeShift',
|
||||
category: categories.Transform,
|
||||
@@ -121,8 +176,34 @@ 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({
|
||||
name: 'absolute',
|
||||
category: categories.Transform,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'averageAbove',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "n", type: "int", } ],
|
||||
defaultParams: [25]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'averageBelow',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "n", type: "int", } ],
|
||||
defaultParams: [25]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'highestCurrent',
|
||||
category: categories.Filter,
|
||||
params: [ { name: "count", type: "int" } ],
|
||||
defaultParams: [5]
|
||||
});
|
||||
|
||||
_.each(categories, function(funcList, catName) {
|
||||
@@ -135,6 +216,19 @@ function (_) {
|
||||
this.updateText();
|
||||
}
|
||||
|
||||
FuncInstance.prototype.render = function (metricExp) {
|
||||
var str = this.def.name + '(';
|
||||
var parameters = _.map(this.params, function(value) {
|
||||
return _.isString(value) ? "'" + value + "'" : value;
|
||||
});
|
||||
|
||||
if (metricExp !== undefined) {
|
||||
parameters.unshift(metricExp);
|
||||
}
|
||||
|
||||
return str + parameters.join(',') + ')';
|
||||
};
|
||||
|
||||
FuncInstance.prototype.updateText = function () {
|
||||
if (this.params.length === 0) {
|
||||
this.text = this.def.name + '()';
|
||||
@@ -166,4 +260,4 @@ function (_) {
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,31 +2,41 @@ define([
|
||||
'angular',
|
||||
'underscore',
|
||||
'jquery',
|
||||
'config'
|
||||
'config',
|
||||
'kbn',
|
||||
'moment'
|
||||
],
|
||||
function (angular, _, $, config) {
|
||||
function (angular, _, $, config, kbn, moment) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('kibana.services');
|
||||
|
||||
module.service('graphiteSrv', function($http, $q, filterSrv) {
|
||||
var graphiteRenderUrl = config.graphiteUrl + "/render";
|
||||
|
||||
this.query = function(options) {
|
||||
try {
|
||||
var graphOptions = {
|
||||
from: $.plot.formatDate(options.range.from, '%H%:%M_%Y%m%d'),
|
||||
until: $.plot.formatDate(options.range.to, '%H%:%M_%Y%m%d'),
|
||||
from: this.translateTime(options.range.from),
|
||||
until: this.translateTime(options.range.to),
|
||||
targets: options.targets,
|
||||
format: options.format,
|
||||
maxDataPoints: options.maxDataPoints
|
||||
};
|
||||
|
||||
var params = buildGraphitePostParams(graphOptions);
|
||||
var params = buildGraphiteParams(graphOptions);
|
||||
|
||||
return $http({
|
||||
if (options.format === 'png') {
|
||||
return $q.when(graphiteRenderUrl + '?' + params.join('&'));
|
||||
}
|
||||
|
||||
return doGraphiteRequest({
|
||||
method: 'POST',
|
||||
url: config.graphiteUrl + '/render/',
|
||||
url: graphiteRenderUrl,
|
||||
data: params.join('&'),
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(err) {
|
||||
@@ -34,6 +44,30 @@ function (angular, _, $, config) {
|
||||
}
|
||||
};
|
||||
|
||||
this.translateTime = function(date) {
|
||||
if (_.isString(date)) {
|
||||
if (date === 'now') {
|
||||
return 'now';
|
||||
}
|
||||
else if (date.indexOf('now') >= 0) {
|
||||
date = date.substring(3);
|
||||
date = date.replace('m', 'min');
|
||||
date = date.replace('M', 'mon');
|
||||
return date;
|
||||
}
|
||||
|
||||
date = kbn.parseDate(date);
|
||||
}
|
||||
|
||||
date = moment.utc(date).local();
|
||||
|
||||
if (config.timezoneOffset) {
|
||||
date = date.zone(config.timezoneOffset);
|
||||
}
|
||||
|
||||
return date.format('HH:mm_YYYYMMDD');
|
||||
};
|
||||
|
||||
this.match = function(targets, graphiteTargetStr) {
|
||||
var found = targets[0];
|
||||
|
||||
@@ -60,7 +94,7 @@ function (angular, _, $, config) {
|
||||
}
|
||||
|
||||
var url = config.graphiteUrl + '/metrics/find/?query=' + interpolated;
|
||||
return $http.get(url)
|
||||
return doGraphiteRequest({method: 'GET', url: url})
|
||||
.then(function(results) {
|
||||
return _.map(results.data, function(metric) {
|
||||
return {
|
||||
@@ -73,7 +107,7 @@ function (angular, _, $, config) {
|
||||
|
||||
this.listDashboards = function(query) {
|
||||
var url = config.graphiteUrl + '/dashboard/find/';
|
||||
return $http.get(url, {params: {query: query || ''}})
|
||||
return doGraphiteRequest({ method: 'GET', url: url, params: {query: query || ''} })
|
||||
.then(function(results) {
|
||||
return results.data.dashboards;
|
||||
});
|
||||
@@ -81,14 +115,26 @@ function (angular, _, $, config) {
|
||||
|
||||
this.loadDashboard = function(dashName) {
|
||||
var url = config.graphiteUrl + '/dashboard/load/' + encodeURIComponent(dashName);
|
||||
return $http.get(url);
|
||||
return doGraphiteRequest({method: 'GET', url: url});
|
||||
};
|
||||
|
||||
function buildGraphitePostParams(options) {
|
||||
function doGraphiteRequest(options) {
|
||||
if (config.graphiteBasicAuth) {
|
||||
options.withCredentials = true;
|
||||
options.headers = options.headers || {};
|
||||
options.headers.Authorization = 'Basic ' + config.graphiteBasicAuth;
|
||||
}
|
||||
|
||||
return $http(options);
|
||||
}
|
||||
|
||||
function buildGraphiteParams(options) {
|
||||
var clean_options = [];
|
||||
var graphite_options = ['target', 'targets', 'from', 'until', 'rawData', 'format', 'maxDataPoints'];
|
||||
|
||||
options['format'] = 'json';
|
||||
if (options.format !== 'png') {
|
||||
options['format'] = 'json';
|
||||
}
|
||||
|
||||
$.each(options, function (key, value) {
|
||||
if ($.inArray(key, graphite_options) === -1) {
|
||||
@@ -112,4 +158,4 @@ function (angular, _, $, config) {
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,6 +116,7 @@ define([
|
||||
|
||||
for (var i = 0; i < 128; i++) {
|
||||
identifierStartTable[i] =
|
||||
i >= 48 && i <= 57 || // 0-9
|
||||
i === 36 || // $
|
||||
i >= 65 && i <= 90 || // A-Z
|
||||
i === 95 || // _
|
||||
@@ -183,10 +184,10 @@ define([
|
||||
}
|
||||
|
||||
match =
|
||||
this.scanIdentifier() ||
|
||||
this.scanTemplateSequence() ||
|
||||
this.scanPunctuator() ||
|
||||
this.scanNumericLiteral();
|
||||
this.scanNumericLiteral() ||
|
||||
this.scanIdentifier() ||
|
||||
this.scanTemplateSequence();
|
||||
|
||||
if (match) {
|
||||
this.skip(match.value.length);
|
||||
@@ -401,14 +402,20 @@ define([
|
||||
(ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z");
|
||||
}
|
||||
|
||||
// Numbers must start either with a decimal digit or a point.
|
||||
// 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,
|
||||
|
||||
@@ -29,12 +29,47 @@ define([
|
||||
}
|
||||
},
|
||||
|
||||
metricSegment: function() {
|
||||
if (this.match('identifier')) {
|
||||
this.index++;
|
||||
curlyBraceSegment: function() {
|
||||
if (this.match('identifier', '{') || this.match('{')) {
|
||||
|
||||
var curlySegment = "";
|
||||
|
||||
while(!this.match('') && !this.match('}')) {
|
||||
curlySegment += this.consumeToken().value;
|
||||
}
|
||||
|
||||
if (!this.match('}')) {
|
||||
this.errorMark("Expected closing '}'");
|
||||
}
|
||||
|
||||
curlySegment += this.consumeToken().value;
|
||||
|
||||
// if curly segment is directly followed by identifier
|
||||
// include it in the segment
|
||||
if (this.match('identifier')) {
|
||||
curlySegment += this.consumeToken().value;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'segment',
|
||||
value: this.tokens[this.index-1].value
|
||||
value: curlySegment
|
||||
};
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
metricSegment: function() {
|
||||
var curly = this.curlyBraceSegment();
|
||||
if (curly) {
|
||||
return curly;
|
||||
}
|
||||
|
||||
if (this.match('identifier') || this.match('number')) {
|
||||
return {
|
||||
type: 'segment',
|
||||
value: this.consumeToken().value
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,7 +77,7 @@ define([
|
||||
this.errorMark('Expected metric identifier');
|
||||
}
|
||||
|
||||
this.index++;
|
||||
this.consumeToken();
|
||||
|
||||
if (!this.match('identifier')) {
|
||||
this.errorMark('Expected identifier after templateStart');
|
||||
@@ -50,21 +85,19 @@ define([
|
||||
|
||||
var node = {
|
||||
type: 'template',
|
||||
value: this.tokens[this.index].value
|
||||
value: this.consumeToken().value
|
||||
};
|
||||
|
||||
this.index++;
|
||||
|
||||
if (!this.match('templateEnd')) {
|
||||
this.errorMark('Expected templateEnd');
|
||||
}
|
||||
|
||||
this.index++;
|
||||
this.consumeToken();
|
||||
return node;
|
||||
},
|
||||
|
||||
metricExpression: function() {
|
||||
if (!this.match('templateStart') && !this.match('identifier')) {
|
||||
if (!this.match('templateStart') && !this.match('identifier') && !this.match('number')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -76,7 +109,7 @@ define([
|
||||
node.segments.push(this.metricSegment());
|
||||
|
||||
while(this.match('.')) {
|
||||
this.index++;
|
||||
this.consumeToken();
|
||||
|
||||
var segment = this.metricSegment();
|
||||
if (!segment) {
|
||||
@@ -96,10 +129,11 @@ define([
|
||||
|
||||
var node = {
|
||||
type: 'function',
|
||||
name: this.tokens[this.index].value,
|
||||
name: this.consumeToken().value,
|
||||
};
|
||||
|
||||
this.index += 2;
|
||||
// consume left paranthesis
|
||||
this.consumeToken();
|
||||
|
||||
node.params = this.functionParameters();
|
||||
|
||||
@@ -107,7 +141,7 @@ define([
|
||||
this.errorMark('Expected closing paranthesis');
|
||||
}
|
||||
|
||||
this.index++;
|
||||
this.consumeToken();
|
||||
|
||||
return node;
|
||||
},
|
||||
@@ -119,15 +153,15 @@ define([
|
||||
|
||||
var param =
|
||||
this.functionCall() ||
|
||||
this.metricExpression() ||
|
||||
this.numericLiteral() ||
|
||||
this.metricExpression() ||
|
||||
this.stringLiteral();
|
||||
|
||||
if (!this.match(',')) {
|
||||
return [param];
|
||||
}
|
||||
|
||||
this.index++;
|
||||
this.consumeToken();
|
||||
return [param].concat(this.functionParameters());
|
||||
},
|
||||
|
||||
@@ -136,11 +170,9 @@ define([
|
||||
return null;
|
||||
}
|
||||
|
||||
this.index++;
|
||||
|
||||
return {
|
||||
type: 'number',
|
||||
value: parseInt(this.tokens[this.index-1].value, 10)
|
||||
value: parseFloat(this.consumeToken().value)
|
||||
};
|
||||
},
|
||||
|
||||
@@ -149,13 +181,11 @@ define([
|
||||
return null;
|
||||
}
|
||||
|
||||
var token = this.tokens[this.index];
|
||||
var token = this.consumeToken();
|
||||
if (token.isUnclosed) {
|
||||
throw { message: 'Unclosed string parameter', pos: token.pos };
|
||||
}
|
||||
|
||||
this.index++;
|
||||
|
||||
return {
|
||||
type: 'string',
|
||||
value: token.value
|
||||
@@ -171,6 +201,12 @@ define([
|
||||
};
|
||||
},
|
||||
|
||||
// returns token value and incre
|
||||
consumeToken: function() {
|
||||
this.index++;
|
||||
return this.tokens[this.index-1];
|
||||
},
|
||||
|
||||
matchToken: function(type, index) {
|
||||
var token = this.tokens[this.index + index];
|
||||
return (token === undefined && type === '') ||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/** @scratch /configuration/config.js/1
|
||||
* == Configuration
|
||||
* config.js is where you will find the core Grafana configuration. This file contains parameter that
|
||||
* must be set before kibana is run for the first time.
|
||||
*/
|
||||
define(['settings'],
|
||||
function (Settings) {
|
||||
"use strict";
|
||||
|
||||
return new Settings({
|
||||
|
||||
elasticsearch: "http://"+window.location.hostname+":9200",
|
||||
|
||||
graphiteUrl: "http://"+window.location.hostname+":8080",
|
||||
|
||||
default_route: '/dashboard/file/default.json',
|
||||
|
||||
grafana_index: "grafana-dash",
|
||||
|
||||
panel_names: [
|
||||
'text',
|
||||
'graphite'
|
||||
]
|
||||
});
|
||||
});
|
||||
42
src/config.sample.js
Normal file
42
src/config.sample.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/** @scratch /configuration/config.js/1
|
||||
* == Configuration
|
||||
* config.js is where you will find the core Grafana configuration. This file contains parameter that
|
||||
* must be set before kibana is run for the first time.
|
||||
*/
|
||||
define(['settings'],
|
||||
function (Settings) {
|
||||
"use strict";
|
||||
|
||||
return new Settings({
|
||||
|
||||
/**
|
||||
* elasticsearch url:
|
||||
* For Basic authentication use: http://username:password@domain.com:9200
|
||||
*/
|
||||
elasticsearch: "http://"+window.location.hostname+":9200",
|
||||
|
||||
/**
|
||||
* graphite-web url:
|
||||
* For Basic authentication use: http://username:password@domain.com
|
||||
* Basic authentication requires special HTTP headers to be configured
|
||||
* in nginx or apache for cross origin domain sharing to work (CORS).
|
||||
* Check install documentation on github
|
||||
*/
|
||||
graphiteUrl: "http://"+window.location.hostname+":8080",
|
||||
|
||||
default_route: '/dashboard/file/default.json',
|
||||
|
||||
/**
|
||||
* If your graphite server has another timezone than you & users browsers specify the offset here
|
||||
* Example: "-0500" (for UTC - 5 hours)
|
||||
*/
|
||||
timezoneOffset: null,
|
||||
|
||||
grafana_index: "grafana-dash",
|
||||
|
||||
panel_names: [
|
||||
'text',
|
||||
'graphite'
|
||||
]
|
||||
});
|
||||
});
|
||||
2
src/css/bootstrap-responsive.min.css
vendored
2
src/css/bootstrap-responsive.min.css
vendored
File diff suppressed because one or more lines are too long
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,3 +21,16 @@
|
||||
// 768px-979px
|
||||
@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768);
|
||||
@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768);
|
||||
|
||||
|
||||
// Media queries
|
||||
// ---------------------
|
||||
@media (max-width: 767px) {
|
||||
div.panel {
|
||||
width: 100% !important;
|
||||
padding: 0px !important;
|
||||
}
|
||||
body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
@import "submenu.less";
|
||||
@import "bootstrap-tagsinput.less";
|
||||
|
||||
.navbar-static-top {
|
||||
border-bottom: 1px solid black;
|
||||
@@ -10,12 +12,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Search
|
||||
|
||||
.grafana-search-panel {
|
||||
padding: 6px 10px;
|
||||
input {
|
||||
width: 100%;
|
||||
.box-sizing(border-box);
|
||||
padding: 15px;
|
||||
|
||||
.search-field-wrapper {
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
button {
|
||||
margin: 0 2px 0 0;
|
||||
}
|
||||
> span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
padding-right: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.selected td, tr.selected:nth-child(odd)>td {
|
||||
@@ -26,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 {
|
||||
@@ -345,4 +359,79 @@ input[type=text].func-param {
|
||||
.editor-row {
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Srollbars
|
||||
//
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar:hover {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button:start:decrement,
|
||||
::-webkit-scrollbar-button:end:increment { display: none; }
|
||||
::-webkit-scrollbar-button:horizontal:decrement { display: none; }
|
||||
::-webkit-scrollbar-button:horizontal:increment { display: none; }
|
||||
::-webkit-scrollbar-button:vertical:decrement { display: none; }
|
||||
::-webkit-scrollbar-button:vertical:increment { display: none; }
|
||||
::-webkit-scrollbar-button:horizontal:decrement:active { background-image: none; }
|
||||
::-webkit-scrollbar-button:horizontal:increment:active { background-image: none; }
|
||||
::-webkit-scrollbar-button:vertical:decrement:active { background-image: none; }
|
||||
::-webkit-scrollbar-button:vertical:increment:active {background-image: none; }
|
||||
|
||||
::-webkit-scrollbar-track-piece { background-color: grayDark; }
|
||||
|
||||
::-webkit-scrollbar-thumb:vertical {
|
||||
height: 50px;
|
||||
background: -webkit-gradient(linear, left top, right top, color-stop(0%, #3a3a3a), color-stop(100%, #222222));
|
||||
border: 1px solid #0d0d0d;
|
||||
border-top: 1px solid #666666;
|
||||
border-left: 1px solid #666666;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:horizontal {
|
||||
width: 50px;
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #3a3a3a), color-stop(100%, #222222));
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
// Media queries
|
||||
// ---------------------
|
||||
@media (max-width: 767px) {
|
||||
div.panel {
|
||||
width: 100% !important;
|
||||
padding: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Containers
|
||||
// ---------------------
|
||||
.container-fluid {
|
||||
@@ -5,6 +14,11 @@
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.container.grafana-container {
|
||||
padding: 5px 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// Backgrounds
|
||||
// ---------------------
|
||||
@@ -51,14 +65,57 @@ code, pre {
|
||||
background-color: @grayLighter;
|
||||
}
|
||||
|
||||
.panelCont {
|
||||
padding: 0px 10px 10px 10px;
|
||||
background: @kibanaPanelBackground;
|
||||
margin: 0px;
|
||||
//border: 1px solid rgba(100, 100, 100, 0.25);
|
||||
//outline: 1px solid darken(@bodyBackground, 10%);
|
||||
.panel {
|
||||
display: inline-table;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.panel-container {
|
||||
padding: 0px 0px 0px 0px;
|
||||
background: @kibanaPanelBackground;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 0px 10px 10px 10px;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
border: 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.panel-loading {
|
||||
position:absolute;
|
||||
top: 0px;
|
||||
right: 4px;
|
||||
z-index: 800;
|
||||
}
|
||||
|
||||
.panel div.panel-extra div.panel-extra-container {
|
||||
margin-right: -10px;
|
||||
margin-top: 3px;
|
||||
text-align: center;
|
||||
ul {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.panel div.panel-extra {
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.panel div.panel-extra .extra {
|
||||
float:right !important;
|
||||
}
|
||||
|
||||
.panel-error {
|
||||
color: @white;
|
||||
padding: 3px 10px 0px 10px;
|
||||
}
|
||||
|
||||
|
||||
div.editor-row {
|
||||
vertical-align: top;
|
||||
}
|
||||
@@ -138,7 +195,7 @@ form input.ng-invalid {
|
||||
}
|
||||
|
||||
.kibana-row {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.row-tab {
|
||||
@@ -173,21 +230,8 @@ 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;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
@@ -217,48 +261,13 @@ form input.ng-invalid {
|
||||
border-top: 0px;
|
||||
}
|
||||
|
||||
.panel-loading {
|
||||
position:absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
z-index: 800;
|
||||
}
|
||||
|
||||
.ui-draggable-dragging {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
border: 0px;
|
||||
//text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.panel div.panel-extra div.panel-extra-container {
|
||||
margin-right: -10px;
|
||||
margin-top: 3px;
|
||||
text-align: center;
|
||||
ul {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.panel div.panel-extra {
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.panel div.panel-extra .extra {
|
||||
float:right !important;
|
||||
//border-bottom: 1px solid lighten(@bodyBackground, 5%);
|
||||
}
|
||||
|
||||
.dragInProgress {
|
||||
background-color: darken(@bodyBackground,1%);
|
||||
border: 1px solid @tableBorder;
|
||||
.dragInProgress .panel-container {
|
||||
border: 3px solid rgba(100,100,100,0.50);
|
||||
}
|
||||
|
||||
.link {
|
||||
@@ -352,10 +361,6 @@ div.flot-text {
|
||||
color: @white;
|
||||
}
|
||||
|
||||
.panel-error {
|
||||
color: @white;
|
||||
padding: 3px 10px 0px 10px;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: @warningBackground;
|
||||
@@ -545,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,8 +1,8 @@
|
||||
define([
|
||||
'app/services/graphite/gfunc'
|
||||
'services/graphite/gfunc'
|
||||
], function(gfunc) {
|
||||
|
||||
describe('when creating func instance from func namae', function() {
|
||||
describe('when creating func instance from func names', function() {
|
||||
|
||||
it('should return func instance', function() {
|
||||
var func = gfunc.createFuncInstance('sumSeries');
|
||||
@@ -34,11 +34,33 @@ define([
|
||||
|
||||
});
|
||||
|
||||
describe('when rendering func instance', function() {
|
||||
|
||||
it('should handle single metric param', function() {
|
||||
var func = gfunc.createFuncInstance('sumSeries');
|
||||
expect(func.render('hello.metric')).to.equal("sumSeries(hello.metric)");
|
||||
});
|
||||
|
||||
it('should handle metric param and int param and string param', function() {
|
||||
var func = gfunc.createFuncInstance('groupByNode');
|
||||
func.params[0] = 5;
|
||||
func.params[1] = 'avg';
|
||||
expect(func.render('hello.metric')).to.equal("groupByNode(hello.metric,5,'avg')");
|
||||
});
|
||||
|
||||
it('should handle function with no metric param', function() {
|
||||
var func = gfunc.createFuncInstance('randomWalk');
|
||||
func.params[0] = 'test';
|
||||
expect(func.render(undefined)).to.equal("randomWalk('test')");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when requesting function categories', function() {
|
||||
|
||||
it('should return function categories', function() {
|
||||
var catIndex = gfunc.getCategories();
|
||||
expect(catIndex.Special.length).to.equal(3);
|
||||
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,21 +21,50 @@ define([
|
||||
expect(tokens[4].value).to.be('se1-server-*');
|
||||
});
|
||||
|
||||
|
||||
it('should tokenize functions and args', function() {
|
||||
var lexer = new Lexer("sum(metric.test, 12, 'test')");
|
||||
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('sum');
|
||||
expect(tokens[0].type).to.be('identifier');
|
||||
expect(tokens[1].value).to.be('(');
|
||||
expect(tokens[1].type).to.be('(');
|
||||
expect(tokens[5].type).to.be(',');
|
||||
expect(tokens[5].value).to.be(',');
|
||||
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();
|
||||
expect(tokens.length).to.be(10);
|
||||
expect(tokens[3].type).to.be('{');
|
||||
expect(tokens[4].value).to.be('first');
|
||||
expect(tokens[5].value).to.be(',');
|
||||
expect(tokens[6].value).to.be('second');
|
||||
});
|
||||
|
||||
it('should tokenize metric expression with number segments', function() {
|
||||
var lexer = new Lexer("metric.10.12_10.test");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].type).to.be('identifier');
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('10');
|
||||
expect(tokens[4].value).to.be('12_10');
|
||||
expect(tokens[4].type).to.be('identifier');
|
||||
});
|
||||
|
||||
it('should tokenize func call with numbered metric and number arg', function() {
|
||||
var lexer = new Lexer("scale(metric.10, 15)");
|
||||
var tokens = lexer.tokenize();
|
||||
expect(tokens[0].type).to.be('identifier');
|
||||
expect(tokens[2].type).to.be('identifier');
|
||||
expect(tokens[2].value).to.be('metric');
|
||||
expect(tokens[4].value).to.be('10');
|
||||
expect(tokens[4].type).to.be('number');
|
||||
expect(tokens[6].type).to.be('number');
|
||||
expect(tokens[6].value).to.be('12');
|
||||
expect(tokens[8].type).to.be('string');
|
||||
expect(tokens[8].value).to.be('test');
|
||||
expect(tokens[tokens.length - 1].value).to.be(')');
|
||||
});
|
||||
|
||||
it('should tokenize metric with template parameter', function() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
define([
|
||||
'app/services/graphite/parser'
|
||||
'services/graphite/parser'
|
||||
], function(Parser) {
|
||||
|
||||
describe('when parsing', function() {
|
||||
@@ -13,6 +13,35 @@ define([
|
||||
expect(rootNode.segments[0].value).to.be('metric');
|
||||
});
|
||||
|
||||
it('simple metric expression with numbers in segments', function() {
|
||||
var parser = new Parser('metric.10.15_20.5');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments.length).to.be(4);
|
||||
expect(rootNode.segments[1].value).to.be('10');
|
||||
expect(rootNode.segments[2].value).to.be('15_20');
|
||||
expect(rootNode.segments[3].value).to.be('5');
|
||||
});
|
||||
|
||||
it('simple metric expression with curly braces', function() {
|
||||
var parser = new Parser('metric.se1-{count, max}');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments.length).to.be(2);
|
||||
expect(rootNode.segments[1].value).to.be('se1-{count,max}');
|
||||
});
|
||||
|
||||
it('simple metric expression with curly braces at start of segment and with post chars', function() {
|
||||
var parser = new Parser('metric.{count, max}-something.count');
|
||||
var rootNode = parser.getAst();
|
||||
|
||||
expect(rootNode.type).to.be('metric');
|
||||
expect(rootNode.segments.length).to.be(3);
|
||||
expect(rootNode.segments[1].value).to.be('{count,max}-something');
|
||||
});
|
||||
|
||||
it('simple function', function() {
|
||||
var parser = new Parser('sum(test)');
|
||||
var rootNode = parser.getAst();
|
||||
@@ -20,6 +49,22 @@ 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();
|
||||
expect(rootNode.type).to.be('function');
|
||||
expect(rootNode.params.length).to.be(1);
|
||||
expect(rootNode.params[0].type).to.be('string');
|
||||
});
|
||||
|
||||
it('function with multiple args', function() {
|
||||
var parser = new Parser("sum(test, 1, 'test')");
|
||||
var rootNode = parser.getAst();
|
||||
@@ -88,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();
|
||||
});
|
||||
37
src/vendor/angular/angular-dragdrop.js
vendored
37
src/vendor/angular/angular-dragdrop.js
vendored
@@ -1,7 +1,36 @@
|
||||
/*
|
||||
This has been modified from the default angular-draganddrop to provide the original
|
||||
model and some other stuff as the 'data' arguement to callEventCallback
|
||||
*/
|
||||
/**
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementing Drag and Drop functionality in AngularJS is easier than ever.
|
||||
* Demo: http://codef0rmer.github.com/angular-dragdrop/
|
||||
*
|
||||
* @version 1.0.4
|
||||
*
|
||||
* (c) 2013 Amit Gharat a.k.a codef0rmer <amit.2006.it@gmail.com> - amitgharat.wordpress.com
|
||||
*/
|
||||
|
||||
/**
|
||||
* This has been modified from the default angular-draganddrop to provide the original
|
||||
* model and some other stuff as the 'data' arguement to callEventCallback
|
||||
*/
|
||||
|
||||
(function (window, angular, undefined) {
|
||||
'use strict';
|
||||
|
||||
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
10
src/vendor/crypto.min.js
vendored
Normal file
10
src/vendor/crypto.min.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Crypto-JS v2.5.3
|
||||
* http://code.google.com/p/crypto-js/
|
||||
* (c) 2009-2012 by Jeff Mott. All rights reserved.
|
||||
* http://code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
(typeof Crypto=="undefined"||!Crypto.util)&&function(){var e=window.Crypto={},g=e.util={rotl:function(a,b){return a<<b|a>>>32-b},rotr:function(a,b){return a<<32-b|a>>>b},endian:function(a){if(a.constructor==Number)return g.rotl(a,8)&16711935|g.rotl(a,24)&4278255360;for(var b=0;b<a.length;b++)a[b]=g.endian(a[b]);return a},randomBytes:function(a){for(var b=[];a>0;a--)b.push(Math.floor(Math.random()*256));return b},bytesToWords:function(a){for(var b=[],c=0,d=0;c<a.length;c++,d+=8)b[d>>>5]|=(a[c]&255)<<
|
||||
24-d%32;return b},wordsToBytes:function(a){for(var b=[],c=0;c<a.length*32;c+=8)b.push(a[c>>>5]>>>24-c%32&255);return b},bytesToHex:function(a){for(var b=[],c=0;c<a.length;c++)b.push((a[c]>>>4).toString(16)),b.push((a[c]&15).toString(16));return b.join("")},hexToBytes:function(a){for(var b=[],c=0;c<a.length;c+=2)b.push(parseInt(a.substr(c,2),16));return b},bytesToBase64:function(a){if(typeof btoa=="function")return btoa(f.bytesToString(a));for(var b=[],c=0;c<a.length;c+=3)for(var d=a[c]<<16|a[c+1]<<
|
||||
8|a[c+2],e=0;e<4;e++)c*8+e*6<=a.length*8?b.push("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(d>>>6*(3-e)&63)):b.push("=");return b.join("")},base64ToBytes:function(a){if(typeof atob=="function")return f.stringToBytes(atob(a));for(var a=a.replace(/[^A-Z0-9+\/]/ig,""),b=[],c=0,d=0;c<a.length;d=++c%4)d!=0&&b.push(("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(a.charAt(c-1))&Math.pow(2,-2*d+8)-1)<<d*2|"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(a.charAt(c))>>>
|
||||
6-d*2);return b}},e=e.charenc={};e.UTF8={stringToBytes:function(a){return f.stringToBytes(unescape(encodeURIComponent(a)))},bytesToString:function(a){return decodeURIComponent(escape(f.bytesToString(a)))}};var f=e.Binary={stringToBytes:function(a){for(var b=[],c=0;c<a.length;c++)b.push(a.charCodeAt(c)&255);return b},bytesToString:function(a){for(var b=[],c=0;c<a.length;c++)b.push(String.fromCharCode(a[c]));return b.join("")}}}();
|
||||
@@ -13,7 +13,7 @@ be injected into your angular controllers.
|
||||
angular.module('elasticjs.service', [])
|
||||
.factory('ejsResource', ['$http', function ($http) {
|
||||
|
||||
return function (config) {
|
||||
return function (config, basicAuth) {
|
||||
|
||||
var
|
||||
|
||||
@@ -43,6 +43,12 @@ angular.module('elasticjs.service', [])
|
||||
config.server = '';
|
||||
}
|
||||
|
||||
// set authentication header
|
||||
if (basicAuth || config.basicAuth) {
|
||||
config.headers = angular.extend( config.headers||{}, {
|
||||
"Authorization": "Basic " + (basicAuth||config.basicAuth)
|
||||
});
|
||||
}
|
||||
/* implement the elastic.js client interface for angular */
|
||||
ejs.client = {
|
||||
server: function (s) {
|
||||
|
||||
27
src/vendor/jquery/jquery.flot.events.js
vendored
27
src/vendor/jquery/jquery.flot.events.js
vendored
@@ -349,34 +349,7 @@
|
||||
function(){
|
||||
var pos = $(this).offset();
|
||||
|
||||
/*// check if the mouse is not already over the event
|
||||
if ($(this).data("bouncing") == false || $(this).data("bouncing") == undefined) {
|
||||
|
||||
// check the div is not already bouncing
|
||||
if ($(this).position().top == $(this).data("top")) {
|
||||
$(this).effect("bounce", {
|
||||
times: 3
|
||||
}, 300);
|
||||
}
|
||||
|
||||
$(this).data("bouncing", true);
|
||||
_showTooltip(pos.left + $(this).width() / 2, pos.top, $(this).data("event"));
|
||||
}*/
|
||||
|
||||
_showTooltip(pos.left + $(this).width() / 2, pos.top, $(this).data("event"));
|
||||
|
||||
if (event.min != event.max) {
|
||||
plot.setSelection({
|
||||
xaxis: {
|
||||
from: event.min,
|
||||
to: event.max
|
||||
},
|
||||
yaxis: {
|
||||
from: yaxis.min,
|
||||
to: yaxis.max
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
// mouseleave
|
||||
function(){
|
||||
|
||||
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);
|
||||
74
src/vendor/license.json
vendored
Normal file
74
src/vendor/license.json
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"angular": {
|
||||
"version":"1.1.5",
|
||||
"license":"MIT"
|
||||
},
|
||||
"angular-dragdrop": {
|
||||
"version":"1.0.4",
|
||||
"license":"MIT"
|
||||
},
|
||||
"angular-strap": {
|
||||
"version":"0.7.5",
|
||||
"license":"MIT"
|
||||
},
|
||||
"bindonce": {
|
||||
"version":"0.2.1",
|
||||
"license":"MIT"
|
||||
},
|
||||
"datepicker": {
|
||||
"version":"12/3/2013",
|
||||
"license":"Apache 2.0"
|
||||
},
|
||||
"timepicker": {
|
||||
"version":"0.2.6",
|
||||
"license":"MIT"
|
||||
},
|
||||
"bootstrap": {
|
||||
"version":"2.3.2",
|
||||
"license":"Apache 2.0"
|
||||
},
|
||||
"elasticjs": {
|
||||
"version":"1.1.1",
|
||||
"license":"MIT"
|
||||
},
|
||||
"jquery": {
|
||||
"version":"1.8.0",
|
||||
"license":"MIT"
|
||||
},
|
||||
"jquery-ui": {
|
||||
"version":"1.10.3",
|
||||
"license":"MIT"
|
||||
},
|
||||
"flot": {
|
||||
"version":"0.8.1",
|
||||
"license":"MIT"
|
||||
},
|
||||
"require": {
|
||||
"version":"2.1.8",
|
||||
"license":"MIT"
|
||||
},
|
||||
"chromath": {
|
||||
"version":"0.0.5",
|
||||
"license":"MIT"
|
||||
},
|
||||
"filesaver": {
|
||||
"version":"2013-01-23",
|
||||
"license":"MIT"
|
||||
},
|
||||
"modernizr": {
|
||||
"version":"2.6.1",
|
||||
"license":"MIT"
|
||||
},
|
||||
"moment": {
|
||||
"version":"2.1.0",
|
||||
"license":"MIT"
|
||||
},
|
||||
"timezone": {
|
||||
"version":"2010",
|
||||
"license":"Apache 2"
|
||||
},
|
||||
"underscore": {
|
||||
"version":"1.5.1",
|
||||
"license":"MIT"
|
||||
}
|
||||
}
|
||||
1194
src/vendor/moment.js
vendored
1194
src/vendor/moment.js
vendored
File diff suppressed because it is too large
Load Diff
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