mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
commit
0dd0cfeb2e
20
common/css/bootstrap.dark.min.css
vendored
20
common/css/bootstrap.dark.min.css
vendored
@ -226,7 +226,7 @@ body {
|
|||||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
color: #c8c8c8;
|
color: #ddd;
|
||||||
background-color: #272b30;
|
background-color: #272b30;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1096,7 +1096,7 @@ input[type="color"],
|
|||||||
margin-bottom: 10.5px;
|
margin-bottom: 10.5px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
color: #c8c8c8;
|
color: #fff;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
-webkit-border-radius: 4px;
|
-webkit-border-radius: 4px;
|
||||||
-moz-border-radius: 4px;
|
-moz-border-radius: 4px;
|
||||||
@ -1129,8 +1129,8 @@ input[type="search"],
|
|||||||
input[type="tel"],
|
input[type="tel"],
|
||||||
input[type="color"],
|
input[type="color"],
|
||||||
.uneditable-input {
|
.uneditable-input {
|
||||||
background-color: #222;
|
background-color: #666;
|
||||||
border: 1px solid #333;
|
border: 1px solid #666;
|
||||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
@ -1197,8 +1197,8 @@ input[type="file"] {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
width: 220px;
|
width: 220px;
|
||||||
background-color: #222;
|
background-color: #666;
|
||||||
border: 1px solid #333;
|
border: 1px solid #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
select[multiple],
|
select[multiple],
|
||||||
@ -5201,8 +5201,8 @@ input[type="submit"].btn.btn-mini {
|
|||||||
top: 10%;
|
top: 10%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
z-index: 1050;
|
z-index: 1050;
|
||||||
width: 560px;
|
width: 760px;
|
||||||
margin-left: -280px;
|
margin-left: -380px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border: 1px solid #999;
|
border: 1px solid #999;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.3);
|
border: 1px solid rgba(0, 0, 0, 0.3);
|
||||||
@ -5228,7 +5228,7 @@ input[type="submit"].btn.btn-mini {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal.fade.in {
|
.modal.fade.in {
|
||||||
top: 10%;
|
top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
.modal-header {
|
||||||
@ -5247,7 +5247,7 @@ input[type="submit"].btn.btn-mini {
|
|||||||
|
|
||||||
.modal-body {
|
.modal-body {
|
||||||
position: relative;
|
position: relative;
|
||||||
max-height: 400px;
|
max-height: 500px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
@ -131,6 +131,14 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.normal {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light {
|
||||||
|
font-weight: 200;
|
||||||
|
}
|
||||||
|
|
||||||
.input-append label {
|
.input-append label {
|
||||||
font-size: inherit !important;
|
font-size: inherit !important;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,6 @@ var config = new Settings(
|
|||||||
kibana_index: "kibana-int",
|
kibana_index: "kibana-int",
|
||||||
modules: ['histogram','map','pie','table','stringquery','sort',
|
modules: ['histogram','map','pie','table','stringquery','sort',
|
||||||
'timepicker','text','fields','hits','dashcontrol',
|
'timepicker','text','fields','hits','dashcontrol',
|
||||||
'column','derivequeries'],
|
'column','derivequeries','trends'],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -83,6 +83,85 @@ angular.module('kibana.services', [])
|
|||||||
return fields;
|
return fields;
|
||||||
|
|
||||||
})
|
})
|
||||||
|
.service('kbnIndex',function($http) {
|
||||||
|
// returns a promise containing an array of all indices matching the index
|
||||||
|
// pattern that exist in a given range
|
||||||
|
this.indices = function(from,to,pattern,interval) {
|
||||||
|
var possible = [];
|
||||||
|
_.each(expand_range(fake_utc(from),fake_utc(to),interval),function(d){
|
||||||
|
possible.push(d.format(pattern));
|
||||||
|
});
|
||||||
|
|
||||||
|
return all_indices().then(function(p) {
|
||||||
|
var indices = _.intersection(possible,p);
|
||||||
|
indices.reverse();
|
||||||
|
return indices
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
// returns a promise containing an array of all indices in an elasticsearch
|
||||||
|
// cluster
|
||||||
|
function all_indices() {
|
||||||
|
var something = $http({
|
||||||
|
url: config.elasticsearch + "/_aliases",
|
||||||
|
method: "GET"
|
||||||
|
}).error(function(data, status, headers, config) {
|
||||||
|
// Handle error condition somehow?
|
||||||
|
});
|
||||||
|
|
||||||
|
return something.then(function(p) {
|
||||||
|
var indices = [];
|
||||||
|
_.each(p.data, function(v,k) {
|
||||||
|
indices.push(k)
|
||||||
|
});
|
||||||
|
return indices;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is stupid, but there is otherwise no good way to ensure that when
|
||||||
|
// I extract the date from an object that I'm get the UTC date. Stupid js.
|
||||||
|
// I die a little inside every time I call this function.
|
||||||
|
// Update: I just read this again. I died a little more inside.
|
||||||
|
// Update2: More death.
|
||||||
|
function fake_utc(date) {
|
||||||
|
date = moment(date).clone().toDate()
|
||||||
|
return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an array of date objects by a given interval
|
||||||
|
function expand_range(start, end, interval) {
|
||||||
|
if(_.contains(['hour','day','week','month','year'],interval)) {
|
||||||
|
var range;
|
||||||
|
start = moment(start).clone();
|
||||||
|
range = [];
|
||||||
|
while (start.isBefore(end)) {
|
||||||
|
range.push(start.clone());
|
||||||
|
switch (interval) {
|
||||||
|
case 'hour':
|
||||||
|
start.add('hours',1)
|
||||||
|
break
|
||||||
|
case 'day':
|
||||||
|
start.add('days',1)
|
||||||
|
break
|
||||||
|
case 'week':
|
||||||
|
start.add('weeks',1)
|
||||||
|
break
|
||||||
|
case 'month':
|
||||||
|
start.add('months',1)
|
||||||
|
break
|
||||||
|
case 'year':
|
||||||
|
start.add('years',1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
range.push(moment(end).clone());
|
||||||
|
return range;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
.service('timer', function($timeout) {
|
.service('timer', function($timeout) {
|
||||||
// This service really just tracks a list of $timeout promises to give us a
|
// This service really just tracks a list of $timeout promises to give us a
|
||||||
// method for cancelling them all when we need to
|
// method for cancelling them all when we need to
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<label class="small">Query</label>
|
<label class="small">Query</label>
|
||||||
<form class="input-append" style="margin-bottom: 0px">
|
<form class="input-append" style="margin-bottom: 0px">
|
||||||
<input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
|
<input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
|
||||||
<button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery=''"><i class="icon-plus"></i></button>
|
<button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery='';set_refresh(true)"><i class="icon-plus"></i></button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="span1">
|
<div class="span1">
|
||||||
@ -34,7 +34,7 @@
|
|||||||
<div class="row-fluid" ng-repeat="q in panel.query">
|
<div class="row-fluid" ng-repeat="q in panel.query">
|
||||||
<div class="span3">
|
<div class="span3">
|
||||||
<form style="margin-bottom: 0px">
|
<form style="margin-bottom: 0px">
|
||||||
<input type="text" style="width:70%" ng-model="q.label">
|
<input type="text" style="width:70%" ng-model="q.label" ng-change="set_refresh(true)">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="span8">
|
<div class="span8">
|
||||||
|
@ -315,7 +315,7 @@ angular.module('kibana.histogram', [])
|
|||||||
color: '#ccc'
|
color: '#ccc'
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
backgroundColor: '#272b30',
|
backgroundColor: null,
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
borderColor: '#eee',
|
borderColor: '#eee',
|
||||||
color: "#eee",
|
color: "#eee",
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
<form class="input-append" style="margin-bottom: 0px">
|
<form class="input-append" style="margin-bottom: 0px">
|
||||||
<label class="small">Query</label>
|
<label class="small">Query</label>
|
||||||
<input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
|
<input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
|
||||||
<button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery=''"><i class="icon-plus"></i></button>
|
<button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery='';set_refresh(true)"><i class="icon-plus"></i></button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="span1">
|
<div class="span1">
|
||||||
@ -47,12 +47,12 @@
|
|||||||
<div class="row-fluid" ng-repeat="q in panel.query">
|
<div class="row-fluid" ng-repeat="q in panel.query">
|
||||||
<div class="span3">
|
<div class="span3">
|
||||||
<form style="margin-bottom: 0px">
|
<form style="margin-bottom: 0px">
|
||||||
<input type="text" style="width:70%" ng-model="q.label">
|
<input type="text" style="width:70%" ng-model="q.label" ng-change="set_refresh(true)">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="span8">
|
<div class="span8">
|
||||||
<form class="input-append" style="margin-bottom: 0px">
|
<form class="input-append" style="margin-bottom: 0px">
|
||||||
<input type="text" style="width:80%" ng-model="q.query">
|
<input type="text" style="width:80%" ng-model="q.query" ng-change="set_refresh(true)">
|
||||||
<button class="btn" ng-click="get_data()"><i class="icon-search"></i></button>
|
<button class="btn" ng-click="get_data()"><i class="icon-search"></i></button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -141,6 +141,17 @@ angular.module('kibana.hits', [])
|
|||||||
$scope.get_data();
|
$scope.get_data();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.set_refresh = function (state) {
|
||||||
|
$scope.refresh = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.close_edit = function() {
|
||||||
|
if($scope.refresh)
|
||||||
|
$scope.get_data();
|
||||||
|
$scope.refresh = false;
|
||||||
|
$scope.$emit('render');
|
||||||
|
}
|
||||||
|
|
||||||
function set_time(time) {
|
function set_time(time) {
|
||||||
$scope.time = time;
|
$scope.time = time;
|
||||||
$scope.index = _.isUndefined(time.index) ? $scope.index : time.index
|
$scope.index = _.isUndefined(time.index) ? $scope.index : time.index
|
||||||
|
@ -145,7 +145,7 @@ angular.module('kibana.map', [])
|
|||||||
map: scope.panel.map,
|
map: scope.panel.map,
|
||||||
regionStyle: {initial: {fill: '#8c8c8c'}},
|
regionStyle: {initial: {fill: '#8c8c8c'}},
|
||||||
zoomOnScroll: false,
|
zoomOnScroll: false,
|
||||||
backgroundColor: '#272b30',
|
backgroundColor: null,
|
||||||
series: {
|
series: {
|
||||||
regions: [{
|
regions: [{
|
||||||
values: scope.data,
|
values: scope.data,
|
||||||
@ -157,9 +157,9 @@ angular.module('kibana.map', [])
|
|||||||
$('.jvectormap-label').css({
|
$('.jvectormap-label').css({
|
||||||
"position" : "absolute",
|
"position" : "absolute",
|
||||||
"display" : "none",
|
"display" : "none",
|
||||||
'color' : "#c8c8c8",
|
'color' : "#c8c8c8",
|
||||||
'padding' : '10px',
|
'padding' : '10px',
|
||||||
'font-size': '11pt',
|
'font-size' : '11pt',
|
||||||
'font-weight' : 200,
|
'font-weight' : 200,
|
||||||
'background-color': '#1f1f1f',
|
'background-color': '#1f1f1f',
|
||||||
'border-radius': '5px'
|
'border-radius': '5px'
|
||||||
|
@ -246,14 +246,13 @@ angular.module('kibana.pie', [])
|
|||||||
},
|
},
|
||||||
label: label,
|
label: label,
|
||||||
stroke: {
|
stroke: {
|
||||||
color: '#272b30',
|
|
||||||
width: 0
|
width: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
//grid: { hoverable: true, clickable: true },
|
//grid: { hoverable: true, clickable: true },
|
||||||
grid: {
|
grid: {
|
||||||
backgroundColor: '#272b30',
|
backgroundColor: null,
|
||||||
hoverable: true,
|
hoverable: true,
|
||||||
clickable: true
|
clickable: true
|
||||||
},
|
},
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
angular.module('kibana.timepicker', [])
|
angular.module('kibana.timepicker', [])
|
||||||
.controller('timepicker', function($scope, eventBus, $timeout, timer, $http) {
|
.controller('timepicker', function($scope, eventBus, $timeout, timer, $http, kbnIndex) {
|
||||||
|
|
||||||
// Set and populate defaults
|
// Set and populate defaults
|
||||||
var _d = {
|
var _d = {
|
||||||
@ -89,7 +89,7 @@ angular.module('kibana.timepicker', [])
|
|||||||
// request one be sent by broadcasting a 'get_time' with its _id to its group
|
// request one be sent by broadcasting a 'get_time' with its _id to its group
|
||||||
// This panel can handle multiple groups
|
// This panel can handle multiple groups
|
||||||
eventBus.register($scope,"get_time", function(event,id) {
|
eventBus.register($scope,"get_time", function(event,id) {
|
||||||
eventBus.broadcast($scope.$id,id,'time',unmoment($scope.time))
|
eventBus.broadcast($scope.$id,id,'time',compile_time($scope.time))
|
||||||
});
|
});
|
||||||
|
|
||||||
// In case some other panel broadcasts a time, set us to an absolute range
|
// In case some other panel broadcasts a time, set us to an absolute range
|
||||||
@ -208,13 +208,17 @@ angular.module('kibana.timepicker', [])
|
|||||||
// Get indices for the time period, then broadcast time range and index list
|
// Get indices for the time period, then broadcast time range and index list
|
||||||
// in a single object. Not sure if I like this.
|
// in a single object. Not sure if I like this.
|
||||||
if($scope.panel.index_interval !== 'none') {
|
if($scope.panel.index_interval !== 'none') {
|
||||||
indices($scope.time.from,$scope.time.to).then(function (p) {
|
kbnIndex.indices($scope.time.from,
|
||||||
|
$scope.time.to,
|
||||||
|
$scope.panel.index,
|
||||||
|
$scope.panel.index_interval
|
||||||
|
).then(function (p) {
|
||||||
$scope.time.index = p;
|
$scope.time.index = p;
|
||||||
eventBus.broadcast($scope.$id,$scope.panel.group,'time',unmoment($scope.time))
|
eventBus.broadcast($scope.$id,$scope.panel.group,'time',compile_time($scope.time))
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
$scope.time.index = [$scope.panel.index];
|
$scope.time.index = [$scope.panel.index];
|
||||||
eventBus.broadcast($scope.$id,$scope.panel.group,'time',unmoment($scope.time))
|
eventBus.broadcast($scope.$id,$scope.panel.group,'time',compile_time($scope.time))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update panel's string representation of the time object.Don't update if
|
// Update panel's string representation of the time object.Don't update if
|
||||||
@ -233,10 +237,12 @@ angular.module('kibana.timepicker', [])
|
|||||||
|
|
||||||
// Prefer to pass around Date() objects in the EventBus since interacting with
|
// Prefer to pass around Date() objects in the EventBus since interacting with
|
||||||
// moment objects in libraries that are expecting Date()s can be tricky
|
// moment objects in libraries that are expecting Date()s can be tricky
|
||||||
function unmoment(time) {
|
function compile_time(time) {
|
||||||
time = _.clone(time)
|
time = _.clone(time)
|
||||||
time.from = time.from.toDate()
|
time.from = time.from.toDate()
|
||||||
time.to = time.to.toDate()
|
time.to = time.to.toDate()
|
||||||
|
time.interval = $scope.panel.index_interval
|
||||||
|
time.pattern = $scope.panel.index
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,81 +260,4 @@ angular.module('kibana.timepicker', [])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a promise containing an array of all indices matching the index
|
|
||||||
// pattern that exist in a given range
|
|
||||||
function indices(from,to) {
|
|
||||||
var possible = [];
|
|
||||||
_.each(expand_range(fake_utc(from),fake_utc(to),$scope.panel.index_interval),function(d){
|
|
||||||
possible.push(d.format($scope.panel.index));
|
|
||||||
});
|
|
||||||
|
|
||||||
return all_indices().then(function(p) {
|
|
||||||
var indices = _.intersection(possible,p);
|
|
||||||
indices.reverse();
|
|
||||||
return indices.length == 0 ? [$scope.panel.defaultindex] : indices;
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// returns a promise containing an array of all indices in an elasticsearch
|
|
||||||
// cluster
|
|
||||||
function all_indices() {
|
|
||||||
var something = $http({
|
|
||||||
url: config.elasticsearch + "/_aliases",
|
|
||||||
method: "GET"
|
|
||||||
}).error(function(data, status, headers, config) {
|
|
||||||
$scope.error = status;
|
|
||||||
});
|
|
||||||
|
|
||||||
return something.then(function(p) {
|
|
||||||
var indices = [];
|
|
||||||
_.each(p.data, function(v,k) {
|
|
||||||
indices.push(k)
|
|
||||||
});
|
|
||||||
return indices;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is stupid, but there is otherwise no good way to ensure that when
|
|
||||||
// I extract the date from an object that I'm get the UTC date. Stupid js.
|
|
||||||
// I die a little inside every time I call this function.
|
|
||||||
// Update: I just read this again. I died a little more inside.
|
|
||||||
// Update2: More death.
|
|
||||||
function fake_utc(date) {
|
|
||||||
date = date.clone().toDate()
|
|
||||||
return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an array of date objects by a given interval
|
|
||||||
function expand_range(start, end, interval) {
|
|
||||||
if(_.contains(['hour','day','week','month','year'],interval)) {
|
|
||||||
var range;
|
|
||||||
start = start.clone();
|
|
||||||
range = [];
|
|
||||||
while (start.isBefore(end)) {
|
|
||||||
range.push(start.clone());
|
|
||||||
switch (interval) {
|
|
||||||
case 'hour':
|
|
||||||
start.add('hours',1)
|
|
||||||
break
|
|
||||||
case 'day':
|
|
||||||
start.add('days',1)
|
|
||||||
break
|
|
||||||
case 'week':
|
|
||||||
start.add('weeks',1)
|
|
||||||
break
|
|
||||||
case 'month':
|
|
||||||
start.add('months',1)
|
|
||||||
break
|
|
||||||
case 'year':
|
|
||||||
start.add('years',1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
range.push(end.clone());
|
|
||||||
return range;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
})
|
64
panels/trends/editor.html
Normal file
64
panels/trends/editor.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<div>
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span12">
|
||||||
|
The trends panel will give you a percentage representation of how your query
|
||||||
|
has moved in your current timespan compared a specified amount of time ago. For
|
||||||
|
example, if the time is 1:10pm, your time picker was set to "Last 10m", and the
|
||||||
|
"Time Ago" parameter was set to '1h', the panel would show how much the query
|
||||||
|
results have changed since 12:00-12:10pm
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h4>Settings</h4>
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span3" ng-hide='panel.auto_int'>
|
||||||
|
<label class="small">Use Elasticsearch date math format here (eg 1m, 5m, 1d, 2w, 1y)</label>
|
||||||
|
</div>
|
||||||
|
<div class="span3">
|
||||||
|
<label class="small">Time Ago</label>
|
||||||
|
<input type="text" class="input-small" ng-model="panel.ago">
|
||||||
|
</div>
|
||||||
|
<div class="span2">
|
||||||
|
<label class="small">Font Size</label>
|
||||||
|
<select class="input-small" ng-model="panel.style['font-size']" ng-options="f for f in ['7pt','8pt','9pt','10pt','12pt','14pt','16pt','18pt','20pt','24pt','28pt','32pt','36pt','42pt','48pt','52pt','60pt','72pt']"></select></span>
|
||||||
|
</div>
|
||||||
|
<div class="span3">
|
||||||
|
<label class="small" >List Format</label>
|
||||||
|
<select class="input-small" ng-model="panel.arrangement" ng-options="f for f in ['horizontal','vertical']"></select></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5>Queries</h5>
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span3">
|
||||||
|
<form style="margin-bottom: 0px">
|
||||||
|
<label class="small">Label</label>
|
||||||
|
<input type="text" placeholder="New Label" style="width:70%" ng-model="newlabel">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="span8">
|
||||||
|
<form class="input-append" style="margin-bottom: 0px">
|
||||||
|
<label class="small">Query</label>
|
||||||
|
<input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
|
||||||
|
<button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery='';set_refresh(true)"><i class="icon-plus"></i></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="span1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row-fluid" ng-repeat="q in panel.query">
|
||||||
|
<div class="span3">
|
||||||
|
<form style="margin-bottom: 0px">
|
||||||
|
<input type="text" style="width:70%" ng-model="q.label" ng-change="set_refresh(true)">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="span8">
|
||||||
|
<form class="input-append" style="margin-bottom: 0px">
|
||||||
|
<input type="text" style="width:80%" ng-model="q.query" ng-change="set_refresh(true)">
|
||||||
|
<button class="btn" ng-click="get_data()"><i class="icon-search"></i></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="span1">
|
||||||
|
<i class="icon-remove pointer" ng-click="remove_query(q)"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
10
panels/trends/module.html
Normal file
10
panels/trends/module.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<kibana-panel ng-controller='trends' ng-init="init()">
|
||||||
|
|
||||||
|
<div ng-style="panel.style" style="line-height:{{panel.style['font-size']}};display:inline-block;padding-right: 5px;" ng-repeat="query in data">
|
||||||
|
<span ng-class="{'text-success': query.hits.new >= query.hits.old, 'text-error': query.hits.old > query.hits.new}" class='strong'>
|
||||||
|
<i class='large' ng-class="{'icon-caret-up': query.hits.new >= query.hits.old, 'icon-caret-down': query.hits.old > query.hits.new}"></i> {{query.percent}}%
|
||||||
|
</span>
|
||||||
|
<span class="tiny pointer light" bs-tooltip="'Then: '+query.hits.old+', Now: '+query.hits.new">({{query.label}})</span>
|
||||||
|
<br ng-show="panel.arrangement == 'vertical'">
|
||||||
|
</div>
|
||||||
|
</kibana-panel>
|
207
panels/trends/module.js
Normal file
207
panels/trends/module.js
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
## Hits
|
||||||
|
|
||||||
|
A variety of representations of the hits a query matches
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
* query :: An array of queries. No labels here, just an array of strings. Maybe
|
||||||
|
there should be labels. Probably.
|
||||||
|
* style :: A hash of css styles
|
||||||
|
* arrangement :: How should I arrange the query results? 'horizontal' or 'vertical'
|
||||||
|
* ago :: Date math formatted time to look back
|
||||||
|
### Group Events
|
||||||
|
#### Sends
|
||||||
|
* get_time :: On panel initialization get time range to query
|
||||||
|
#### Receives
|
||||||
|
* time :: An object containing the time range to use and the index(es) to query
|
||||||
|
* query :: An Array of queries, even if its only one
|
||||||
|
|
||||||
|
*/
|
||||||
|
angular.module('kibana.trends', [])
|
||||||
|
.controller('trends', function($scope, eventBus, kbnIndex) {
|
||||||
|
|
||||||
|
// Set and populate defaults
|
||||||
|
var _d = {
|
||||||
|
query : ["*"],
|
||||||
|
group : "default",
|
||||||
|
style : { "font-size": '14pt'},
|
||||||
|
ago : '1d',
|
||||||
|
arrangement : 'vertical',
|
||||||
|
}
|
||||||
|
_.defaults($scope.panel,_d)
|
||||||
|
|
||||||
|
$scope.init = function () {
|
||||||
|
$scope.hits = 0;
|
||||||
|
eventBus.register($scope,'time', function(event,time){
|
||||||
|
set_time(time)
|
||||||
|
});
|
||||||
|
eventBus.register($scope,'query', function(event, query) {
|
||||||
|
$scope.panel.query = _.map(query,function(q) {
|
||||||
|
return {query: q, label: q};
|
||||||
|
})
|
||||||
|
$scope.get_data();
|
||||||
|
});
|
||||||
|
// Now that we're all setup, request the time from our group
|
||||||
|
eventBus.broadcast($scope.$id,$scope.panel.group,'get_time')
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.get_data = function(segment,query_id) {
|
||||||
|
delete $scope.panel.error
|
||||||
|
$scope.panel.loading = true;
|
||||||
|
|
||||||
|
// Make sure we have everything for the request to complete
|
||||||
|
if(_.isUndefined($scope.index) || _.isUndefined($scope.time))
|
||||||
|
return
|
||||||
|
|
||||||
|
$scope.old_time = {
|
||||||
|
from : new Date($scope.time.from.getTime() - interval_to_seconds($scope.panel.ago)*1000),
|
||||||
|
to : new Date($scope.time.to.getTime() - interval_to_seconds($scope.panel.ago)*1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _segment = _.isUndefined(segment) ? 0 : segment
|
||||||
|
var request = $scope.ejs.Request();
|
||||||
|
|
||||||
|
// Build the question part of the query
|
||||||
|
var queries = [];
|
||||||
|
_.each($scope.panel.query, function(v) {
|
||||||
|
queries.push($scope.ejs.FilteredQuery(
|
||||||
|
ejs.QueryStringQuery(v.query || '*'),
|
||||||
|
ejs.RangeFilter($scope.time.field)
|
||||||
|
.from($scope.time.from)
|
||||||
|
.to($scope.time.to))
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the facet part
|
||||||
|
_.each(queries, function(v) {
|
||||||
|
request = request
|
||||||
|
.facet($scope.ejs.QueryFacet("new"+_.indexOf(queries,v))
|
||||||
|
.query(v)
|
||||||
|
).size(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
var queries = [];
|
||||||
|
_.each($scope.panel.query, function(v) {
|
||||||
|
queries.push($scope.ejs.FilteredQuery(
|
||||||
|
ejs.QueryStringQuery(v.query || '*'),
|
||||||
|
ejs.RangeFilter($scope.time.field)
|
||||||
|
.from($scope.old_time.from)
|
||||||
|
.to($scope.old_time.to))
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the facet part
|
||||||
|
_.each(queries, function(v) {
|
||||||
|
request = request
|
||||||
|
.facet($scope.ejs.QueryFacet("old"+_.indexOf(queries,v))
|
||||||
|
.query(v)
|
||||||
|
).size(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: Spy for hits panel
|
||||||
|
//$scope.populate_modal(request);
|
||||||
|
|
||||||
|
// If we're on the first segment we need to get our indices
|
||||||
|
if (_segment == 0) {
|
||||||
|
kbnIndex.indices(
|
||||||
|
$scope.old_time.from,
|
||||||
|
$scope.old_time.to,
|
||||||
|
$scope.time.pattern,
|
||||||
|
$scope.time.interval
|
||||||
|
).then(function (p) {
|
||||||
|
$scope.index = _.union(p,$scope.index);
|
||||||
|
process_results(request.indices($scope.index[_segment]).doSearch());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
process_results(request.indices($scope.index[_segment]).doSearch());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate scope when we have results
|
||||||
|
function process_results(results) {
|
||||||
|
results.then(function(results) {
|
||||||
|
|
||||||
|
$scope.panel.loading = false;
|
||||||
|
if(_segment == 0) {
|
||||||
|
$scope.hits = {};
|
||||||
|
$scope.data = [];
|
||||||
|
query_id = $scope.query_id = new Date().getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for error and abort if found
|
||||||
|
if(!(_.isUndefined(results.error))) {
|
||||||
|
$scope.panel.error = $scope.parse_error(results.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if($scope.query_id === query_id) {
|
||||||
|
var i = 0;
|
||||||
|
_.each($scope.panel.query, function(k) {
|
||||||
|
var n = results.facets['new'+i].count
|
||||||
|
var o = results.facets['old'+i].count
|
||||||
|
|
||||||
|
var hits = {
|
||||||
|
new : _.isUndefined($scope.data[i]) || _segment == 0 ? n : $scope.data[i].hits.new+n,
|
||||||
|
old : _.isUndefined($scope.data[i]) || _segment == 0 ? o : $scope.data[i].hits.old+o
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.hits.new += n;
|
||||||
|
$scope.hits.old += o;
|
||||||
|
|
||||||
|
var percent = Math.round(percentage(hits.old,hits.new)*100)/100
|
||||||
|
// Create series
|
||||||
|
$scope.data[i] = {
|
||||||
|
label: $scope.panel.query[i].label || "query"+(parseInt(i)+1),
|
||||||
|
hits: {
|
||||||
|
new : hits.new,
|
||||||
|
old : hits.old
|
||||||
|
},
|
||||||
|
percent: _.isNull(percent) ? 0 : percent
|
||||||
|
};
|
||||||
|
|
||||||
|
i++;
|
||||||
|
});
|
||||||
|
$scope.$emit('render');
|
||||||
|
if(_segment < $scope.index.length-1)
|
||||||
|
$scope.get_data(_segment+1,query_id)
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function percentage(x,y) {
|
||||||
|
return 100*(y-x)/x
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.remove_query = function(q) {
|
||||||
|
$scope.panel.query = _.without($scope.panel.query,q);
|
||||||
|
$scope.get_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.add_query = function(label,query) {
|
||||||
|
$scope.panel.query.unshift({
|
||||||
|
query: query,
|
||||||
|
label: label,
|
||||||
|
});
|
||||||
|
$scope.get_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.set_refresh = function (state) {
|
||||||
|
$scope.refresh = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.close_edit = function() {
|
||||||
|
if($scope.refresh)
|
||||||
|
$scope.get_data();
|
||||||
|
$scope.refresh = false;
|
||||||
|
$scope.$emit('render');
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_time(time) {
|
||||||
|
$scope.time = time;
|
||||||
|
$scope.index = time.index || $scope.index
|
||||||
|
$scope.get_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user