From a956a36c965e9a8295059796b4fe6ca577a6a3a8 Mon Sep 17 00:00:00 2001 From: Rashid Khan Date: Fri, 15 Feb 2013 15:23:04 -0700 Subject: [PATCH] Fixed typo in elastic angular client, added dash loader panel, removed dashboards.js, replaced with default.json, added global alerts, add some display options for histogram --- common/css/main.css | 4 + common/lib/elastic-angular-client.js | 2 +- common/lib/elastic-angular-client.min.js | 2 +- config.js | 18 +- dashboards.js | 267 ------------------ default.json | 328 +++++++++++++++++++++++ index.html | 10 +- js/app.js | 1 - js/controllers.js | 73 ++--- js/services.js | 12 +- panels/dashcontrol/editor.html | 32 +++ panels/dashcontrol/load.html | 37 +++ panels/dashcontrol/module.html | 4 + panels/dashcontrol/module.js | 209 +++++++++++++++ panels/dashcontrol/save.html | 28 ++ panels/histogram/editor.html | 4 +- panels/histogram/module.html | 4 +- panels/histogram/module.js | 9 +- partials/roweditor.html | 21 +- 19 files changed, 722 insertions(+), 343 deletions(-) delete mode 100644 dashboards.js create mode 100644 default.json create mode 100644 panels/dashcontrol/editor.html create mode 100644 panels/dashcontrol/load.html create mode 100644 panels/dashcontrol/module.html create mode 100644 panels/dashcontrol/module.js create mode 100644 panels/dashcontrol/save.html diff --git a/common/css/main.css b/common/css/main.css index e781c12b0b3..7bad2ccb853 100644 --- a/common/css/main.css +++ b/common/css/main.css @@ -40,6 +40,10 @@ opacity: 1; } +.popover { + max-width: 500px; +} + /* .row-header i.editlink { opacity: 0; diff --git a/common/lib/elastic-angular-client.js b/common/lib/elastic-angular-client.js index 8a3d660295c..a3811909ec2 100644 --- a/common/lib/elastic-angular-client.js +++ b/common/lib/elastic-angular-client.js @@ -60,7 +60,7 @@ angular.module('elasticjs.service', []) }, del: function (path, data, successcb, errorcb) { path = url + path; - return promiseThen($http.delee(path, data), successcb, errorcb); + return promiseThen($http.delete(path, data), successcb, errorcb); }, head: function (path, data, successcb, errorcb) { path = url + path; diff --git a/common/lib/elastic-angular-client.min.js b/common/lib/elastic-angular-client.min.js index 522b9e99d27..a88c594c820 100644 --- a/common/lib/elastic-angular-client.min.js +++ b/common/lib/elastic-angular-client.min.js @@ -1,4 +1,4 @@ /*! elastic.js - v1.0.0 - 2013-01-15 * https://github.com/fullscale/elastic.js * Copyright (c) 2013 FullScale Labs, LLC; Licensed MIT */ -"use strict";angular.module("elasticjs.service",[]).factory("ejsResource",["$http",function(e){return function(t){var n=window.ejs||{},r=function(e,t,n){return e.then(function(e){return(t||angular.noop)(e.data),e.data},function(e){return(n||angular.noop)(undefined),undefined})};return t==null&&(t=""),n.client={server:function(e){return e==null?t:(t=e,this)},post:function(n,i,s,o){return n=t+n,r(e.post(n,i),s,o)},get:function(n,i,s,o){return n=t+n,r(e.get(n,i),s,o)},put:function(n,i,s,o){return n=t+n,r(e.put(n,i),s,o)},del:function(n,i,s,o){return n=t+n,r(e.delee(n,i),s,o)},head:function(n,r,i,s){return n=t+n,e.head(n,r).then(function(e){return(i||angular.noop)(e.headers()),e.headers()},function(e){return(s||angular.noop)(undefined),undefined})}},n}}]); \ No newline at end of file +"use strict";angular.module("elasticjs.service",[]).factory("ejsResource",["$http",function(e){return function(t){var n=window.ejs||{},r=function(e,t,n){return e.then(function(e){return(t||angular.noop)(e.data),e.data},function(e){return(n||angular.noop)(undefined),undefined})};return t==null&&(t=""),n.client={server:function(e){return e==null?t:(t=e,this)},post:function(n,i,s,o){return n=t+n,r(e.post(n,i),s,o)},get:function(n,i,s,o){return n=t+n,r(e.get(n,i),s,o)},put:function(n,i,s,o){return n=t+n,r(e.put(n,i),s,o)},del:function(n,i,s,o){return n=t+n,r(e.delete(n,i),s,o)},head:function(n,r,i,s){return n=t+n,e.head(n,r).then(function(e){return(i||angular.noop)(e.headers()),e.headers()},function(e){return(s||angular.noop)(undefined),undefined})}},n}}]); diff --git a/config.js b/config.js index f03cc256efa..6f2508fcd85 100644 --- a/config.js +++ b/config.js @@ -1,8 +1,5 @@ /* -The settings before the break are the only ones that are currently implemented -The remaining settings do nothing - elasticsearch: URL to your elasticsearch server timeformat: Format for time in histograms (might go away) modules: Panel modules to load. In the future these will be inferred @@ -13,25 +10,12 @@ NOTE: No timezone support yet, everything is in UTC at the moment. If you need to configure the default dashboard, please see dashboard.js -shared.json contains an example sharable dashboard. Note the subtle differences -between dashboard.js and shared.json. One is a javascript object, the other is -json. - */ var config = new Settings( { elasticsearch: 'http://localhost:9200', timeformat: 'mm/dd HH:MM:ss', modules: ['histogram','map','pie','table','stringquery','sort', - 'timepicker','text','fields','hits'], - - perpage: 50, - timezone: 'user', - operator: 'OR', - exportdelim: ',', - smartindex: true, - indexlimit: 150, - indexdefault: 'logstash-*', - primaryfield: '_all' + 'timepicker','text','fields','hits','dashcontrol'], } ); diff --git a/dashboards.js b/dashboards.js deleted file mode 100644 index c9a09967e4b..00000000000 --- a/dashboards.js +++ /dev/null @@ -1,267 +0,0 @@ -var dashboards = -{ - title: "Infinite Monkey Dashboard", - rows: [ - { - title: "Query Control", - height: "30px", - panels: [ - { - type : "stringquery", - span : 12, - group : ['default','counter','histogram'] - } - ] - }, - { - title: "Status", - collapse: false, - height: "100px", - panels: [ - { - type : "timepicker", - span : 4, - mode : 'relative', - index : "\"shakespeare\"", - refresh : { - enable : false, - interval: 30, - min : 10 - }, - timespan : '1h', - timefield: '@timestamp', - group: ['default','pies'], - }, - { - type : "sort", - span : 3, - }, - { - title : "Histogram Timer", - type : "timepicker", - span : 0, - mode : 'relative', - timespan : '5m', - index : "\"shakespeare\"", - refresh : { - enable : true, - interval: 3, - min : 10 - }, - timefield: '@timestamp', - group: 'histogram', - }, - { - type : "histogram", - span : 3, - show : ['lines'], - fill : 0.3, - group : "histogram", - query : [ - { label : "Event Rate", query : "*", color: '#FF7400' } - ], - }, - { - title : "Counter Timer", - type : "timepicker", - span : 0, - mode : 'relative', - timespan : '30d', - index : "\"shakespeare\"", - refresh : { - enable : true, - interval: 3, - min : 10 - }, - timefield: '@timestamp', - group: 'counter', - }, - { - type : "hits", - title : "Lines Completed", - span : 2, - group : 'counter', - }, - { - type : "text", - style : {"font-size":"85%"}, - span: 0, - content : "Rows are collapsable, and input panels can send event to" + - " multiple groups. The Search panel is part of one group, while" + - " the time panel is part of two" - }, - ] - }, - { - title: "Top 3 Characters", - collapse: true, - height: "150px", - panels: [ - { - type : "text", - title : "About", - style : {"font-size":"85%"}, - span: 2, - content : "These donut charts demonstrate configurable binding." + - " They exist in a different group from the other panels and are" + - " bound only to the time selector, not to the query input. Thus" + - " they will change when you select a new time range, but not if" + - " you enter a search.", - }, - { - title : "Hamlet", - type : "pie", - span : 2, - size : 3, - legend : false, - labels : false, - donut : true, - colors : ['#20805E','#26527C','#BF8530','#A60000','#006363','#679B00'], - field : 'country', - //query : { query: "*", field: "country"} - query : { field : "speaker", query : "play_name:Hamlet" }, - group : "pies" - }, - { - title : "Othello", - type : "pie", - span : 2, - size : 3, - legend : false, - labels : false, - donut : true, - colors : ['#35D59D','#FFB140','#F43D6B','#A60000','#006363','#679B00'], - field : 'country', - //query : { query: "*", field: "country"} - query : { field : "speaker", query : "play_name:Othello" }, - group : "pies" - }, - { - title : "A Winters Tale", - type : "pie", - span : 2, - size : 3, - legend : false, - labels : false, - donut : true, - colors : ['#78AF2C','#BF4630','#6A237E','#A60000','#006363','#679B00'], - field : 'country', - //query : { query: "*", field: "country"} - query : { field : "speaker", query : 'play_name:"A Winters Tale"' }, - group : "pies" - }, - { - title : "The Tempest", - type : "pie", - span : 2, - size : 3, - legend : false, - labels : false, - donut : true, - colors : ['#2A4480','#BFA730','#BF7130','#A60000','#006363','#679B00'], - field : 'country', - //query : { query: "*", field: "country"} - query : { field : "speaker", query : 'play_name:"The Tempest"' }, - group : "pies" - }, - { - title : "King Lear", - type : "pie", - span : 2, - size : 3, - legend : false, - labels : false, - donut : true, - colors : ['#01939A','#FFAB00','#FF0700','#A60000','#006363','#679B00'], - field : 'country', - //query : { query: "*", field: "country"} - query : { field : "speaker", query : 'play_name:"King Lear"' }, - group : "pies" - }, - ] - }, - { - title: "Lines of Plays", - height: "210px", - collapse: false, - panels: [ - { - title : "Plays", - type : "pie", - span : 4, - size : 8, - labels : false, - colors : ['#BF3030','#1D7373','#86B32D','#A60000','#006363','#679B00'], - field : 'country', - mode : "terms", - query : { query:"*", field:"play_name" } - }, - { - type : "text", - title : "About", - style : {"font-size":"85%"}, - span: 0, - content : "The table panel can be sorted via a sort panel, or by" + - " clicking the table header. Unlike the donut charts above, this" + - " pie is bound to the query input. Try searching for a speaker" + - " (eg, FALSTAFF) to see a break down of the plays they appear in.", - }, - { - title : "Newest Lines", - editable: true, - type : "table", - span : 6, - query : "*", - style : {"font-size":"85%"}, - fields : ['@timestamp','play_name','speaker','text_entry'], - }, - { - type : "fields", - title : "Fields", - span : 2, - }, - ] - }, - - { - title: "Monkey Monitoring", - collapse: false, - height: "225px", - panels: [ - { - title : "Monkey Shakespeare Lines", - type : "histogram", - span : 5, - show : ['bars','stack'], - fill : 1, - query : [ - { label : "Query Hits", query : "*", color: '#86B32D' }, - { label : "Hamlet", query : "play_name:Hamlet" }, - { label : "Macbeth", query : "play_name:macbeth" }, - ], - }, - { - title : "Monkey Typists Worldwide", - type : "map", - map : 'world', - field : "country", - span : 5, - size : 500, - query : "*", - }, - { - type : "text", - title : "About", - style : {"font-size":"85%"}, - span: 2, - content : "Histograms can show multiple queries. In the case that a" + - " multi-query histogram is bound to a query input, only the first" + - " data series will be altered. All panels exist in the 'default'" + - " group by default. The map panel can be used to visualize events" + - " with attached geo data.", - }, - ] - } - - ] -}; diff --git a/default.json b/default.json new file mode 100644 index 00000000000..6573d9790e4 --- /dev/null +++ b/default.json @@ -0,0 +1,328 @@ +{ + "title": "Infinite Monkey Dashboard", + "rows": [ + { + "title": "Query Control", + "height": "30px", + "panels": [ + { + "type": "stringquery", + "span": 12, + "group": [ + "default", + "counter", + "histogram" + ], + "label": "Search", + "query": "*", + "size": 100, + "sort": [ + "@timestamp", + "desc" + ] + } + ], + "collapse": false, + "editable": true + }, + { + "title": "Status", + "collapse": false, + "height": "50px", + "panels": [ + { + "type": "timepicker", + "span": 5, + "mode": "relative", + "index": "\"shakespeare\"", + "refresh": { + "enable": false, + "interval": 30, + "min": 10 + }, + "timespan": "1h", + "timefield": "@timestamp", + "group": [ + "default", + "pies" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + { + "title": "Histogram Timer", + "type": "timepicker", + "span": 0, + "mode": "relative", + "timespan": "5m", + "index": "\"shakespeare\"", + "refresh": { + "enable": true, + "interval": 10, + "min": 10 + }, + "timefield": "@timestamp", + "group": "histogram", + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + { + "type": "histogram", + "span": 2, + "show": [ + "lines", + "y-axis" + ], + "fill": 0.3, + "group": "histogram", + "query": [ + { + "label": "Event Rate", + "query": "*", + "color": "#FF7400" + } + ], + "interval": "5s", + "index": "shakespeare", + "title": "Lines per 5s" + }, + { + "title": "Counter Timer", + "type": "timepicker", + "span": 0, + "mode": "relative", + "timespan": "30d", + "index": "\"shakespeare\"", + "refresh": { + "enable": true, + "interval": 10, + "min": 10 + }, + "timefield": "@timestamp", + "group": "counter", + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + { + "type": "hits", + "title": "Lines Completed", + "span": 2, + "group": "counter", + "query": "*", + "style": { + "font-size": "36pt", + "font-weight": "bold" + }, + "index": "shakespeare", + "error": false + }, + { + "type": "text", + "style": { + "font-size": "85%" + }, + "span": 0, + "content": "Rows are collapsable, and input panels can send event to multiple groups. The Search panel is part of one group, while the time panel is part of two", + "group": "default" + }, + { + "title": "Dashboard Loader", + "type": "dashcontrol", + "span": 3, + "group": "default", + "save": { + "gist": true, + "elasticsearch": true, + "local": true, + "default": true + }, + "load": { + "gist": true, + "elasticsearch": true, + "local": true + }, + "elasticsearch_size": 20, + "error": false + } + ], + "editable": true + }, + { + "title": "Lines of Plays", + "height": "210px", + "collapse": false, + "panels": [ + { + "title": "Plays", + "type": "pie", + "span": 4, + "size": 8, + "labels": false, + "colors": [ + "#BF3030", + "#1D7373", + "#86B32D", + "#A60000", + "#006363", + "#679B00" + ], + "field": "country", + "mode": "terms", + "query": { + "query": "*", + "field": "play_name" + }, + "exclude": [], + "donut": false, + "tilt": false, + "legend": true, + "group": "default", + "default_field": "_all", + "index": "shakespeare" + }, + { + "type": "text", + "title": "About", + "style": { + "font-size": "85%" + }, + "span": 0, + "content": "The table panel can be sorted via a sort panel, or by clicking the table header. Unlike the donut charts above, this pie is bound to the query input. Try searching for a speaker (eg, FALSTAFF) to see a break down of the plays they appear in.", + "group": "default" + }, + { + "title": "Newest Lines", + "editable": true, + "type": "table", + "span": 6, + "query": "*", + "style": { + "font-size": "85%" + }, + "fields": [ + "@timestamp", + "play_name", + "speaker", + "text_entry" + ], + "size": 100, + "sort": [ + "@timestamp", + "desc" + ], + "group": "default", + "index": "shakespeare", + "error": false + }, + { + "type": "fields", + "title": "Fields", + "span": 2, + "group": "default", + "style": { + "font-size": "85%", + "line-height": "15px" + }, + "sort": [ + "@timestamp", + "desc" + ] + } + ], + "editable": true + }, + { + "title": "Monkey Monitoring", + "collapse": false, + "height": "225px", + "panels": [ + { + "title": "Monkey Shakespeare Lines", + "type": "histogram", + "span": 5, + "show": [ + "bars", + "stack", + "legend", + "x-axis", + "y-axis" + ], + "fill": 1, + "query": [ + { + "label": "Query Hits", + "query": "*", + "color": "#86B32D" + }, + { + "label": "Hamlet", + "query": "play_name:Hamlet" + }, + { + "label": "Macbeth", + "query": "play_name:macbeth" + } + ], + "interval": "1m", + "group": "default", + "index": "shakespeare" + }, + { + "title": "Monkey Typists Worldwide", + "type": "map", + "map": "world", + "field": "country", + "span": 5, + "size": 500, + "query": "*", + "colors": [ + "#C8EEFF", + "#0071A4" + ], + "exclude": [], + "group": "default", + "index": "shakespeare" + }, + { + "type": "text", + "title": "About", + "style": { + "font-size": "85%" + }, + "span": 2, + "content": "Histograms can show multiple queries. In the case that a multi-query histogram is bound to a query input, only the first data series will be altered. All panels exist in the 'default' group by default. The map panel can be used to visualize events with attached geo data.", + "group": "default" + } + ], + "editable": true + } + ], + "editable": true +} \ No newline at end of file diff --git a/index.html b/index.html index 424da916d0a..ceca3003c11 100644 --- a/index.html +++ b/index.html @@ -29,21 +29,19 @@ +
+ + {{alert.title}}
{{$index + 1}} alert(s)
+
-
diff --git a/js/app.js b/js/app.js index dde960f3ff7..bba227ceb86 100644 --- a/js/app.js +++ b/js/app.js @@ -31,7 +31,6 @@ var labjs = $LAB .script("common/lib/datepicker.js") .script("common/lib/shared.js") .script("common/lib/filesaver.js") - .script("dashboards.js") .script("js/services.js") .script("js/controllers.js") .script("js/filters.js") diff --git a/js/controllers.js b/js/controllers.js index 0cc87f4319d..2f7a432f2cb 100644 --- a/js/controllers.js +++ b/js/controllers.js @@ -3,7 +3,7 @@ 'use strict'; angular.module('kibana.controllers', []) -.controller('DashCtrl', function($scope, $rootScope, $http, ejsResource, timer) { +.controller('DashCtrl', function($scope, $rootScope, $http, $timeout, ejsResource, eventBus) { var _d = { title: "", @@ -15,6 +15,7 @@ angular.module('kibana.controllers', []) $scope.config = config; $scope._ = _; $scope.reset_row(); + $scope.clear_all_alerts(); // The global dashboards object should be moved to an $http request for json if (Modernizr.localstorage && @@ -22,39 +23,28 @@ angular.module('kibana.controllers', []) localStorage['dashboard'] !== '' ) { $scope.dashboards = JSON.parse(localStorage['dashboard']); + _.defaults($scope.dashboards,_d); } else { - $scope.dashboards = dashboards + $http({ + url: "default.json", + method: "GET", + }).success(function(data, status, headers, config) { + $scope.dashboards = data + _.defaults($scope.dashboards,_d); + }).error(function(data, status, headers, config) { + $scope.alert('Default dashboard missing!','Could not locate default.json','error') + }); } - _.defaults($scope.dashboards,_d) + eventBus.register($scope,'dashboard', function(event,dashboard){ + console.log('got broadcast') + $scope.dashboards = dashboard; + _.defaults($scope.dashboards,_d) + }) var ejs = $scope.ejs = ejsResource(config.elasticsearch); } - - $scope.export = function() { - var blob = new Blob([angular.toJson($scope.dashboards,true)], {type: "application/json;charset=utf-8"}); - saveAs(blob, $scope.dashboards.title+"-"+new Date().getTime()); - } - - $scope.default = function() { - if (Modernizr.localstorage) { - localStorage['dashboard'] = angular.toJson($scope.dashboards); - alert($scope.dashboards.title + " has been set as your default dashboard") - } else { - alert("Sorry, your browser is too old for this functionality"); - } - } - - $scope.purge = function() { - if (Modernizr.localstorage) { - localStorage['dashboard'] = ''; - alert('Default dashboard cleared') - } else { - alert("Sorry, your browser is too old for this functionality"); - } - } - $scope.add_row = function(dashboards,row) { $scope.dashboards.rows.push(row); } @@ -67,11 +57,32 @@ angular.module('kibana.controllers', []) }; }; - $scope.init(); + $scope.alert = function(title,text,severity,timeout) { + var alert = { + title: title, + text: text, + severity: severity || 'info', + }; + $scope.global_alert.push(alert); + if (timeout > 0) + $timeout(function() { + $scope.global_alert = _.without($scope.global_alert,alert) + console.log($scope.global_alert) + }, timeout); + } + $scope.clear_alert = function(alert) { + $scope.global_alert = _.without($scope.global_alert,alert); + } + + $scope.clear_all_alerts = function() { + $scope.global_alert = [] + } + + $scope.init(); }) -.controller('RowCtrl', function($scope, $rootScope, $timeout, ejsResource, timer) { +.controller('RowCtrl', function($scope, $rootScope, $timeout, ejsResource) { var _d = { title: "Row", @@ -107,9 +118,9 @@ angular.module('kibana.controllers', []) $scope.reset_panel = function() { $scope.panel = { - span: 1, + span: 3, editable: true, - groups: ['default'], + group: ['default'], }; }; diff --git a/js/services.js b/js/services.js index 6fce70abd08..fdf799dc743 100644 --- a/js/services.js +++ b/js/services.js @@ -21,18 +21,22 @@ angular.module('kibana.services', []) // addressed to the scope in question and runs the registered function if it // is. this.register = function(scope,type,fn) { - console.log('registered:' + type + " for " + scope.panel.title + " " + scope.$id) scope.$on(type,function(event,packet){ var _id = scope.$id; var _to = packet.to; var _from = packet.from; + var _group = (!(_.isUndefined(scope.panel))) ? scope.panel.group : ["NONE"] + //console.log('registered:' + type + " for " + scope.panel.title + " " + scope.$id) if(!(_.isArray(_to))) _to = [_to]; - if(!(_.isArray(scope.panel.group))) - scope.panel.group = [scope.panel.group]; + if(!(_.isArray(_group))) + _group = [_group]; - if(_.intersection(_to,scope.panel.group).length > 0 || _.indexOf(_to,_id) > -1) { + if(_.intersection(_to,_group).length > 0 || + _.indexOf(_to,_id) > -1 || + _.indexOf(_to,'ALL') > -1 + ) { //console.log('Got: '+type + ' from ' + _from + ' to ' + _to + ': ' + angular.toJson(packet.data)) fn(event,packet.data); } diff --git a/panels/dashcontrol/editor.html b/panels/dashcontrol/editor.html new file mode 100644 index 00000000000..2cafe2e8a70 --- /dev/null +++ b/panels/dashcontrol/editor.html @@ -0,0 +1,32 @@ +
+
Allow saving to
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
Allow loading from
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
\ No newline at end of file diff --git a/panels/dashcontrol/load.html b/panels/dashcontrol/load.html new file mode 100644 index 00000000000..6cc0b3f379d --- /dev/null +++ b/panels/dashcontrol/load.html @@ -0,0 +1,37 @@ +
+
+
Local File
+
+
+
+
+
+
Gist Enter a gist number or url
+
+
+ +
Dashboards in gist:{{gist.url | gistid}} click to load
+
No gist dashboards found
+ + + + +
{{file.title}}
+
+
+
+
Elasticsearch
+
+ + +
+
Elasticsearch stored dashboards
+
No dashboards matching your query found
+ + + + + +
{{dashboard._id}}
+
+
\ No newline at end of file diff --git a/panels/dashcontrol/module.html b/panels/dashcontrol/module.html new file mode 100644 index 00000000000..1ef69c67724 --- /dev/null +++ b/panels/dashcontrol/module.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/panels/dashcontrol/module.js b/panels/dashcontrol/module.js new file mode 100644 index 00000000000..9b1f8ef317a --- /dev/null +++ b/panels/dashcontrol/module.js @@ -0,0 +1,209 @@ +angular.module('kibana.dashcontrol', []) +.controller('dashcontrol', function($scope, $http, eventBus, timer) { + + var _id = _.uniqueId(); + + // Set and populate defaults + var _d = { + group : "default", + save : { + gist: true, + elasticsearch: true, + local: true, + 'default': true + }, + load : { + gist: true, + elasticsearch: true, + local: true, + }, + elasticsearch_size: 20, + } + _.defaults($scope.panel,_d); + + $scope.init = function() { + $scope.gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/; + $scope.gist = {}; + $scope.elasticsearch = {}; + } + + $scope.export = function() { + var blob = new Blob([angular.toJson($scope.dashboards,true)], {type: "application/json;charset=utf-8"}); + saveAs(blob, $scope.dashboards.title+"-"+new Date().getTime()); + } + + $scope.default = function() { + if (Modernizr.localstorage) { + localStorage['dashboard'] = angular.toJson($scope.dashboards); + $scope.alert('Success', + $scope.dashboards.title + " has been set as your default dashboard", + 'success',5000) + } else { + $scope.alert('Bummer!', + "Your browser is too old for this functionality", + 'error',5000); + } + } + + $scope.purge = function() { + if (Modernizr.localstorage) { + localStorage['dashboard'] = ''; + $scope.alert('Success', + 'Default dashboard cleared', + 'success',5000) + } else { + $scope.alert('Doh!', + "Your browser is too old for this functionality", + 'error',5000); + } + } + + $scope.save_elasticsearch = function() { + var save = _.clone($scope.dashboards) + save.title = $scope.elasticsearch.title; + var result = $scope.ejs.Document('kibana-int','dashboard',save.title).source({ + user: 'guest', + group: 'guest', + title: save.title, + dashboard: angular.toJson(save) + }).doIndex(); + result.then(function(result) { + $scope.alert('Dashboard Saved','This dashboard has been saved to Elasticsearch','success',5000) + $scope.elasticsearch_dblist($scope.elasticsearch.query); + $scope.elasticsearch.title = ''; + }) + } + + $scope.delete_elasticsearch = function(dashboard) { + var result = $scope.ejs.Document('kibana-int','dashboard',dashboard._id).doDelete(); + result.then(function(result) { + $scope.alert('Dashboard Deleted','','success',5000) + $scope.elasticsearch.dashboards = _.without($scope.elasticsearch.dashboards,dashboard) + }) + } + + $scope.elasticsearch_dblist = function(query) { + if($scope.panel.load.elasticsearch) { + var request = $scope.ejs.Request().indices('kibana-int').types('dashboard'); + var results = request.query( + $scope.ejs.QueryStringQuery(query || '*') + ).size($scope.panel.elasticsearch_size).doSearch(); + results.then(function(results) { + if(_.isUndefined(results)) { + $scope.panel.error = 'Your query was unsuccessful'; + return; + } + $scope.panel.error = false; + $scope.hits = results.hits.total; + $scope.elasticsearch.dashboards = results.hits.hits + }); + } + } + + $scope.save_gist = function() { + var save = _.clone($scope.dashboards) + save.title = $scope.gist.title; + $http({ + url: "https://api.github.com/gists", + method: "POST", + data: { + "description": save.title, + "public": false, + "files": { + "kibana-dashboard.json": { + "content": angular.toJson(save,true) + } + } + } + }).success(function(data, status, headers, config) { + $scope.gist.last = data.html_url; + $scope.alert('Gist saved','You will be able to access your exported dashboard file at '+data.html_url+' in a moment','success') + }).error(function(data, status, headers, config) { + $scope.alert('Unable to save','Save to gist failed for some reason','error',5000) + }); + } + + $scope.gist_dblist = function(id) { + $http({ + url: "https://api.github.com/gists/"+id, + method: "GET" + }).success(function(data, status, headers, config) { + $scope.gist.files = [] + _.each(data.files,function(v,k) { + try { + var file = JSON.parse(v.content) + $scope.gist.files.push(file) + } catch(e) { + $scope.alert('Gist failure','The dashboard file is invalid','warning',5000) + } + }); + }).error(function(data, status, headers, config) { + $scope.alert('Gist Failed','Could not retrieve dashboard list from gist','error',5000) + }); + } + + $scope.dash_load = function(dashboard) { + if(!_.isObject(dashboard)) + dashboard = JSON.parse(dashboard) + + eventBus.broadcast($scope.$id,'ALL','dashboard',dashboard) + timer.cancel_all(); + } + + $scope.gist_id = function(string) { + if($scope.is_gist(string)) + return string.match($scope.gist_pattern)[0].replace(/.*\//, ''); + } + + $scope.is_gist = function(string) { + if(!_.isUndefined(string) && string != '' && !_.isNull(string.match($scope.gist_pattern))) + return string.match($scope.gist_pattern).length > 0 ? true : false; + else + return false + } + + $scope.init(); + + +}) +.directive('dashUpload', function(timer, eventBus){ + return { + restrict: 'A', + link: function(scope, elem, attrs) { + function file_selected(evt) { + var files = evt.target.files; // FileList object + + // files is a FileList of File objects. List some properties. + var output = []; + for (var i = 0, f; f = files[i]; i++) { + var reader = new FileReader(); + reader.onload = (function(theFile) { + return function(e) { + scope.dash_load(JSON.parse(e.target.result)) + scope.$apply(); + }; + })(f); + reader.readAsText(f); + } + } + + // Check for the various File API support. + if (window.File && window.FileReader && window.FileList && window.Blob) { + // Something + document.getElementById('dashupload').addEventListener('change', file_selected, false); + } else { + alert('Sorry, the HTML5 File APIs are not fully supported in this browser.'); + } + } + } +}).filter('gistid', function() { + var gist_pattern = /(\d{5,})|([a-z0-9]{10,})|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/; + return function(input, scope) { + //return input+"boners" + if(!(_.isUndefined(input))) { + var output = input.match(gist_pattern); + if(!_.isNull(output) && !_.isUndefined(output)) + return output[0].replace(/.*\//, ''); + } + } +});; \ No newline at end of file diff --git a/panels/dashcontrol/save.html b/panels/dashcontrol/save.html new file mode 100644 index 00000000000..9c48852fc5c --- /dev/null +++ b/panels/dashcontrol/save.html @@ -0,0 +1,28 @@ +
+ +
+
Locally
+
+ +
+
+
+
Gist
+
+ + +

+ Last gist: {{gist.last}} +
+
+
Elasticsearch
+
+ + +
+
+
\ No newline at end of file diff --git a/panels/histogram/editor.html b/panels/histogram/editor.html index eddb63a6947..7e29817de22 100644 --- a/panels/histogram/editor.html +++ b/panels/histogram/editor.html @@ -33,8 +33,8 @@
- - + +
diff --git a/panels/histogram/module.html b/panels/histogram/module.html index 0d194ae2a71..dd94a34b7a9 100644 --- a/panels/histogram/module.html +++ b/panels/histogram/module.html @@ -1,5 +1,3 @@ -
-
-
+
\ No newline at end of file diff --git a/panels/histogram/module.js b/panels/histogram/module.js index ac0b2c419a7..f04dd1fa021 100644 --- a/panels/histogram/module.js +++ b/panels/histogram/module.js @@ -7,7 +7,7 @@ angular.module('kibana.histogram', []) var _d = { query : [ {query: "*", label:"Query"} ], interval: secondsToHms(calculate_interval($scope.from,$scope.to,40,0)/1000), - show : ['bars'], + show : ['bars','y-axis','x-axis','legend'], fill : false, group : "default", } @@ -133,6 +133,9 @@ angular.module('kibana.histogram', []) bars: _.indexOf(scope.panel.show,'bars') < 0 ? false : true, points: _.indexOf(scope.panel.show,'points') < 0 ? false : true, stack: _.indexOf(scope.panel.show,'stack') < 0 ? null : true, + legend: _.indexOf(scope.panel.show,'legend') < 0 ? false : true, + 'x-axis': _.indexOf(scope.panel.show,'x-axis') < 0 ? false : true, + 'y-axis': _.indexOf(scope.panel.show,'y-axis') < 0 ? false : true, } // Set barwidth based on specified interval @@ -147,6 +150,7 @@ angular.module('kibana.histogram', []) // Populate element $.plot(elem, scope.data, { legend: { + show: show.legend, position: "nw", labelFormatter: function(label, series) { return '' + label + ' / ' + @@ -160,8 +164,9 @@ angular.module('kibana.histogram', []) points: { show: show.points }, shadowSize: 1 }, - yaxis: { min: 0, color: "#000" }, + yaxis: { show: show['y-axis'], min: 0, color: "#000" }, xaxis: { + show: show['x-axis'], mode: "time", timeformat: "%H:%M:%S
%m-%d", label: "Datetime", diff --git a/partials/roweditor.html b/partials/roweditor.html index 866a5144f86..92dee236d77 100644 --- a/partials/roweditor.html +++ b/partials/roweditor.html @@ -15,6 +15,17 @@
+
+

New Panel

+ + Select Type +
+
+ + +
+
+

Panels

@@ -22,12 +33,14 @@ Title Type + Span Delete Move {{panel.title}} {{panel.type}} + @@ -35,14 +48,6 @@
-

New Panel

- - Select Type -
-
-
-
-