mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
New timepicker, collapsable query and filter sections
This commit is contained in:
parent
377aa16f2c
commit
d5bc550bb1
@ -1,5 +1,5 @@
|
|||||||
define(['jquery', 'underscore'],
|
define(['jquery', 'underscore','moment'],
|
||||||
function($, _) {
|
function($, _, moment) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var kbn = {};
|
var kbn = {};
|
||||||
@ -247,6 +247,139 @@ function($, _) {
|
|||||||
return new Date(new Date().getTime() - (kbn.interval_to_ms(string)));
|
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.
|
// LOL. hahahahaha. DIE.
|
||||||
kbn.flatten_json = function(object,root,array) {
|
kbn.flatten_json = function(object,root,array) {
|
||||||
if (typeof array === 'undefined') {
|
if (typeof array === 'undefined') {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
define([
|
define([
|
||||||
'./dash',
|
'./dash',
|
||||||
'./dashLoader',
|
'./dashLoader',
|
||||||
'./row',
|
'./row'
|
||||||
], function () {});
|
], function () {});
|
@ -16,6 +16,7 @@ function (angular, app, _) {
|
|||||||
collapsable: true,
|
collapsable: true,
|
||||||
editable: true,
|
editable: true,
|
||||||
panels: [],
|
panels: [],
|
||||||
|
notice: false
|
||||||
};
|
};
|
||||||
|
|
||||||
_.defaults($scope.row,_d);
|
_.defaults($scope.row,_d);
|
||||||
@ -34,6 +35,8 @@ function (angular, app, _) {
|
|||||||
$timeout(function() {
|
$timeout(function() {
|
||||||
$scope.$broadcast('render');
|
$scope.$broadcast('render');
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
row.notice = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ define([
|
|||||||
'./arrayJoin',
|
'./arrayJoin',
|
||||||
'./dashUpload',
|
'./dashUpload',
|
||||||
'./kibanaPanel',
|
'./kibanaPanel',
|
||||||
|
'./kibanaSimplePanel',
|
||||||
'./ngBlur',
|
'./ngBlur',
|
||||||
'./ngModelOnBlur',
|
'./ngModelOnBlur',
|
||||||
'./tip',
|
'./tip',
|
||||||
|
53
src/app/directives/kibanaSimplePanel.js
Normal file
53
src/app/directives/kibanaSimplePanel.js
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -1,4 +1,4 @@
|
|||||||
define(['angular', 'jquery', 'underscore'], function (angular, $, _) {
|
define(['angular', 'jquery', 'underscore', 'moment'], function (angular, $, _, moment) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var module = angular.module('kibana.filters');
|
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() {
|
module.filter('noXml', function() {
|
||||||
var noXml = function(text) {
|
var noXml = function(text) {
|
||||||
return _.isString(text)
|
return _.isString(text)
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
<i class="filter-action pointer icon-remove" bs-tooltip="'Remove'" ng-click="remove(id)"></i>
|
<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" 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>
|
</div>
|
||||||
|
|
||||||
|
@ -27,6 +27,10 @@ function (angular, app, _) {
|
|||||||
};
|
};
|
||||||
_.defaults($scope.panel,_d);
|
_.defaults($scope.panel,_d);
|
||||||
|
|
||||||
|
$scope.$on('filter', function() {
|
||||||
|
$scope.row.notice = true;
|
||||||
|
});
|
||||||
|
|
||||||
$scope.init = function() {
|
$scope.init = function() {
|
||||||
$scope.filterSrv = filterSrv;
|
$scope.filterSrv = filterSrv;
|
||||||
};
|
};
|
||||||
|
@ -137,7 +137,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
|||||||
* @return {[type]} [description]
|
* @return {[type]} [description]
|
||||||
*/
|
*/
|
||||||
$scope.get_time_range = function () {
|
$scope.get_time_range = function () {
|
||||||
var range = $scope.range = filterSrv.timeRange('min');
|
var range = $scope.range = filterSrv.timeRange('last');
|
||||||
return range;
|
return range;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -289,7 +289,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
|||||||
// function $scope.zoom
|
// function $scope.zoom
|
||||||
// factor :: Zoom factor, so 0.5 = cuts timespan in half, 2 doubles timespan
|
// factor :: Zoom factor, so 0.5 = cuts timespan in half, 2 doubles timespan
|
||||||
$scope.zoom = function(factor) {
|
$scope.zoom = function(factor) {
|
||||||
var _range = filterSrv.timeRange('min');
|
var _range = filterSrv.timeRange('last');
|
||||||
var _timespan = (_range.to.valueOf() - _range.from.valueOf());
|
var _timespan = (_range.to.valueOf() - _range.from.valueOf());
|
||||||
var _center = _range.to.valueOf() - _timespan/2;
|
var _center = _range.to.valueOf() - _timespan/2;
|
||||||
|
|
||||||
@ -496,8 +496,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
|
|||||||
elem.bind("plotselected", function (event, ranges) {
|
elem.bind("plotselected", function (event, ranges) {
|
||||||
filterSrv.set({
|
filterSrv.set({
|
||||||
type : 'time',
|
type : 'time',
|
||||||
from : moment.utc(ranges.xaxis.from),
|
from : moment.utc(ranges.xaxis.from).toDate(),
|
||||||
to : moment.utc(ranges.xaxis.to),
|
to : moment.utc(ranges.xaxis.to).toDate(),
|
||||||
field : scope.panel.time_field
|
field : scope.panel.time_field
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
78
src/app/panels/timepicker2/custom.html
Normal file
78
src/app/panels/timepicker2/custom.html
Normal 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">  <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>
|
10
src/app/panels/timepicker2/editor.html
Executable file
10
src/app/panels/timepicker2/editor.html
Executable 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>
|
39
src/app/panels/timepicker2/module.html
Executable file
39
src/app/panels/timepicker2/module.html
Executable 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>
|
223
src/app/panels/timepicker2/module.js
Executable file
223
src/app/panels/timepicker2/module.js
Executable 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();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
5
src/app/panels/timepicker2/refreshctrl.html
Executable file
5
src/app/panels/timepicker2/refreshctrl.html
Executable 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>
|
@ -87,7 +87,8 @@ function (angular, app, _, kbn) {
|
|||||||
timeField = timeField[0];
|
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 = {
|
$scope.old_time = {
|
||||||
from : new Date($scope.time.from.getTime() - kbn.interval_to_ms($scope.panel.ago)),
|
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))
|
to : new Date($scope.time.to.getTime() - kbn.interval_to_ms($scope.panel.ago))
|
||||||
|
@ -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>
|
<li><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/dashboard'><i class='icon-home'></i></a></li>
|
||||||
|
|
||||||
|
@ -1,5 +1,18 @@
|
|||||||
<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>
|
||||||
|
|
||||||
|
<div class="container-fluid main">
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="row-fluid container" style="margin-top:10px; width:98%">
|
||||||
<!-- Rows -->
|
<!-- 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-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-control">
|
||||||
@ -46,6 +59,7 @@
|
|||||||
<i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}}
|
<i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content Panel -->
|
<!-- Content Panel -->
|
||||||
<div class="row-fluid" style="position:relative" ng-class="{'dragInProgress':dashboard.panelDragging}" >
|
<div class="row-fluid" style="position:relative" ng-class="{'dragInProgress':dashboard.panelDragging}" >
|
||||||
<kibana-panel type="panel.type" ng-cloak></kibana-panel>
|
<kibana-panel type="panel.type" ng-cloak></kibana-panel>
|
||||||
@ -74,4 +88,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
@ -25,6 +25,14 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
|||||||
failover: false,
|
failover: false,
|
||||||
panel_hints: true,
|
panel_hints: true,
|
||||||
rows: [],
|
rows: [],
|
||||||
|
pulldowns: [
|
||||||
|
{
|
||||||
|
type: 'query',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'filtering'
|
||||||
|
}
|
||||||
|
],
|
||||||
services: {},
|
services: {},
|
||||||
loader: {
|
loader: {
|
||||||
save_gist: false,
|
save_gist: false,
|
||||||
@ -109,7 +117,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
|||||||
this.refresh = function() {
|
this.refresh = function() {
|
||||||
if(self.current.index.interval !== 'none') {
|
if(self.current.index.interval !== 'none') {
|
||||||
if(filterSrv.idsByType('time').length > 0) {
|
if(filterSrv.idsByType('time').length > 0) {
|
||||||
var _range = filterSrv.timeRange('min');
|
var _range = filterSrv.timeRange('last');
|
||||||
kbnIndex.indices(_range.from,_range.to,
|
kbnIndex.indices(_range.from,_range.to,
|
||||||
self.current.index.pattern,self.current.index.interval
|
self.current.index.pattern,self.current.index.interval
|
||||||
).then(function (p) {
|
).then(function (p) {
|
||||||
@ -175,7 +183,7 @@ function (angular, $, kbn, _, config, moment, Modernizr) {
|
|||||||
filterSrv.init();
|
filterSrv.init();
|
||||||
|
|
||||||
// If there's an index interval set and no existing time filter, send a refresh to set one
|
// 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();
|
self.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
define([
|
define([
|
||||||
'angular',
|
'angular',
|
||||||
'underscore',
|
'underscore',
|
||||||
'config'
|
'config',
|
||||||
], function (angular, _, config) {
|
'kbn'
|
||||||
|
], function (angular, _, config, kbn) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var module = angular.module('kibana.services');
|
var module = angular.module('kibana.services');
|
||||||
@ -35,10 +36,13 @@ define([
|
|||||||
self.ids = dashboard.current.services.filter.ids;
|
self.ids = dashboard.current.services.filter.ids;
|
||||||
_f = dashboard.current.services.filter;
|
_f = dashboard.current.services.filter;
|
||||||
|
|
||||||
|
// Date filters hold strings now, not dates
|
||||||
|
/*
|
||||||
_.each(self.getByType('time',true),function(time) {
|
_.each(self.getByType('time',true),function(time) {
|
||||||
self.list[time.id].from = new Date(time.from);
|
self.list[time.id].from = new Date(time.from);
|
||||||
self.list[time.id].to = new Date(time.to);
|
self.list[time.id].to = new Date(time.to);
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -78,6 +82,7 @@ define([
|
|||||||
dashboard.refresh();
|
dashboard.refresh();
|
||||||
},0);
|
},0);
|
||||||
}
|
}
|
||||||
|
$rootScope.$broadcast('filter');
|
||||||
return _r;
|
return _r;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,6 +106,7 @@ define([
|
|||||||
dashboard.refresh();
|
dashboard.refresh();
|
||||||
},0);
|
},0);
|
||||||
}
|
}
|
||||||
|
$rootScope.$broadcast('filter');
|
||||||
return _r;
|
return _r;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -150,10 +156,11 @@ define([
|
|||||||
switch(filter.type)
|
switch(filter.type)
|
||||||
{
|
{
|
||||||
case 'time':
|
case 'time':
|
||||||
return ejs.RangeFilter(filter.field)
|
var _f = ejs.RangeFilter(filter.field).from(kbn.parseDate(filter.from).valueOf());
|
||||||
.from(filter.from.valueOf())
|
if(!_.isUndefined(filter.to)) {
|
||||||
//.from("now-1d")
|
_f = _f.to(filter.to.valueOf());
|
||||||
.to(filter.to.valueOf());
|
}
|
||||||
|
return _f;
|
||||||
case 'range':
|
case 'range':
|
||||||
return ejs.RangeFilter(filter.field)
|
return ejs.RangeFilter(filter.field)
|
||||||
.from(filter.from)
|
.from(filter.from)
|
||||||
@ -187,26 +194,26 @@ define([
|
|||||||
return _.pluck(self.getByType('time'),'field');
|
return _.pluck(self.getByType('time'),'field');
|
||||||
};
|
};
|
||||||
|
|
||||||
// This special function looks for all time filters, and returns a time range according to the mode
|
// Parse is used when you need to know about the raw filter
|
||||||
// No idea when max would actually be used
|
this.timeRange = function(parse) {
|
||||||
this.timeRange = function(mode) {
|
var _t = _.last(_.where(self.list,{type:'time',active:true}));
|
||||||
var _t = _.where(self.list,{type:'time',active:true});
|
if(_.isUndefined(_t)) {
|
||||||
if(_t.length === 0) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
switch(mode) {
|
if(parse === false) {
|
||||||
case "min":
|
|
||||||
return {
|
return {
|
||||||
from: new Date(_.max(_.pluck(_t,'from'))),
|
from: _t.from,
|
||||||
to: new Date(_.min(_.pluck(_t,'to')))
|
to: _t.to
|
||||||
};
|
};
|
||||||
case "max":
|
} else {
|
||||||
|
var
|
||||||
|
_from = _t.from,
|
||||||
|
_to = _t.to || new Date();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
from: new Date(_.min(_.pluck(_t,'from'))),
|
from : kbn.parseDate(_from),
|
||||||
to: new Date(_.max(_.pluck(_t,'to')))
|
to : kbn.parseDate(_to)
|
||||||
};
|
};
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
2
src/css/bootstrap.dark.min.css
vendored
2
src/css/bootstrap.dark.min.css
vendored
File diff suppressed because one or more lines are too long
2
src/css/bootstrap.light.min.css
vendored
2
src/css/bootstrap.light.min.css
vendored
File diff suppressed because one or more lines are too long
@ -34,16 +34,14 @@
|
|||||||
<div class="navbar-inner">
|
<div class="navbar-inner">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<span class="brand">{{dashboard.current.title}}</span>
|
<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 class="nav pull-right" ng-controller='dashLoader' ng-init="init()" ng-include="'app/partials/dashLoader.html'">
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</html>
|
||||||
|
34
src/vendor/bootstrap/less/overrides.less
vendored
34
src/vendor/bootstrap/less/overrides.less
vendored
@ -16,6 +16,10 @@ div.fake-input {
|
|||||||
.border-radius(@inputBorderRadius @inputBorderRadius @inputBorderRadius @inputBorderRadius);
|
.border-radius(@inputBorderRadius @inputBorderRadius @inputBorderRadius @inputBorderRadius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form input.ng-invalid {
|
||||||
|
color: @errorText;
|
||||||
|
}
|
||||||
|
|
||||||
.editor-title {
|
.editor-title {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
font-size: 1.7em;
|
font-size: 1.7em;
|
||||||
@ -80,6 +84,22 @@ div.fake-input {
|
|||||||
font-weight: 200;
|
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 {
|
.row-open i {
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
}
|
}
|
||||||
@ -146,9 +166,15 @@ div.fake-input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
|
color: @linkColor;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
color: @linkColorHover;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.pointer:hover {
|
.pointer:hover {
|
||||||
color: @linkColorHover;
|
color: @linkColorHover;
|
||||||
}
|
}
|
||||||
@ -157,10 +183,6 @@ div.fake-input {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link:hover {
|
|
||||||
color: @linkColorHover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover {
|
.popover {
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
}
|
}
|
||||||
@ -191,6 +213,10 @@ div.fake-input {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.normal {
|
.normal {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user