New timepicker, collapsable query and filter sections

This commit is contained in:
Rashid Khan 2013-10-05 18:41:20 -05:00
parent 377aa16f2c
commit d5bc550bb1
24 changed files with 728 additions and 112 deletions

View File

@ -1,5 +1,5 @@
define(['jquery', 'underscore'],
function($, _) {
define(['jquery', 'underscore','moment'],
function($, _, moment) {
'use strict';
var kbn = {};
@ -247,6 +247,139 @@ function($, _) {
return new Date(new Date().getTime() - (kbn.interval_to_ms(string)));
};
/* This is a simplified version of elasticsearch's date parser */
kbn.parseDate = function(text) {
if(_.isDate(text)) {
return text;
}
var time,
mathString = "",
index,
parseString;
if (text.substring(0,3) === "now") {
time = new Date();
mathString = text.substring("now".length);
} else {
index = text.indexOf("||");
parseString;
if (index === -1) {
parseString = text;
mathString = ""; // nothing else
} else {
parseString = text.substring(0, index);
mathString = text.substring(index + 2);
}
// We're going to just require ISO8601 timestamps, k?
time = new Date(parseString);
}
if (!mathString.length) {
return time;
}
//return [time,parseString,mathString];
return kbn.parseDateMath(mathString, time);
};
kbn.parseDateMath = function(mathString, time, roundUp) {
var dateTime = moment(time);
for (var i = 0; i < mathString.length; ) {
var c = mathString.charAt(i++),
type,
num,
unit;
if (c === '/') {
type = 0;
} else if (c === '+') {
type = 1;
} else if (c === '-') {
type = 2;
} else {
console.log("no type");
return false;
}
if (isNaN(mathString.charAt(i))) {
num = 1;
} else {
var numFrom = i;
while (!isNaN(mathString.charAt(i))) {
i++;
}
num = parseInt(mathString.substring(numFrom, i),10);
}
if (type === 0) {
// rounding is only allowed on whole numbers
if (num !== 1) {
console.log("nbad rounding");
return false;
}
}
unit = mathString.charAt(i++);
switch (unit) {
case 'M':
if (type === 0) {
roundUp ? dateTime.endOf('month') : dateTime.startOf('month');
} else if (type === 1) {
dateTime.add('months',num);
} else if (type === 2) {
dateTime.subtract('months',num);
}
break;
case 'w':
if (type === 0) {
roundUp ? dateTime.endOf('week') : dateTime.startOf('week');
} else if (type === 1) {
dateTime.add('weeks',num);
} else if (type === 2) {
dateTime.subtract('weeks',num);
}
break;
case 'd':
if (type === 0) {
roundUp ? dateTime.endOf('day') : dateTime.startOf('day');
} else if (type === 1) {
dateTime.add('days',num);
} else if (type === 2) {
dateTime.subtract('days',num);
}
break;
case 'h':
case 'H':
if (type === 0) {
roundUp ? dateTime.endOf('hour') : dateTime.startOf('hour');
} else if (type === 1) {
dateTime.add('hours',num);
} else if (type === 2) {
dateTime.subtract('hours',num);
}
break;
case 'm':
if (type === 0) {
roundUp ? dateTime.endOf('minute') : dateTime.startOf('minute');
} else if (type === 1) {
dateTime.add('minutes',num);
} else if (type === 2) {
dateTime.subtract('minutes',num);
}
break;
case 's':
if (type === 0) {
roundUp ? dateTime.endOf('second') : dateTime.startOf('second');
} else if (type === 1) {
dateTime.add('seconds',num);
} else if (type === 2) {
dateTime.subtract('seconds',num);
}
break;
default:
console.log("unknown unit");
return false;
}
}
return dateTime.toDate();
};
// LOL. hahahahaha. DIE.
kbn.flatten_json = function(object,root,array) {
if (typeof array === 'undefined') {

View File

@ -1,5 +1,5 @@
define([
'./dash',
'./dashLoader',
'./row',
'./row'
], function () {});

View File

@ -16,6 +16,7 @@ function (angular, app, _) {
collapsable: true,
editable: true,
panels: [],
notice: false
};
_.defaults($scope.row,_d);
@ -34,6 +35,8 @@ function (angular, app, _) {
$timeout(function() {
$scope.$broadcast('render');
});
} else {
row.notice = false;
}
};

View File

@ -3,6 +3,7 @@ define([
'./arrayJoin',
'./dashUpload',
'./kibanaPanel',
'./kibanaSimplePanel',
'./ngBlur',
'./ngModelOnBlur',
'./tip',

View File

@ -0,0 +1,53 @@
define([
'angular'
],
function (angular) {
'use strict';
angular
.module('kibana.directives')
.directive('kibanaSimplePanel', function($compile) {
return {
restrict: 'E',
link: function($scope, elem, attr) {
// once we have the template, scan it for controllers and
// load the module.js if we have any
// compile the module and uncloack. We're done
function loadModule($module) {
$module.appendTo(elem);
/* jshint indent:false */
$compile(elem.contents())($scope);
elem.removeClass("ng-cloak");
}
$scope.$watch(attr.type, function (name) {
elem.addClass("ng-cloak");
// load the panels module file, then render it in the dom.
$scope.require([
'jquery',
'text!panels/'+name+'/module.html'
], function ($, moduleTemplate) {
var $module = $(moduleTemplate);
// top level controllers
var $controllers = $module.filter('ngcontroller, [ng-controller], .ng-controller');
// add child controllers
$controllers = $controllers.add($module.find('ngcontroller, [ng-controller], .ng-controller'));
if ($controllers.length) {
$scope.require([
'panels/'+name+'/module'
], function() {
loadModule($module);
});
} else {
loadModule($module);
}
});
});
}
};
});
});

View File

@ -1,4 +1,4 @@
define(['angular', 'jquery', 'underscore'], function (angular, $, _) {
define(['angular', 'jquery', 'underscore', 'moment'], function (angular, $, _, moment) {
'use strict';
var module = angular.module('kibana.filters');
@ -42,6 +42,16 @@ define(['angular', 'jquery', 'underscore'], function (angular, $, _) {
};
});
module.filter('moment', function() {
return function(date,mode) {
switch(mode) {
case 'ago':
return moment(date).fromNow();
}
return moment(date).fromNow();
};
});
module.filter('noXml', function() {
var noXml = function(text) {
return _.isString(text)

View File

@ -55,7 +55,7 @@
<i class="filter-action pointer icon-remove" bs-tooltip="'Remove'" ng-click="remove(id)"></i>
<i class="filter-action pointer" ng-class="{'icon-check': filterSrv.list[id].active,'icon-check-empty': !filterSrv.list[id].active}" bs-tooltip="'Toggle'" ng-click="toggle(id)"></i>
<i class="filter-action pointer icon-edit" ng-hide="filterSrv.list[id].editing" bs-tooltip="'Edit'" ng-click="filterSrv.list[id].editing = true"></i>
<i class="filter-action pointer icon-edit" ng-hide="filterSrv.list[id].editing || !isEditable(filterSrv.list[id])" bs-tooltip="'Edit'" ng-click="filterSrv.list[id].editing = true"></i>
</div>

View File

@ -27,6 +27,10 @@ function (angular, app, _) {
};
_.defaults($scope.panel,_d);
$scope.$on('filter', function() {
$scope.row.notice = true;
});
$scope.init = function() {
$scope.filterSrv = filterSrv;
};

View File

@ -137,7 +137,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
* @return {[type]} [description]
*/
$scope.get_time_range = function () {
var range = $scope.range = filterSrv.timeRange('min');
var range = $scope.range = filterSrv.timeRange('last');
return range;
};
@ -289,7 +289,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
// function $scope.zoom
// factor :: Zoom factor, so 0.5 = cuts timespan in half, 2 doubles timespan
$scope.zoom = function(factor) {
var _range = filterSrv.timeRange('min');
var _range = filterSrv.timeRange('last');
var _timespan = (_range.to.valueOf() - _range.from.valueOf());
var _center = _range.to.valueOf() - _timespan/2;
@ -496,8 +496,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
elem.bind("plotselected", function (event, ranges) {
filterSrv.set({
type : 'time',
from : moment.utc(ranges.xaxis.from),
to : moment.utc(ranges.xaxis.to),
from : moment.utc(ranges.xaxis.from).toDate(),
to : moment.utc(ranges.xaxis.to).toDate(),
field : scope.panel.time_field
});
});

View File

@ -0,0 +1,78 @@
<div class="modal-body">
<style>
.timepicker-to-column {
margin-top: 10px;
}
.timepicker-input input {
outline: 0 !important;
border: 0px !important;
-webkit-box-shadow: 0;
-moz-box-shadow: 0;
box-shadow: 0;
position: relative;
}
.timepicker-input input::-webkit-outer-spin-button,
.timepicker-input input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input.timepicker-date {
width: 90px;
}
input.timepicker-hms {
width: 20px;
}
input.timepicker-ms {
width: 25px;
}
div.timepicker-now {
float: right;
}
</style>
<div class="timepicker form-horizontal">
<form name="input">
<div class="timepicker-from-column">
<label class="small">From</label>
<div class="fake-input timepicker-input">
<input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.from.date" data-date-format="mm/dd/yyyy" required bs-datepicker />@
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.hour" required ng-pattern="patterns.hour" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.minute" required ng-pattern="patterns.minute" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.second" required ng-pattern="patterns.second" onClick="this.select();"/>.
<input class="timepicker-ms" type="text" maxlength="3" ng-change="validate(temptime)" ng-model="temptime.from.millisecond" required ng-pattern="patterns.millisecond" onClick="this.select();"/>
</div>
</div>
<div class="timepicker-to-column">
<label class="small">To (<a class="link" ng-class="{'strong':panel.now}" ng-click="setNow();panel.now=true">now</a>)</label>
<div class="fake-input timepicker-input">
<div ng-hide="panel.now">
<input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.to.date" data-date-format="mm/dd/yyyy" required bs-datepicker />@
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.hour" required ng-pattern="patterns.hour" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.minute" required ng-pattern="patterns.minute" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.second" required ng-pattern="patterns.second" onClick="this.select();"/>.
<input class="timepicker-ms" type="text" maxlength="3" ng-change="validate(temptime)" ng-model="temptime.to.millisecond" required ng-pattern="patterns.millisecond" onClick="this.select();"/>
</div>
<span type="text" ng-show="panel.now" ng-disabled="panel.now">&nbsp <i class="pointer icon-remove-sign" ng-click="setNow();panel.now=false"></i> Right Now <input type="text" name="dummy" style="visibility:hidden" /></span>
</div>
</div>
</form>
<div class="clearfix"></div>
</div>
</div>
<div class="modal-footer">
<form name="input" style="margin-bottom:0">
<span class="" ng-hide="input.$valid">Invalid date or range</span>
<button ng-click="setAbsoluteTimeFilter(validate(temptime));dismiss();" ng-disabled="!input.$valid" class="btn btn-success">Apply</button>
<button ng-click="dismiss();" class="btn btn-danger">Cancel</button>
</form>
</div>

View File

@ -0,0 +1,10 @@
<div class="row-fluid">
<div class="span6">
<label class="small">Relative time options <small>comma seperated</small></label>
<input type="text" array-join class="input-large" ng-model="panel.time_options">
</div>
<div class="span3">
<label class="small">Time Field</label>
<input type="text" class="input-small" ng-model="panel.timefield">
</div>
</div>

View File

@ -0,0 +1,39 @@
<div ng-controller='timepicker2' ng-init="init()">
<style>
.timepicker-timestring {
font-weight: normal;
}
.timepicker-dropdown {
margin: 0px !important;
border: 0px !important;
}
</style>
<!-- This is a complete hack. The form actually exists in the modal, but due to transclusion
$scope.input isn't available on the controller unless the form element is in this file -->
<form name="input" style="margin:3px 0 0 0">
<ul class="nav nav-pills timepicker-dropdown">
<li class="dropdown" bs-tooltip="(time.from.date | date:'yyyy-MM-dd HH:mm:ss.sss') + ' to ' +(time.to.date | date:'yyyy-MM-dd HH:mm:ss.sss')" data-placement="bottom" ng-click="dismiss();">
<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="#">
<span class="small" ng-show="filterSrv.idsByType('time').length">
<span class="pointer" ng-hide="panel.now">{{time.from.date | date:'MMM d, y HH:mm:ss'}}</span>
<span class="pointer" ng-show="panel.now">{{time.from.date | moment:'ago'}}</span>
to
<span class="pointer" ng-hide="panel.now" >{{time.to.date | date:'MMM d, y HH:mm:ss'}}</span>
<span class="pointer" ng-show="panel.now">{{time.to.date | moment:'ago'}}</span>
</span>
<span ng-hide="filterSrv.idsByType('time').length">Set a time filter</span>
<i class="small icon-caret-down"></i>
</a>
<ul class="dropdown-menu">
<li ng-repeat='timespan in panel.time_options'>
<a ng-click="setRelativeFilter(timespan)">Last {{timespan}}</a>
</li>
<li><a ng-click="customTime()">Custom</a></li>
</ul>
</li>
</ul>
</form>
</div>

View File

@ -0,0 +1,223 @@
/*
## Timepicker2
### Parameters
* mode :: The default mode of the panel. Options: 'relative', 'absolute' 'since' Default: 'relative'
* time_options :: An array of possible time options. Default: ['5m','15m','1h','6h','12h','24h','2d','7d','30d']
* timespan :: The default options selected for the relative view. Default: '15m'
* timefield :: The field in which time is stored in the document.
* refresh: Object containing refresh parameters
* enable :: true/false, enable auto refresh by default. Default: false
* interval :: Seconds between auto refresh. Default: 30
* min :: The lowest interval a user may set
*/
define([
'angular',
'app',
'underscore',
'moment',
'kbn'
],
function (angular, app, _, moment, kbn) {
'use strict';
var module = angular.module('kibana.panels.timepicker2', []);
app.useModule(module);
module.controller('timepicker2', function($scope, $modal, $q, filterSrv) {
$scope.panelMeta = {
status : "Stable",
description : "A panel for controlling the time range filters. If you have time based data, "+
" or if you're using time stamped indices, you need one of these"
};
// Set and populate defaults
var _d = {
status : "Stable",
mode : "relative",
time_options : ['5m','15m','1h','6h','12h','24h','2d','7d','30d'],
timespan : '15m',
timefield : '@timestamp',
timeformat : "",
refresh : {
enable : false,
interval: 30,
min : 3
}
};
_.defaults($scope.panel,_d);
var customTimeModal = $modal({
template: './app/panels/timepicker2/custom.html',
persist: true,
show: false,
scope: $scope,
keyboard: false
});
$scope.filterSrv = filterSrv;
// ng-pattern regexs
$scope.patterns = {
date: /^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/,
hour: /^([01]?[0-9]|2[0-3])$/,
minute: /^[0-5][0-9]$/,
second: /^[0-5][0-9]$/,
millisecond: /^[0-9]*$/
};
$scope.$on('refresh', function(){$scope.init();});
$scope.init = function() {
var time = filterSrv.timeRange('last');
if(time) {
$scope.panel.now = filterSrv.timeRange(false).to === "now" ? true : false;
$scope.time = getScopeTimeObj(time.from,time.to);
}
};
$scope.customTime = function() {
// Assume the form is valid since we're setting it to something valid
$scope.input.$setValidity("dummy", true);
$scope.temptime = cloneTime($scope.time);
// Date picker needs the date to be at the start of the day
$scope.temptime.from.date.setHours(0,0,0,0);
$scope.temptime.to.date.setHours(0,0,0,0);
$q.when(customTimeModal).then(function(modalEl) {
modalEl.modal('show');
});
};
// Constantly validate the input of the fields. This function does not change any date variables
// outside of its own scope
$scope.validate = function(time) {
// Assume the form is valid. There is a hidden dummy input for invalidating it programatically.
$scope.input.$setValidity("dummy", true);
var _from = datepickerToLocal(time.from.date),
_to = datepickerToLocal(time.to.date),
_t = time;
if($scope.input.$valid) {
_from.setHours(_t.from.hour,_t.from.minute,_t.from.second,_t.from.millisecond);
_to.setHours(_t.to.hour,_t.to.minute,_t.to.second,_t.to.millisecond);
// Check that the objects are valid and to is after from
if(isNaN(_from.getTime()) || isNaN(_to.getTime()) || _from.getTime() >= _to.getTime()) {
$scope.input.$setValidity("dummy", false);
return false;
}
} else {
return false;
}
return {from:_from,to:_to};
};
$scope.setNow = function() {
$scope.time.to = getTimeObj(new Date());
};
/*
time : {
from: Date
to: Date
}
*/
$scope.setAbsoluteTimeFilter = function (time) {
// Create filter object
var _filter = _.clone(time);
_filter.type = 'time';
_filter.field = $scope.panel.timefield;
if($scope.panel.now) {
_filter.to = "now";
}
// Clear all time filters, set a new one
filterSrv.removeByType('time',true);
// Set the filter
$scope.panel.filter_id = filterSrv.set(_filter);
// Update our representation
$scope.time = getScopeTimeObj(time.from,time.to);
return $scope.panel.filter_id;
};
$scope.setRelativeFilter = function(timespan) {
$scope.panel.now = true;
// Create filter object
var _filter = {
type : 'time',
field : $scope.panel.timefield,
from : "now-"+timespan,
to: "now"
};
// Clear all time filters, set a new one
filterSrv.removeByType('time',true);
// Set the filter
$scope.panel.filter_id = filterSrv.set(_filter);
// Update our representation
$scope.time = getScopeTimeObj(kbn.parseDate(_filter.from),new Date());
return $scope.panel.filter_id;
};
var pad = function(n, width, z) {
z = z || '0';
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
};
var cloneTime = function(time) {
var _n = {
from: _.clone(time.from),
to: _.clone(time.to)
};
// Create new dates as _.clone is shallow.
_n.from.date = new Date(_n.from.date);
_n.to.date = new Date(_n.to.date);
return _n;
};
var getScopeTimeObj = function(from,to) {
return {
from: getTimeObj(from),
to: getTimeObj(to)
};
};
var getTimeObj = function(date) {
return {
date: new Date(date),
hour: pad(date.getHours(),2),
minute: pad(date.getMinutes(),2),
second: pad(date.getSeconds(),2),
millisecond: pad(date.getMilliseconds(),3)
};
};
// Do not use the results of this function unless you plan to use setHour/Minutes/etc on the result
var datepickerToLocal = function(date) {
console.log(date);
date = moment(date).clone().toDate();
console.log(moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toDate());
return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toDate();
};
});
});

View File

@ -0,0 +1,5 @@
<form name="refreshPopover" class='form-inline input-append' style="margin:0px">
<label><small>Interval (seconds)</small></label><br>
<input type="number" class="input-mini" ng-model="refresh_interval">
<button type="button" class="btn" ng-click="set_interval(refresh_interval);dismiss()"><i class="icon-ok"></i></button>
</form>

View File

@ -87,7 +87,8 @@ function (angular, app, _, kbn) {
timeField = timeField[0];
}
$scope.time = filterSrv.timeRange('min');
// This logic can be simplifie greatly with the new kbn.parseDate
$scope.time = filterSrv.timeRange('last');
$scope.old_time = {
from : new Date($scope.time.from.getTime() - kbn.interval_to_ms($scope.panel.ago)),
to : new Date($scope.time.to.getTime() - kbn.interval_to_ms($scope.panel.ago))

View File

@ -1,3 +1,4 @@
<li ng-controller="RowCtrl"><kibana-simple-panel type="'timepicker2'" ng-cloak></kibana-simple-panel></li>
<li><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/dashboard'><i class='icon-home'></i></a></li>

View File

@ -1,77 +1,93 @@
<div class="row-fluid container" style="margin-top:10px; width:98%">
<!-- is there a better way to repeat without actually affecting the page? -->
<nil ng-repeat="row in dashboard.current.pulldowns" ng-controller="RowCtrl">
<div class="top-row-open" ng-hide="row.collapse">
<kibana-simple-panel type="row.type" ng-cloak></kibana-simple-panel>
</div>
<div class="top-row-close pointer" ng-click="toggle_row(row);dismiss();" bs-tooltip="'Toggle '+row.type" data-placement="bottom">
<span class="small row-text">{{row.type}}</span>
<i class="small" ng-class="{'icon-expand':row.collapse,'icon-collapse-top':!row.collapse}"></i>
<i class="small icon-circle text-warning" ng-show="row.notice && row.collapse"></i>
</div>
</nil>
<!-- Rows -->
<div class="row-fluid kibana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.current.rows" ng-style="row_style(row)">
<div class="row-control">
<div class="row-fluid" style="padding:0px;margin:0px;position:relative;">
<div class="container-fluid main">
<div class="row-fluid">
<div class="row-fluid container" style="margin-top:10px; width:98%">
<!-- Rows -->
<div class="row-fluid kibana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.current.rows" ng-style="row_style(row)">
<div class="row-control">
<div class="row-fluid" style="padding:0px;margin:0px;position:relative;">
<div class="row-close span12" ng-show="row.collapse" data-placement="bottom" >
<span class="row-button" bs-modal="'app/partials/roweditor.html'" class="pointer">
<i bs-tooltip="'Configure row'" data-placement="right" ng-show="row.editable" class="icon-cog pointer"></i>
</span>
<span class="row-button" ng-click="toggle_row(row)" ng-show="row.collapsable">
<i bs-tooltip="'Expand row'" data-placement="right" ng-show="row.editable" class="icon-expand pointer" ></i>
</span>
<span class="row-button row-text" ng-click="toggle_row(row)" ng-class="{'pointer':row.collapsable}">{{row.title || 'Row '+$index}}</span>
</div>
<div style="text-align:left" class="row-open" ng-show="!row.collapse">
<span ng-show="row.collapsable">
<i bs-tooltip="'Hide row'" data-placement="right" class="icon-collapse-top" ng-click="toggle_row(row)"></i>
<br>
</span>
<span bs-modal="'app/partials/roweditor.html'" ng-show="row.editable">
<i bs-tooltip="'Configure row'" data-placement="right" class="icon-cog pointer"></i>
<br>
</span>
<span ng-show="rowSpan(row) == 12 && row.editable">
<i bs-tooltip="'Row full. Create a new row to add more panels'" data-placement="right" class="icon-trello"></i>
<br>
</span>
<span ng-show="rowSpan(row) > 12">
<i bs-tooltip="'Total span > 12. This row may format poorly'" data-placement="right" class="icon-warning-sign text-warning"></i>
<br>
</span>
</div>
</div>
<div class="row-fluid" style="padding-top:0px" ng-if="!row.collapse">
<!-- Panels -->
<div ng-repeat="(name, panel) in row.panels|filter:isPanel" ng-hide="panel.span == 0 || panel.hide" class="span{{panel.span}} panel nospace" style="min-height:{{row.height}}; position:relative" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:$index,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver(true)',onOut:'panelMoveOut'}">
<!-- Error Panel -->
<div class="row-fluid">
<div class="span12 alert alert-error panel-error" ng-hide="!panel.error">
<a class="close" ng-click="panel.error=false">&times;</a>
<i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}}
<div class="row-close span12" ng-show="row.collapse" data-placement="bottom" >
<span class="row-button" bs-modal="'app/partials/roweditor.html'" class="pointer">
<i bs-tooltip="'Configure row'" data-placement="right" ng-show="row.editable" class="icon-cog pointer"></i>
</span>
<span class="row-button" ng-click="toggle_row(row)" ng-show="row.collapsable">
<i bs-tooltip="'Expand row'" data-placement="right" ng-show="row.editable" class="icon-expand pointer" ></i>
</span>
<span class="row-button row-text" ng-click="toggle_row(row)" ng-class="{'pointer':row.collapsable}">{{row.title || 'Row '+$index}}</span>
</div>
<div style="text-align:left" class="row-open" ng-show="!row.collapse">
<span ng-show="row.collapsable">
<i bs-tooltip="'Hide row'" data-placement="right" class="icon-collapse-top" ng-click="toggle_row(row)"></i>
<br>
</span>
<span bs-modal="'app/partials/roweditor.html'" ng-show="row.editable">
<i bs-tooltip="'Configure row'" data-placement="right" class="icon-cog pointer"></i>
<br>
</span>
<span ng-show="rowSpan(row) == 12 && row.editable">
<i bs-tooltip="'Row full. Create a new row to add more panels'" data-placement="right" class="icon-trello"></i>
<br>
</span>
<span ng-show="rowSpan(row) > 12">
<i bs-tooltip="'Total span > 12. This row may format poorly'" data-placement="right" class="icon-warning-sign text-warning"></i>
<br>
</span>
</div>
</div>
<!-- Content Panel -->
<div class="row-fluid" style="position:relative" ng-class="{'dragInProgress':dashboard.panelDragging}" >
<kibana-panel type="panel.type" ng-cloak></kibana-panel>
<div class="row-fluid" style="padding-top:0px" ng-if="!row.collapse">
<!-- Panels -->
<div ng-repeat="(name, panel) in row.panels|filter:isPanel" ng-hide="panel.span == 0 || panel.hide" class="span{{panel.span}} panel nospace" style="min-height:{{row.height}}; position:relative" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:$index,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver(true)',onOut:'panelMoveOut'}">
<!-- Error Panel -->
<div class="row-fluid">
<div class="span12 alert alert-error panel-error" ng-hide="!panel.error">
<a class="close" ng-click="panel.error=false">&times;</a>
<i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}}
</div>
</div>
<!-- Content Panel -->
<div class="row-fluid" style="position:relative" ng-class="{'dragInProgress':dashboard.panelDragging}" >
<kibana-panel type="panel.type" ng-cloak></kibana-panel>
</div>
</div>
<div ng-hide="(12-rowSpan(row)) < 1 || !dashboard.current.panel_hints" class="panel span{{(12-rowSpan(row))}}" ng-class="{'dragInProgress':dashboard.panelDragging}" style="height:{{row.height}};" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:row.panels.length,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver',onOut:'panelMoveOut'}">
<span bs-modal="'app/partials/roweditor.html'" ng-show="row.editable && !dashboard.panelDragging">
<i ng-hide="rowSpan(row) == 0" class="pointer icon-plus-sign" ng-click="editor.index = 2" bs-tooltip="'Add a panel to this row'" data-placement="right"></i>
<span ng-click="editor.index = 2" style="margin-top: 8px; margin-left: 3px" ng-show="rowSpan(row) == 0" class="btn btn-mini">Add panel to empty row</btn>
</span>
</div>
</div>
</div>
<div ng-hide="(12-rowSpan(row)) < 1 || !dashboard.current.panel_hints" class="panel span{{(12-rowSpan(row))}}" ng-class="{'dragInProgress':dashboard.panelDragging}" style="height:{{row.height}};" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:row.panels.length,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver',onOut:'panelMoveOut'}">
<span bs-modal="'app/partials/roweditor.html'" ng-show="row.editable && !dashboard.panelDragging">
<i ng-hide="rowSpan(row) == 0" class="pointer icon-plus-sign" ng-click="editor.index = 2" bs-tooltip="'Add a panel to this row'" data-placement="right"></i>
<span ng-click="editor.index = 2" style="margin-top: 8px; margin-left: 3px" ng-show="rowSpan(row) == 0" class="btn btn-mini">Add panel to empty row</btn>
</span>
</div>
</div>
<div class="row-fluid" ng-show='dashboard.current.editable'>
<div class="span12" style="text-align:right;">
<span style="margin-left: 0px;" class="pointer btn btn-mini" bs-modal="'app/partials/dasheditor.html'">
<span ng-click="editor.index = 2"><i class="icon-plus-sign"></i> ADD A ROW</span>
</span>
</div>
</div>
</div>
</div>
<div class="row-fluid" ng-show='dashboard.current.editable'>
<div class="span12" style="text-align:right;">
<span style="margin-left: 0px;" class="pointer btn btn-mini" bs-modal="'app/partials/dasheditor.html'">
<span ng-click="editor.index = 2"><i class="icon-plus-sign"></i> ADD A ROW</span>
</span>
</div>
</div>
</div>

View File

@ -25,6 +25,14 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
failover: false,
panel_hints: true,
rows: [],
pulldowns: [
{
type: 'query',
},
{
type: 'filtering'
}
],
services: {},
loader: {
save_gist: false,
@ -109,7 +117,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
this.refresh = function() {
if(self.current.index.interval !== 'none') {
if(filterSrv.idsByType('time').length > 0) {
var _range = filterSrv.timeRange('min');
var _range = filterSrv.timeRange('last');
kbnIndex.indices(_range.from,_range.to,
self.current.index.pattern,self.current.index.interval
).then(function (p) {
@ -175,7 +183,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
filterSrv.init();
// If there's an index interval set and no existing time filter, send a refresh to set one
if(dashboard.index.interval !== 'none' && filterSrv.idsByType('time').length === 0) {
if(dashboard.index.interval !== 'none') {
self.refresh();
}

View File

@ -1,8 +1,9 @@
define([
'angular',
'underscore',
'config'
], function (angular, _, config) {
'config',
'kbn'
], function (angular, _, config, kbn) {
'use strict';
var module = angular.module('kibana.services');
@ -35,10 +36,13 @@ define([
self.ids = dashboard.current.services.filter.ids;
_f = dashboard.current.services.filter;
// Date filters hold strings now, not dates
/*
_.each(self.getByType('time',true),function(time) {
self.list[time.id].from = new Date(time.from);
self.list[time.id].to = new Date(time.to);
});
*/
};
@ -78,6 +82,7 @@ define([
dashboard.refresh();
},0);
}
$rootScope.$broadcast('filter');
return _r;
};
@ -101,6 +106,7 @@ define([
dashboard.refresh();
},0);
}
$rootScope.$broadcast('filter');
return _r;
};
@ -150,10 +156,11 @@ define([
switch(filter.type)
{
case 'time':
return ejs.RangeFilter(filter.field)
.from(filter.from.valueOf())
//.from("now-1d")
.to(filter.to.valueOf());
var _f = ejs.RangeFilter(filter.field).from(kbn.parseDate(filter.from).valueOf());
if(!_.isUndefined(filter.to)) {
_f = _f.to(filter.to.valueOf());
}
return _f;
case 'range':
return ejs.RangeFilter(filter.field)
.from(filter.from)
@ -187,26 +194,26 @@ define([
return _.pluck(self.getByType('time'),'field');
};
// This special function looks for all time filters, and returns a time range according to the mode
// No idea when max would actually be used
this.timeRange = function(mode) {
var _t = _.where(self.list,{type:'time',active:true});
if(_t.length === 0) {
// Parse is used when you need to know about the raw filter
this.timeRange = function(parse) {
var _t = _.last(_.where(self.list,{type:'time',active:true}));
if(_.isUndefined(_t)) {
return false;
}
switch(mode) {
case "min":
if(parse === false) {
return {
from: new Date(_.max(_.pluck(_t,'from'))),
to: new Date(_.min(_.pluck(_t,'to')))
from: _t.from,
to: _t.to
};
case "max":
} else {
var
_from = _t.from,
_to = _t.to || new Date();
return {
from: new Date(_.min(_.pluck(_t,'from'))),
to: new Date(_.max(_.pluck(_t,'to')))
from : kbn.parseDate(_from),
to : kbn.parseDate(_to)
};
default:
return false;
}
};

View File

@ -51,4 +51,4 @@ function (Settings) {
'terms'
]
});
});
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -34,16 +34,14 @@
<div class="navbar-inner">
<div class="container-fluid">
<span class="brand">{{dashboard.current.title}}</span>
<ul class="nav pull-right" ng-controller='dashLoader' ng-init="init()" ng-include="'app/partials/dashLoader.html'">
</ul>
</div>
</div>
</div>
<div class="container-fluid main">
<div class="row-fluid">
<div ng-view></div>
</div>
</div>
</body>
<div ng-view></div>
</body>
</html>

View File

@ -16,6 +16,10 @@ div.fake-input {
.border-radius(@inputBorderRadius @inputBorderRadius @inputBorderRadius @inputBorderRadius);
}
form input.ng-invalid {
color: @errorText;
}
.editor-title {
margin-right: 10px;
font-size: 1.7em;
@ -80,6 +84,22 @@ div.fake-input {
font-weight: 200;
}
.top-row-open {
background: darken(@bodyBackground, 3%);
padding: 5px 25px 5px 25px;
}
.top-row-close {
outline: 1px solid darken(@bodyBackground, 10%);
border-top: 1px solid lighten(@bodyBackground, 10%);
padding: 0px;
margin: 1px 0px 0px 0px;
text-align: center;
min-height: 16px !important;
line-height: 16px;
background: darken(@bodyBackground, 3%);
}
.row-open i {
font-size: 10pt;
}
@ -146,9 +166,15 @@ div.fake-input {
}
.link {
color: @linkColor;
cursor: pointer;
}
.link:hover {
color: @linkColorHover;
}
.pointer:hover {
color: @linkColorHover;
}
@ -157,10 +183,6 @@ div.fake-input {
cursor: pointer;
}
.link:hover {
color: @linkColorHover;
}
.popover {
max-width: 480px;
}
@ -191,6 +213,10 @@ div.fake-input {
font-weight: bold;
}
a {
cursor: pointer;
}
.normal {
font-weight: normal;
}