Commit a956a36c by Rashid Khan

Fixed typo in elastic angular client, added dash loader panel, removed…

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
parent d8f2d412
...@@ -40,6 +40,10 @@ ...@@ -40,6 +40,10 @@
opacity: 1; opacity: 1;
} }
.popover {
max-width: 500px;
}
/* /*
.row-header i.editlink { .row-header i.editlink {
opacity: 0; opacity: 0;
......
...@@ -60,7 +60,7 @@ angular.module('elasticjs.service', []) ...@@ -60,7 +60,7 @@ angular.module('elasticjs.service', [])
}, },
del: function (path, data, successcb, errorcb) { del: function (path, data, successcb, errorcb) {
path = url + path; 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) { head: function (path, data, successcb, errorcb) {
path = url + path; path = url + path;
......
/*! elastic.js - v1.0.0 - 2013-01-15 /*! elastic.js - v1.0.0 - 2013-01-15
* https://github.com/fullscale/elastic.js * https://github.com/fullscale/elastic.js
* Copyright (c) 2013 FullScale Labs, LLC; Licensed MIT */ * 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}}]); "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}}]);
\ No newline at end of file
/* /*
The settings before the break are the only ones that are currently implemented
The remaining settings do nothing
elasticsearch: URL to your elasticsearch server elasticsearch: URL to your elasticsearch server
timeformat: Format for time in histograms (might go away) timeformat: Format for time in histograms (might go away)
modules: Panel modules to load. In the future these will be inferred 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. ...@@ -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 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( var config = new Settings(
{ {
elasticsearch: 'http://localhost:9200', elasticsearch: 'http://localhost:9200',
timeformat: 'mm/dd HH:MM:ss', timeformat: 'mm/dd HH:MM:ss',
modules: ['histogram','map','pie','table','stringquery','sort', modules: ['histogram','map','pie','table','stringquery','sort',
'timepicker','text','fields','hits'], 'timepicker','text','fields','hits','dashcontrol'],
perpage: 50,
timezone: 'user',
operator: 'OR',
exportdelim: ',',
smartindex: true,
indexlimit: 150,
indexdefault: 'logstash-*',
primaryfield: '_all'
} }
); );
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.",
},
]
}
]
};
{
"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
...@@ -29,21 +29,19 @@ ...@@ -29,21 +29,19 @@
</head> </head>
<body ng-controller="DashCtrl" ng-cloak> <body ng-controller="DashCtrl" ng-cloak>
<div ng-repeat='alert in global_alert' class="alert alert-{{alert.severity}} span12" style="position: fixed;top:2px;opacity:0.9;z-index:8000">
<button type="button" class="close" ng-click="clear_alert(alert)">&times;</button>
<strong>{{alert.title}}</strong> <span ng-bind-html-unsafe='alert.text'></span> <div class='pull-right small'> {{$index + 1}} alert(s) </div>
</div>
<div class="navbar navbar-fixed-top"> <div class="navbar navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<div class="container-fluid"> <div class="container-fluid">
<span class="brand"><small>Kibana Preview</small></span> <span class="brand"><small>Kibana Preview</small></span>
<span class="brand">{{dashboards.title}}</span> <span class="brand">{{dashboards.title}}</span>
<div class="brand"><i class='icon-edit pointer' ng-show='dashboards.editable' bs-modal="'partials/dasheditor.html'"></i></div> <div class="brand"><i class='icon-edit pointer' ng-show='dashboards.editable' bs-modal="'partials/dasheditor.html'"></i></div>
<div class="brand"><i class='icon-download pointer' ng-click="export()" bs-tooltip="'Export this dashboard'" data-placement="bottom"></i></div>
<div class="brand"><i class='icon-bookmark pointer' ng-click="default()" bs-tooltip="'Set as default dashboard'" data-placement="bottom"></i></div>
<div class="brand"><i class='icon-ban-circle pointer' ng-click="purge()" bs-tooltip="'Clear default dashboard settings'" data-placement="bottom"></i></div>
<div class='pull-right' style="padding-top: 5px; padding-left: 10px"><input type="file" id="upload" upload /></div>
</div> </div>
</div> </div>
</div> </div>
<div class="container-fluid"> <div class="container-fluid">
<div class="row-fluid"> <div class="row-fluid">
<div ng-view></div> <div ng-view></div>
......
...@@ -31,7 +31,6 @@ var labjs = $LAB ...@@ -31,7 +31,6 @@ var labjs = $LAB
.script("common/lib/datepicker.js") .script("common/lib/datepicker.js")
.script("common/lib/shared.js") .script("common/lib/shared.js")
.script("common/lib/filesaver.js") .script("common/lib/filesaver.js")
.script("dashboards.js")
.script("js/services.js") .script("js/services.js")
.script("js/controllers.js") .script("js/controllers.js")
.script("js/filters.js") .script("js/filters.js")
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
'use strict'; 'use strict';
angular.module('kibana.controllers', []) angular.module('kibana.controllers', [])
.controller('DashCtrl', function($scope, $rootScope, $http, ejsResource, timer) { .controller('DashCtrl', function($scope, $rootScope, $http, $timeout, ejsResource, eventBus) {
var _d = { var _d = {
title: "", title: "",
...@@ -15,6 +15,7 @@ angular.module('kibana.controllers', []) ...@@ -15,6 +15,7 @@ angular.module('kibana.controllers', [])
$scope.config = config; $scope.config = config;
$scope._ = _; $scope._ = _;
$scope.reset_row(); $scope.reset_row();
$scope.clear_all_alerts();
// The global dashboards object should be moved to an $http request for json // The global dashboards object should be moved to an $http request for json
if (Modernizr.localstorage && if (Modernizr.localstorage &&
...@@ -22,39 +23,28 @@ angular.module('kibana.controllers', []) ...@@ -22,39 +23,28 @@ angular.module('kibana.controllers', [])
localStorage['dashboard'] !== '' localStorage['dashboard'] !== ''
) { ) {
$scope.dashboards = JSON.parse(localStorage['dashboard']); $scope.dashboards = JSON.parse(localStorage['dashboard']);
_.defaults($scope.dashboards,_d);
} else { } 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); 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.add_row = function(dashboards,row) {
$scope.dashboards.rows.push(row); $scope.dashboards.rows.push(row);
} }
...@@ -67,11 +57,32 @@ angular.module('kibana.controllers', []) ...@@ -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 = { var _d = {
title: "Row", title: "Row",
...@@ -107,9 +118,9 @@ angular.module('kibana.controllers', []) ...@@ -107,9 +118,9 @@ angular.module('kibana.controllers', [])
$scope.reset_panel = function() { $scope.reset_panel = function() {
$scope.panel = { $scope.panel = {
span: 1, span: 3,
editable: true, editable: true,
groups: ['default'], group: ['default'],
}; };
}; };
......
...@@ -21,18 +21,22 @@ angular.module('kibana.services', []) ...@@ -21,18 +21,22 @@ angular.module('kibana.services', [])
// addressed to the scope in question and runs the registered function if it // addressed to the scope in question and runs the registered function if it
// is. // is.
this.register = function(scope,type,fn) { this.register = function(scope,type,fn) {
console.log('registered:' + type + " for " + scope.panel.title + " " + scope.$id)
scope.$on(type,function(event,packet){ scope.$on(type,function(event,packet){
var _id = scope.$id; var _id = scope.$id;
var _to = packet.to; var _to = packet.to;
var _from = packet.from; 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))) if(!(_.isArray(_to)))
_to = [_to]; _to = [_to];
if(!(_.isArray(scope.panel.group))) if(!(_.isArray(_group)))
scope.panel.group = [scope.panel.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)) //console.log('Got: '+type + ' from ' + _from + ' to ' + _to + ': ' + angular.toJson(packet.data))
fn(event,packet.data); fn(event,packet.data);
} }
......
<div>
<h5>Allow saving to</h5>
<div class="row-fluid">
<div class="span2">
<label class="small"> Export </label><input type="checkbox" ng-model="panel.save.local" ng-checked="panel.save.local">
</div>
<div class="span2">
<label class="small"> Defaults </label><input type="checkbox" ng-model="panel.save.default" ng-checked="panel.save.default">
</div>
<div class="span2">
<label class="small"> Gist </label><input type="checkbox" ng-model="panel.save.gist" ng-checked="panel.save.gist">
</div>
<div class="span2">
<label class="small"> Elasticsearch </label><input type="checkbox" ng-model="panel.save.elasticsearch" ng-checked="panel.save.elasticsearch">
</div>
</div>
<h5>Allow loading from</h5>
<div class="row-fluid">
<div class="span2">
<label class="small"> Local file </label><input type="checkbox" ng-model="panel.load.local" ng-checked="panel.load.local">
</div>
<div class="span2">
<label class="small"> Gist </label><input type="checkbox" ng-model="panel.load.gist" ng-checked="panel.load.gist">
</div>
<div class="span2">
<label class="small"> Elasticsearch </label><input type="checkbox" ng-model="panel.load.elasticsearch" ng-checked="panel.load.elasticsearch">
</div>
<div class="span4" ng-show="panel.load.elasticsearch">
<label class="small"> Elasticsearch list size</label><input class="input-small" type="number" ng-model="panel.elasticsearch_size">
</div>
</div>
</div>
\ No newline at end of file
<div>
<div ng-show='panel.load.local'>
<h5>Local File</h5>
<form>
<input type="file" id="dashupload" dash-upload /><br>
</form>
</div>
<div ng-show='panel.load.gist'>
<h5>Gist <small>Enter a gist number or url</small></h5>
<form>
<input type="text" ng-model="gist.url"/><br>
<button class="btn" ng-click="gist_dblist(gist_id(gist.url))" ng-show="is_gist(gist.url)"><i class="icon-github-alt"></i> Get gist:{{gist.url | gistid}}</button>
<h6 ng-show="gist.files.length">Dashboards in gist:{{gist.url | gistid}} <small>click to load</small></h6>
<h6 ng-hide="gist.files.length">No gist dashboards found</h6>
<table class="table table-condensed table-striped">
<tr ng-repeat="file in gist.files">
<td><a ng-click="dash_load(file)">{{file.title}}</a></td>
</tr>
</table>
</form>
</div>
<div ng-show='panel.load.elasticsearch'>
<h5>Elasticsearch</h5>
<form class="input-append">
<input type="text" ng-model="elasticsearch.query"/>
<button ng-click="elasticsearch_dblist(elasticsearch.query)" class='btn'><i class='icon-search'></i></button>
</form>
<h6 ng-show="elasticsearch.dashboards.length">Elasticsearch stored dashboards</h6>
<h6 ng-hide="elasticsearch.dashboards.length">No dashboards matching your query found</h6>
<table class="table table-condensed table-striped">
<tr ng-repeat="dashboard in elasticsearch.dashboards">
<td><a ng-click="delete_elasticsearch(dashboard)"><i class="icon-remove"></i></a></td>
<td><a ng-click="dash_load(dashboard['_source']['dashboard'])">{{dashboard._id}}</a></td>
</tr>
</table>
</div>
</div>
\ No newline at end of file
<kibana-panel ng-controller='dashcontrol'>
<button class='btn' ng-show="panel.load.gist || panel.load.elasticsearch || panel.load.local" data-placement="bottom" data-unique="1" ng-click="elasticsearch_dblist(elasticsearch.query)" bs-popover="'panels/dashcontrol/load.html'"><i class='icon-folder-open'></i> Load <i class='icon-caret-down'></i></button>
<button class='btn' ng-show="panel.save.gist || panel.save.elasticsearch || panel.save.local || panel.save.default" data-placement="bottom" data-unique="1" bs-popover="'panels/dashcontrol/save.html'"><i class='icon-save'></i> Save <i class='icon-caret-down'></i></button>
</kibana-panel>
\ No newline at end of file
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 <a href="'+data.html_url+'">'+data.html_url+'</a> 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
<div>
<div ng-show="panel.save.default || panel.save.local">
<h5>Locally</h5>
<form>
<ul class="nav nav-list">
<li><a ng-show="panel.save.local" ng-click="export()"><i class="icon-download"></i> Export to File</a></li>
<li><a ng-show="panel.save.default" ng-click="default()"><i class="icon-bookmark"></i> Set as Default</a></li>
<li><a ng-show="panel.save.default" ng-click="purge()"><i class="icon-ban-circle"></i> Clear Default</a></li>
</ul>
</form>
</div>
<div ng-show="panel.save.gist">
<h5>Gist</h5>
<form class="input-append">
<input class='input-medium' placeholder='Title' type="text" ng-model="gist.title"/>
<button class="btn" ng-click="save_gist()"><i class="icon-github-alt"></i></button>
</form><br>
<small ng-show="gist.last">Last gist: <a target="_blank" href="{{gist.last}}">{{gist.last}}</a></small>
</div>
<div ng-show="panel.save.elasticsearch">
<h5>Elasticsearch</h5>
<form class="input-append">
<input class='input-medium' placeholder='Title' type="text" ng-model="elasticsearch.title"/>
<button class="btn" ng-click="save_elasticsearch()"><i class="icon-save"></i></button>
</form>
</div>
</div>
\ No newline at end of file
...@@ -33,8 +33,8 @@ ...@@ -33,8 +33,8 @@
</div> </div>
<div class="row-fluid"> <div class="row-fluid">
<div class="span3"> <div class="span3">
<label class="small">Effect Options</label> <label class="small">Chart Options</label>
<select ng-change="$emit('render')" multiple style="width:95%" ng-model="panel.show" ng-options="f for f in ['bars','points','stack','lines']"></select> <select ng-change="$emit('render')" multiple style="width:95%" ng-model="panel.show" ng-options="f for f in ['bars','points','stack','lines','legend','x-axis','y-axis']"></select>
</div> </div>
<div class="span2"> <div class="span2">
<label class="small">Fill (0.0 - 1.0)</label> <label class="small">Fill (0.0 - 1.0)</label>
......
<kibana-panel ng-controller='histogram' style="height:{{row.height}}"> <kibana-panel ng-controller='histogram' style="height:{{row.height}}">
<div> <div histogram params="{{panel}}" style="height:{{row.height}}"></div>
<div histogram params="{{panel}} clearfix" style="height:{{row.height}}"></div>
</div>
</kibana-panel> </kibana-panel>
\ No newline at end of file
...@@ -7,7 +7,7 @@ angular.module('kibana.histogram', []) ...@@ -7,7 +7,7 @@ angular.module('kibana.histogram', [])
var _d = { var _d = {
query : [ {query: "*", label:"Query"} ], query : [ {query: "*", label:"Query"} ],
interval: secondsToHms(calculate_interval($scope.from,$scope.to,40,0)/1000), interval: secondsToHms(calculate_interval($scope.from,$scope.to,40,0)/1000),
show : ['bars'], show : ['bars','y-axis','x-axis','legend'],
fill : false, fill : false,
group : "default", group : "default",
} }
...@@ -133,6 +133,9 @@ angular.module('kibana.histogram', []) ...@@ -133,6 +133,9 @@ angular.module('kibana.histogram', [])
bars: _.indexOf(scope.panel.show,'bars') < 0 ? false : true, bars: _.indexOf(scope.panel.show,'bars') < 0 ? false : true,
points: _.indexOf(scope.panel.show,'points') < 0 ? false : true, points: _.indexOf(scope.panel.show,'points') < 0 ? false : true,
stack: _.indexOf(scope.panel.show,'stack') < 0 ? null : 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 // Set barwidth based on specified interval
...@@ -147,6 +150,7 @@ angular.module('kibana.histogram', []) ...@@ -147,6 +150,7 @@ angular.module('kibana.histogram', [])
// Populate element // Populate element
$.plot(elem, scope.data, { $.plot(elem, scope.data, {
legend: { legend: {
show: show.legend,
position: "nw", position: "nw",
labelFormatter: function(label, series) { labelFormatter: function(label, series) {
return '<span class="legend">' + label + ' / ' + return '<span class="legend">' + label + ' / ' +
...@@ -160,8 +164,9 @@ angular.module('kibana.histogram', []) ...@@ -160,8 +164,9 @@ angular.module('kibana.histogram', [])
points: { show: show.points }, points: { show: show.points },
shadowSize: 1 shadowSize: 1
}, },
yaxis: { min: 0, color: "#000" }, yaxis: { show: show['y-axis'], min: 0, color: "#000" },
xaxis: { xaxis: {
show: show['x-axis'],
mode: "time", mode: "time",
timeformat: "%H:%M:%S<br>%m-%d", timeformat: "%H:%M:%S<br>%m-%d",
label: "Datetime", label: "Datetime",
......
...@@ -16,18 +16,31 @@ ...@@ -16,18 +16,31 @@
</div> </div>
</div> </div>
<div class="row-fluid"> <div class="row-fluid">
<h4>New Panel</h4>
<select class="input-medium" ng-model="panel.type" ng-options="f for f in config.modules"></select>
<small>Select Type</small>
<div ng-show="!(_.isUndefined(panel.type))">
<div ng-include src="'partials/panelgeneral.html'"></div>
<!--<div ng-include src="'panels/'+panel.type+'/editor.html'"></div>-->
<button ng-click="add_panel(row,panel); reset_panel();" class="btn btn-primary">Create Panel</button><br>
</div>
</div>
<div class="row-fluid">
<div class="span12"> <div class="span12">
<h4>Panels</h4> <h4>Panels</h4>
<table class="table table-condensed table-striped"> <table class="table table-condensed table-striped">
<thead> <thead>
<th>Title</th> <th>Title</th>
<th>Type</th> <th>Type</th>
<th>Span</th>
<th>Delete</th> <th>Delete</th>
<th>Move</th> <th>Move</th>
</thead> </thead>
<tr ng-repeat="panel in row.panels"> <tr ng-repeat="panel in row.panels">
<td>{{panel.title}}</td> <td>{{panel.title}}</td>
<td>{{panel.type}}</td> <td>{{panel.type}}</td>
<td><select class="input-mini" ng-model="panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select></td>
<td><i ng-click="row.panels = _.without(row.panels,panel)" class="pointer icon-remove"></i></td> <td><i ng-click="row.panels = _.without(row.panels,panel)" class="pointer icon-remove"></i></td>
<td><i ng-click="_.move(row.panels,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td> <td><i ng-click="_.move(row.panels,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
<td><i ng-click="_.move(row.panels,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td> <td><i ng-click="_.move(row.panels,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
...@@ -35,14 +48,6 @@ ...@@ -35,14 +48,6 @@
</table> </table>
</div> </div>
</div> </div>
<h4>New Panel</h4>
<select class="input-medium" ng-model="panel.type" ng-options="f for f in config.modules"></select>
<small>Select Type</small>
<div ng-show="!(_.isUndefined(panel.type))">
<div ng-include src="'partials/panelgeneral.html'"></div>
<div ng-include src="'panels/'+panel.type+'/editor.html'"></div>
<button ng-click="add_panel(row,panel); reset_panel();" class="btn btn-primary">Create Panel</button><br>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-success" ng-click="dismiss();reset_panel();send_render()">Close</button> <button type="button" class="btn btn-success" ng-click="dismiss();reset_panel();send_render()">Close</button>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment