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

This commit is contained in:
Rashid Khan 2013-02-15 15:23:04 -07:00
parent d8f2d412cd
commit a956a36c96
19 changed files with 722 additions and 343 deletions

View File

@ -40,6 +40,10 @@
opacity: 1;
}
.popover {
max-width: 500px;
}
/*
.row-header i.editlink {
opacity: 0;

View File

@ -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;

View File

@ -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}}]);
"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}}]);

View File

@ -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'],
}
);

View File

@ -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.",
},
]
}
]
};

328
default.json Normal file
View File

@ -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
}

View File

@ -29,21 +29,19 @@
</head>
<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-inner">
<div class="container-fluid">
<span class="brand"><small>Kibana Preview</small></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-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 class="container-fluid">
<div class="row-fluid">
<div ng-view></div>

View File

@ -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")

View File

@ -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.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'],
};
};

View File

@ -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);
}

View File

@ -0,0 +1,32 @@
<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>

View File

@ -0,0 +1,37 @@
<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>

View File

@ -0,0 +1,4 @@
<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>

View File

@ -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 <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(/.*\//, '');
}
}
});;

View File

@ -0,0 +1,28 @@
<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>

View File

@ -33,8 +33,8 @@
</div>
<div class="row-fluid">
<div class="span3">
<label class="small">Effect 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>
<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','legend','x-axis','y-axis']"></select>
</div>
<div class="span2">
<label class="small">Fill (0.0 - 1.0)</label>

View File

@ -1,5 +1,3 @@
<kibana-panel ng-controller='histogram' style="height:{{row.height}}">
<div>
<div histogram params="{{panel}} clearfix" style="height:{{row.height}}"></div>
</div>
<div histogram params="{{panel}}" style="height:{{row.height}}"></div>
</kibana-panel>

View File

@ -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 '<span class="legend">' + 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<br>%m-%d",
label: "Datetime",

View File

@ -15,6 +15,17 @@
<label class="small"> Editable </label><input type="checkbox" ng-model="row.editable" ng-checked="row.editable" />
</div>
</div>
<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">
<h4>Panels</h4>
@ -22,12 +33,14 @@
<thead>
<th>Title</th>
<th>Type</th>
<th>Span</th>
<th>Delete</th>
<th>Move</th>
</thead>
<tr ng-repeat="panel in row.panels">
<td>{{panel.title}}</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="_.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>
@ -35,14 +48,6 @@
</table>
</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 class="modal-footer">
<button type="button" class="btn btn-success" ng-click="dismiss();reset_panel();send_render()">Close</button>