Quellcode durchsuchen

Merge branch 'elastic_annotations'

Conflicts:
	src/css/bootstrap.dark.min.css
	src/css/bootstrap.light.min.css
	src/css/default.min.css
Torkel Ödegaard vor 11 Jahren
Ursprung
Commit
d2a342a94e

+ 1 - 0
CHANGELOG.md

@@ -12,6 +12,7 @@
 - [Issue #610](https://github.com/grafana/grafana/issues/610). InfluxDB: Support for InfluxdB v0.8 list series response schemea (series typeahead)
 - [Issue #266](https://github.com/grafana/grafana/issues/266). Graphite: New option cacheTimeout to override graphite default memcache timeout
 - [Issue #606](https://github.com/grafana/grafana/issues/606). General: New global option in config.js to specify admin password (useful to hinder users from accidentally make changes)
+- [Issue #201](https://github.com/grafana/grafana/issues/201). Annotations: Elasticsearch datasource support for events
 
 **Changes**
 - [Issue #536](https://github.com/grafana/grafana/issues/536). Graphite: Use unix epoch for Graphite from/to for absolute time ranges

+ 15 - 19
src/app/components/settings.js

@@ -13,18 +13,10 @@ function (_, crypto) {
      * @type {Object}
      */
     var defaults = {
-      elasticsearch                 : "http://"+window.location.hostname+":9200",
-      datasources                   : {
-        default: {
-          url: "http://"+window.location.hostname+":8080",
-          default: true
-        }
-      },
+      datasources                   : {},
       panels                        : ['graph', 'text'],
       plugins                       : {},
       default_route                 : '/dashboard/file/default.json',
-      grafana_index                 : 'grafana-dash',
-      elasticsearch_all_disabled    : false,
       playlist_timespan             : "1m",
       unsaved_changes_warning       : true,
       admin: {}
@@ -57,13 +49,21 @@ function (_, crypto) {
       return datasource;
     };
 
+    // backward compatible with old config
     if (options.graphiteUrl) {
-      settings.datasources = {
-        graphite: {
-          type: 'graphite',
-          url: options.graphiteUrl,
-          default: true
-        }
+      settings.datasources.graphite = {
+        type: 'graphite',
+        url: options.graphiteUrl,
+        default: true
+      };
+    }
+
+    if (options.elasticsearch) {
+      settings.datasources.elasticsearch = {
+        type: 'elasticsearch',
+        url: options.elasticsearch,
+        index: options.grafana_index,
+        grafanaDB: true
       };
     }
 
@@ -73,10 +73,6 @@ function (_, crypto) {
       if (datasource.type === 'influxdb') { parseMultipleHosts(datasource); }
     });
 
-    var elasticParsed = parseBasicAuth({ url: settings.elasticsearch });
-    settings.elasticsearchBasicAuth = elasticParsed.basicAuth;
-    settings.elasticsearch = elasticParsed.url;
-
     if (settings.plugins.panels) {
       settings.panels = _.union(settings.panels, settings.plugins.panels);
     }

+ 35 - 7
src/app/controllers/dashLoader.js

@@ -2,16 +2,18 @@ define([
   'angular',
   'underscore',
   'moment',
+  'config',
   'filesaver'
 ],
-function (angular, _, moment) {
+function (angular, _, moment, config) {
   'use strict';
 
   var module = angular.module('grafana.controllers');
 
-  module.controller('dashLoader', function($scope, $rootScope, $http, alertSrv, $location, playlistSrv, elastic) {
+  module.controller('dashLoader', function($scope, $rootScope, $http, alertSrv, $location, playlistSrv, datasourceSrv) {
 
     $scope.init = function() {
+      $scope.db = datasourceSrv.getGrafanaDB();
       $scope.onAppEvent('save-dashboard', function() {
         $scope.saveDashboard();
       });
@@ -19,7 +21,6 @@ function (angular, _, moment) {
       $scope.onAppEvent('zoom-out', function() {
         $scope.zoom(2);
       });
-
     };
 
     $scope.exitFullscreen = function() {
@@ -38,6 +39,9 @@ function (angular, _, moment) {
       if(type === 'save') {
         return (_l.save_elasticsearch);
       }
+      if(type === 'share') {
+        return (_l.save_temp);
+      }
       return false;
     };
 
@@ -52,7 +56,7 @@ function (angular, _, moment) {
     };
 
     $scope.saveForSharing = function() {
-      elastic.saveForSharing($scope.dashboard)
+      $scope.db.saveDashboardTemp($scope.dashboard)
         .then(function(result) {
 
           $scope.share = { url: result.url, title: result.title };
@@ -62,10 +66,32 @@ function (angular, _, moment) {
         });
     };
 
+    $scope.passwordCache = function(pwd) {
+      if (!window.sessionStorage) { return null; }
+      if (!pwd) { return window.sessionStorage["grafanaAdminPassword"]; }
+      window.sessionStorage["grafanaAdminPassword"] = pwd;
+    };
+
+    $scope.isAdmin = function() {
+      if (!config.admin || !config.admin.password) { return true; }
+      if (this.passwordCache() === config.admin.password) { return true; }
+
+      var password = window.prompt("Admin password", "");
+      this.passwordCache(password);
+
+      if (password === config.admin.password) { return true; }
+
+      alertSrv.set('Save failed', 'Password incorrect', 'error');
+
+      return false;
+    };
+
     $scope.saveDashboard = function() {
-      elastic.saveDashboard($scope.dashboard, $scope.dashboard.title)
+      if (!this.isAdmin()) { return false; }
+
+      $scope.db.saveDashboard($scope.dashboard, $scope.dashboard.title)
         .then(function(result) {
-          alertSrv.set('Dashboard Saved', 'Dashboard has been saved to Elasticsearch as "' + result.title + '"','success', 5000);
+          alertSrv.set('Dashboard Saved', 'Dashboard has been saved as "' + result.title + '"','success', 5000);
 
           $location.path(result.url);
 
@@ -81,7 +107,9 @@ function (angular, _, moment) {
         return;
       }
 
-      elastic.deleteDashboard(id).then(function(id) {
+      if (!this.isAdmin()) { return false; }
+
+      $scope.db.deleteDashboard(id).then(function(id) {
         alertSrv.set('Dashboard Deleted', id + ' has been deleted', 'success', 5000);
       }, function() {
         alertSrv.set('Dashboard Not Deleted', 'An error occurred deleting the dashboard', 'error', 5000);

+ 3 - 3
src/app/controllers/graphiteImport.js

@@ -11,8 +11,7 @@ function (angular, app, _) {
   module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv) {
 
     $scope.init = function() {
-      console.log('hej!');
-      $scope.datasources = datasourceSrv.listOptions();
+      $scope.datasources = datasourceSrv.getMetricSources();
       $scope.setDatasource(null);
     };
 
@@ -96,7 +95,8 @@ function (angular, app, _) {
         currentRow.panels.push(panel);
       });
 
-      $scope.dashboard.dash_load(newDashboard);
+      $scope.emitAppEvent('setup-dashboard', newDashboard);
+      $scope.dismiss();
     }
 
   });

+ 10 - 40
src/app/controllers/search.js

@@ -9,13 +9,14 @@ function (angular, _, config, $) {
 
   var module = angular.module('grafana.controllers');
 
-  module.controller('SearchCtrl', function($scope, $rootScope, $element, $location, elastic) {
+  module.controller('SearchCtrl', function($scope, $rootScope, $element, $location, datasourceSrv) {
 
     $scope.init = function() {
       $scope.giveSearchFocus = 0;
       $scope.selectedIndex = -1;
       $scope.results = {dashboards: [], tags: [], metrics: []};
       $scope.query = { query: 'title:' };
+      $scope.db = datasourceSrv.getGrafanaDB();
       $scope.onAppEvent('open-search', $scope.openSearch);
     };
 
@@ -57,40 +58,12 @@ function (angular, _, config, $) {
       };
     };
 
-    $scope.searchDasboards = function(queryString) {
-      var tagsOnly = queryString.indexOf('tags!:') === 0;
-      if (tagsOnly) {
-        var tagsQuery = queryString.substring(6, queryString.length);
-        queryString = 'tags:' + tagsQuery + '*';
-      }
-      else {
-        if (queryString.length === 0) {
-          queryString = 'title:';
-        }
-
-        if (queryString[queryString.length - 1] !== '*') {
-          queryString += '*';
-        }
-      }
-
-      var query = {
-        query: { query_string: { query: queryString } },
-        facets: { tags: { terms: { field: "tags", order: "term", size: 50 } } },
-        size: 20,
-        sort: ["_uid"]
-      };
-
-      return elastic.post('/dashboard/_search', query)
+    $scope.searchDashboards = function(queryString) {
+      return $scope.db.searchDashboards(queryString)
         .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.tagsOnly = results.dashboards.length === 0 && results.tags.length > 0;
+          $scope.results.dashboards = results.dashboards;
+          $scope.results.tags = results.tags;
         });
     };
 
@@ -118,12 +91,9 @@ function (angular, _, config, $) {
       $scope.selectedIndex = -1;
 
       var queryStr = $scope.query.query.toLowerCase();
+      queryStr = queryStr.replace(' and ', ' AND ');
 
-      if (queryStr.indexOf('m:') !== 0) {
-        queryStr = queryStr.replace(' and ', ' AND ');
-        $scope.searchDasboards(queryStr);
-        return;
-      }
+      $scope.searchDashboards(queryStr);
     };
 
     $scope.openSearch = function (evt) {
@@ -177,4 +147,4 @@ function (angular, _, config, $) {
     };
   });
 
-});
+});

+ 1 - 1
src/app/panels/annotations/editor.js

@@ -12,7 +12,7 @@ function (angular, app, _) {
   var module = angular.module('grafana.panels.annotations', []);
   app.useModule(module);
 
-  module.controller('AnnotationsEditorCtrl', function($scope, datasourceSrv, $rootScope) {
+  module.controller('AnnotationsEditorCtrl', function($scope, datasourceSrv) {
 
     var annotationDefaults = {
       name: '',

+ 1 - 1
src/app/panels/graph/module.js

@@ -220,7 +220,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
       $scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs,'title');
       $scope.hiddenSeries = {};
 
-      $scope.datasources = datasourceSrv.listOptions();
+      $scope.datasources = datasourceSrv.getMetricSources();
       $scope.setDatasource($scope.panel.datasource);
 
       if ($scope.panel.targets.length === 0) {

+ 6 - 3
src/app/partials/dashLoader.html

@@ -27,7 +27,7 @@
 
     <li ng-show="dashboard.loader.save_elasticsearch">
       <form class="input-prepend nomargin save-dashboard-dropdown-save-form">
-        <input class='input-medium' ng-model="dashboard.title" type="text" ng-model="elasticsearch.title"/>
+        <input class='input-medium' ng-model="dashboard.title" type="text" />
         <button class="btn" ng-click="saveDashboard()"><i class="icon-save"></i></button>
       </form>
     </li>
@@ -47,8 +47,11 @@
     <li ng-show="dashboard.loader.save_local">
       <a class="link" ng-click="exportDashboard()">Export dashboard</a>
     </li>
-    <li ng-show="showDropdown('share')"><a bs-tooltip="'Share'" data-placement="bottom" ng-click="saveForSharing()" config-modal="app/partials/dashLoaderShare.html">Share temp copy</i></a></li>
-
+		<li ng-show="showDropdown('share')">
+			<a bs-tooltip="'Share'" data-placement="bottom" ng-click="saveForSharing()" config-modal="app/partials/dashLoaderShare.html">
+				Share temp copy
+			</a>
+	  </li>
   </ul>
 </li>
 

+ 2 - 6
src/app/partials/dasheditor.html

@@ -2,7 +2,7 @@
   <div class="pull-right editor-title">Dashboard settings</div>
 
   <div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
-    <div ng-repeat="tab in ['General', 'Rows','Controls', 'Metrics', 'Import']" data-title="{{tab}}">
+    <div ng-repeat="tab in ['General', 'Rows','Controls', 'Import']" data-title="{{tab}}">
     </div>
     <div ng-repeat="tab in dashboard.nav" data-title="{{tab.type}}">
     </div>
@@ -111,11 +111,7 @@
     </div>
   </div>
 
-  <div ng-if="editor.index == 3">
-    <ng-include src="'app/partials/loadmetrics.html'"></ng-include>
-  </div>
-
-  <div ng-if="editor.index == 4">
+	<div ng-if="editor.index == 3">
     <ng-include src="'app/partials/import.html'"></ng-include>
   </div>
 

+ 39 - 1
src/app/partials/elasticsearch/annotation_editor.html

@@ -1 +1,39 @@
-<h2>Elasticsearch</h2>
+<div class="editor-row">
+	<div class="section">
+		<h5>Index name</h5>
+		<div class="editor-option">
+			<input type="text" class="span4" ng-model='currentAnnotation.index' placeholder="events-*"></input>
+		</div>
+	</div>
+	<div class="section">
+		<h5>Search query (lucene) <tip>Use [[filterName]] in query to replace part of the query with a filter value</h5>
+		<div class="editor-option">
+			<input type="text" class="span6" ng-model='currentAnnotation.query' placeholder="tags:deploy"></input>
+		</div>
+	</div>
+</div>
+
+<div class="editor-row">
+  <div class="section">
+		<h5>Field mappings</h5>
+		<div class="editor-option">
+			<label class="small">Time</label>
+			<input type="text" class="input-small" ng-model='currentAnnotation.timeField' placeholder="@timestamp"></input>
+		</div>
+
+		<div class="editor-option">
+			<label class="small">Title</label>
+			<input type="text" class="input-small" ng-model='currentAnnotation.titleField' placeholder="desc"></input>
+		</div>
+
+		<div class="editor-option">
+			<label class="small">Tags</label>
+			<input type="text" class="input-small" ng-model='currentAnnotation.tagsField' placeholder="tags"></input>
+		</div>
+
+		<div class="editor-option">
+			<label class="small">Text</label>
+			<input type="text" class="input-small" ng-model='currentAnnotation.textField' placeholder=""></input>
+		</div>
+	</div>
+</div>

+ 9 - 33
src/app/routes/dashboard-from-es.js

@@ -1,9 +1,7 @@
 define([
   'angular',
-  'jquery',
-  'config'
 ],
-function (angular, $, config) {
+function (angular) {
   "use strict";
 
   var module = angular.module('grafana.routes');
@@ -20,37 +18,15 @@ function (angular, $, config) {
       });
   });
 
-  module.controller('DashFromElasticProvider', function($scope, $rootScope, elastic, $routeParams, alertSrv) {
+  module.controller('DashFromElasticProvider', function($scope, $rootScope, datasourceSrv, $routeParams, alertSrv) {
 
-    var elasticsearch_load = function(id) {
-      var url = '/dashboard/' + id;
-
-      // hack to check if it is a temp dashboard
-      if (window.location.href.indexOf('dashboard/temp') > 0) {
-        url = '/temp/' + id;
-      }
-
-      return elastic.get(url)
-        .then(function(result) {
-          if (result._source && result._source.dashboard) {
-            return angular.fromJson(result._source.dashboard);
-          } else {
-            return false;
-          }
-        }, function(data, status) {
-          if(status === 0) {
-            alertSrv.set('Error',"Could not contact Elasticsearch at " +
-              config.elasticsearch + ". Please ensure that Elasticsearch is reachable from your browser.",'error');
-          } else {
-            alertSrv.set('Error',"Could not find dashboard " + id, 'error');
-          }
-          return false;
-        });
-    };
-
-    elasticsearch_load($routeParams.id).then(function(dashboard) {
-      $scope.emitAppEvent('setup-dashboard', dashboard);
-    });
+    var db = datasourceSrv.getGrafanaDB();
+    db.getDashboard($routeParams.id)
+      .then(function(dashboard) {
+        $scope.emitAppEvent('setup-dashboard', dashboard);
+      }).then(null, function(error) {
+        alertSrv.set('Error', error, 'error');
+      });
 
   });
 

+ 1 - 2
src/app/services/all.js

@@ -8,8 +8,7 @@ define([
   './annotationsSrv',
   './playlistSrv',
   './unsavedChangesSrv',
-  './elasticsearch/es-client',
   './dashboard/dashboardKeyBindings',
   './dashboard/dashboardModel',
 ],
-function () {});
+function () {});

+ 2 - 1
src/app/services/annotationsSrv.js

@@ -37,6 +37,7 @@ define([
 
       var promises  = _.map(annotations, function(annotation) {
         var datasource = datasourceSrv.get(annotation.datasource);
+
         return datasource.annotationQuery(annotation, filterSrv, rangeUnparsed)
           .then(this.receiveAnnotationResults)
           .then(null, errorHandler);
@@ -64,7 +65,7 @@ define([
     function addAnnotation(options) {
       var tooltip = "<small><b>" + options.title + "</b><br/>";
       if (options.tags) {
-        tooltip += (options.tags || '') + '<br/>';
+        tooltip += '<span class="tag label label-tag">' + (options.tags || '') + '</span><br/>';
       }
 
       if (timezone === 'browser') {

+ 30 - 17
src/app/services/datasourceSrv.js

@@ -14,6 +14,9 @@ function (angular, _, config) {
 
   module.service('datasourceSrv', function($q, filterSrv, $http, $injector) {
     var datasources = {};
+    var metricSources = [];
+    var annotationSources = [];
+    var grafanaDB = {};
 
     this.init = function() {
       _.each(config.datasources, function(value, key) {
@@ -27,6 +30,26 @@ function (angular, _, config) {
         this.default = datasources[_.keys(datasources)[0]];
         this.default.default = true;
       }
+
+      // create list of different source types
+      _.each(datasources, function(value, key) {
+        if (value.supportMetrics) {
+          metricSources.push({
+            name: value.name,
+            value: value.default ? null : key,
+          });
+        }
+        if (value.supportAnnotations) {
+          annotationSources.push({
+            name: key,
+            editorSrc: value.annotationEditorSrc,
+          });
+        }
+        if (value.grafanaDB) {
+          grafanaDB = value;
+        }
+      });
+
     };
 
     this.datasourceFactory = function(ds) {
@@ -56,25 +79,15 @@ function (angular, _, config) {
     };
 
     this.getAnnotationSources = function() {
-      var results = [];
-      _.each(datasources, function(value, key) {
-        if (value.supportAnnotations) {
-          results.push({
-            name: key,
-            editorSrc: value.annotationEditorSrc,
-          });
-        }
-      });
-      return results;
+      return annotationSources;
     };
 
-    this.listOptions = function() {
-      return _.map(config.datasources, function(value, key) {
-        return {
-          name: value.default ? key + ' (default)' : key,
-          value: value.default ? null : key
-        };
-      });
+    this.getMetricSources = function() {
+      return metricSources;
+    };
+
+    this.getGrafanaDB = function() {
+      return grafanaDB;
     };
 
     this.init();

+ 0 - 116
src/app/services/elasticsearch/es-client.js

@@ -1,116 +0,0 @@
-define([
-  'angular',
-  'config'
-],
-function(angular, config) {
-  "use strict";
-
-  var module = angular.module('grafana.services');
-
-  module.service('elastic', function($http, $q) {
-
-    this._request = function(method, url, data) {
-      var options = {
-        url: config.elasticsearch + "/" + config.grafana_index + url,
-        method: method,
-        data: data
-      };
-
-      if (config.elasticsearchBasicAuth) {
-        options.headers = {
-          "Authorization": "Basic " + config.elasticsearchBasicAuth
-        };
-      }
-
-      return $http(options);
-    };
-
-    this.get = function(url) {
-      return this._request('GET', url)
-        .then(function(results) {
-          return results.data;
-        });
-    };
-
-    this.post = function(url, data) {
-      return this._request('POST', url, data)
-        .then(function(results) {
-          return results.data;
-        });
-    };
-
-    this.deleteDashboard = function(id) {
-      if (!this.isAdmin()) { return $q.reject("Invalid admin password"); }
-
-      return this._request('DELETE', '/dashboard/' + id)
-        .then(function(result) {
-          return result.data._id;
-        }, function(err) {
-          throw err.data;
-        });
-    };
-
-    this.saveForSharing = function(dashboard) {
-      var data = {
-        user: 'guest',
-        group: 'guest',
-        title: dashboard.title,
-        tags: dashboard.tags,
-        dashboard: angular.toJson(dashboard)
-      };
-
-      var ttl = dashboard.loader.save_temp_ttl;
-
-      return this._request('POST', '/temp/?ttl=' + ttl, data)
-        .then(function(result) {
-
-          var baseUrl = window.location.href.replace(window.location.hash,'');
-          var url = baseUrl + "#dashboard/temp/" + result.data._id;
-
-          return { title: dashboard.title, url: url };
-
-        }, function(err) {
-          throw "Failed to save to temp dashboard to elasticsearch " + err.data;
-        });
-    };
-
-    this.passwordCache = function(pwd) {
-      if (!window.sessionStorage) { return null; }
-      if (!pwd) { return window.sessionStorage["grafanaAdminPassword"]; }
-      window.sessionStorage["grafanaAdminPassword"] = pwd;
-    };
-
-    this.isAdmin = function() {
-      if (!config.admin || !config.admin.password) { return true; }
-      if (this.passwordCache() === config.admin.password) { return true; }
-
-      var password = window.prompt("Admin password", "");
-      this.passwordCache(password);
-
-      return password === config.admin.password;
-    };
-
-    this.saveDashboard = function(dashboard, title) {
-      if (!this.isAdmin()) { return $q.reject("Invalid admin password"); }
-
-      var dashboardClone = angular.copy(dashboard);
-      title = dashboardClone.title = title ? title : dashboard.title;
-
-      var data = {
-        user: 'guest',
-        group: 'guest',
-        title: title,
-        tags: dashboardClone.tags,
-        dashboard: angular.toJson(dashboardClone)
-      };
-
-      return this._request('PUT', '/dashboard/' + encodeURIComponent(title), data)
-        .then(function() {
-          return { title: title, url: '/dashboard/elasticsearch/' + title };
-        }, function(err) {
-          throw 'Failed to save to elasticsearch ' + err.data;
-        });
-    };
-
-  });
-});

+ 187 - 0
src/app/services/elasticsearch/es-datasource.js

@@ -19,10 +19,197 @@ function (angular, _, $, config, kbn, moment) {
       this.url = datasource.url;
       this.name = datasource.name;
       this.supportAnnotations = true;
+      this.supportMetrics = false;
+      this.index = datasource.index;
+      this.grafanaDB = datasource.grafanaDB;
       this.annotationEditorSrc = 'app/partials/elasticsearch/annotation_editor.html';
     }
 
+    ElasticDatasource.prototype._request = function(method, url, index, data) {
+      var options = {
+        url: this.url + "/" + index + url,
+        method: method,
+        data: data
+      };
+
+      if (this.basicAuth) {
+        options.headers = {
+          "Authorization": "Basic " + this.basicAuth
+        };
+      }
+
+      return $http(options);
+    };
+
+    ElasticDatasource.prototype._get = function(url) {
+      return this._request('GET', url, this.index)
+        .then(function(results) {
+          return results.data;
+        });
+    };
+
+    ElasticDatasource.prototype._post = function(url, data) {
+      return this._request('POST', url, this.index, data)
+        .then(function(results) {
+          return results.data;
+        });
+    };
+
     ElasticDatasource.prototype.annotationQuery = function(annotation, filterSrv, rangeUnparsed) {
+      var range = {};
+      var timeField = annotation.timeField || '@timestamp';
+      var queryString = annotation.query || '*';
+      var tagsField = annotation.tagsField || 'tags';
+      var titleField = annotation.titleField || 'desc';
+      var textField = annotation.textField || null;
+
+      range[annotation.timeField]= {
+        from: rangeUnparsed.from,
+        to: rangeUnparsed.to,
+      };
+
+      var filter = { "bool": { "must": [{ "range": range }] } };
+      var query = { "bool": { "should": [{ "query_string": { "query": queryString } }] } };
+      var data = { "query" : { "filtered": { "query" : query, "filter": filter } }, "size": 100 };
+
+      return this._request('POST', '/_search', annotation.index, data).then(function(results) {
+        var list = [];
+        var hits = results.data.hits.hits;
+
+        for (var i = 0; i < hits.length; i++) {
+          var source = hits[i]._source;
+          var event = {
+            annotation: annotation,
+            time: moment.utc(source[timeField]).valueOf(),
+            title: source[titleField],
+          };
+
+          if (source[tagsField]) {
+            if (_.isArray(source[tagsField])) {
+              event.tags = source[tagsField].join(', ');
+            }
+            else {
+              event.tags = source[tagsField];
+            }
+          }
+          if (textField && source[textField]) {
+            event.text = source[textField];
+          }
+
+          list.push(event);
+        }
+        return list;
+      });
+    };
+
+    ElasticDatasource.prototype.getDashboard = function(id) {
+      var url = '/dashboard/' + id;
+
+      // hack to check if it is a temp dashboard
+      if (window.location.href.indexOf('dashboard/temp') > 0) {
+        url = '/temp/' + id;
+      }
+
+      return this._get(url)
+        .then(function(result) {
+          if (result._source && result._source.dashboard) {
+            return angular.fromJson(result._source.dashboard);
+          } else {
+            return false;
+          }
+        }, function(data) {
+          if(data.status === 0) {
+            throw "Could not contact Elasticsearch. Please ensure that Elasticsearch is reachable from your browser.";
+          } else {
+            throw "Could not find dashboard " + id;
+          }
+        });
+    };
+
+    ElasticDatasource.prototype.saveDashboard = function(dashboard, title) {
+      var dashboardClone = angular.copy(dashboard);
+      title = dashboardClone.title = title ? title : dashboard.title;
+
+      var data = {
+        user: 'guest',
+        group: 'guest',
+        title: title,
+        tags: dashboardClone.tags,
+        dashboard: angular.toJson(dashboardClone)
+      };
+
+      return this._request('PUT', '/dashboard/' + encodeURIComponent(title), this.index, data)
+        .then(function() {
+          return { title: title, url: '/dashboard/elasticsearch/' + title };
+        }, function(err) {
+          throw 'Failed to save to elasticsearch ' + err.data;
+        });
+    };
+
+    ElasticDatasource.prototype.saveDashboardTemp = function(dashboard) {
+      var data = {
+        user: 'guest',
+        group: 'guest',
+        title: dashboard.title,
+        tags: dashboard.tags,
+        dashboard: angular.toJson(dashboard)
+      };
+
+      var ttl = dashboard.loader.save_temp_ttl;
+
+      return this._request('POST', '/temp/?ttl=' + ttl, this.index, data)
+        .then(function(result) {
+
+          var baseUrl = window.location.href.replace(window.location.hash,'');
+          var url = baseUrl + "#dashboard/temp/" + result.data._id;
+
+          return { title: dashboard.title, url: url };
+
+        }, function(err) {
+          throw "Failed to save to temp dashboard to elasticsearch " + err.data;
+        });
+    };
+
+    ElasticDatasource.prototype.deleteDashboard = function(id) {
+      return this._request('DELETE', '/dashboard/' + id, this.index)
+        .then(function(result) {
+          return result.data._id;
+        }, function(err) {
+          throw err.data;
+        });
+    };
+
+    ElasticDatasource.prototype.searchDashboards = function(queryString) {
+      var tagsOnly = queryString.indexOf('tags!:') === 0;
+      if (tagsOnly) {
+        var tagsQuery = queryString.substring(6, queryString.length);
+        queryString = 'tags:' + tagsQuery + '*';
+      }
+      else {
+        if (queryString.length === 0) {
+          queryString = 'title:';
+        }
+
+        if (queryString[queryString.length - 1] !== '*') {
+          queryString += '*';
+        }
+      }
+
+      var query = {
+        query: { query_string: { query: queryString } },
+        facets: { tags: { terms: { field: "tags", order: "term", size: 50 } } },
+        size: 20,
+        sort: ["_uid"]
+      };
+
+      return this._post('/dashboard/_search', query)
+        .then(function(results) {
+          if(_.isUndefined(results.hits)) {
+            return { dashboards: [], tags: [] };
+          }
+
+          return { dashboards: results.hits.hits, tags: results.facets.terms };
+        });
     };
 
     return ElasticDatasource;

+ 1 - 0
src/app/services/graphite/graphiteDatasource.js

@@ -21,6 +21,7 @@ function (angular, _, $, config, kbn, moment) {
       this.name = datasource.name;
       this.render_method = datasource.render_method || 'POST';
       this.supportAnnotations = true;
+      this.supportMetrics = true;
       this.annotationEditorSrc = 'app/partials/graphite/annotation_editor.html';
       this.cacheTimeout = datasource.cacheTimeout;
     }

+ 1 - 0
src/app/services/influxdb/influxdbDatasource.js

@@ -23,6 +23,7 @@ function (angular, _, kbn, InfluxSeries) {
       };
 
       this.supportAnnotations = true;
+      this.supportMetrics = true;
       this.annotationEditorSrc = 'app/partials/influxdb/annotation_editor.html';
     }
 

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
src/css/bootstrap.dark.min.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
src/css/bootstrap.light.min.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
src/css/default.min.css


+ 3 - 1
src/css/less/graph.less

@@ -128,4 +128,6 @@
   }
 }
 
-
+.annotation-tags {
+  color: @purple;
+}

+ 0 - 1
tasks/options/ngmin.js

@@ -12,7 +12,6 @@ module.exports = function(config) {
         'app/routes/**/*.js',
         'app/app.js',
         'vendor/angular/**/*.js',
-        'vendor/elasticjs/elastic-angular-client.js'
       ],
       dest: '<%= tempDir %>'
     }

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.