diff --git a/common/css/datepicker.css b/common/css/datepicker.css deleted file mode 100644 index 15268d94bb7..00000000000 --- a/common/css/datepicker.css +++ /dev/null @@ -1,156 +0,0 @@ -/* ========================================================= - * bootstrap-datepicker.js - * original by Stefan Petre - * tweaked by gus - * ========================================================= */ - -.bs-sc-datepicker.dropdown-menu { -max-width: inherit; -} -.bs-sc-datepicker { -top: 0; -left: 0; -padding: 4px; -margin-top: 1px; --webkit-border-radius: 4px; --moz-border-radius: 4px; -border-radius: 4px; -z-index: 1051; -} -.bs-sc-datepicker:before { -content: ''; -display: inline-block; -border-left: 7px solid transparent; -border-right: 7px solid transparent; -border-bottom: 7px solid #ccc; -border-bottom-color: rgba(0, 0, 0, 0.2); -position: absolute; -top: -7px; -left: 6px; -} -.bs-sc-datepicker:after { -content: ''; -display: inline-block; -border-left: 6px solid transparent; -border-right: 6px solid transparent; -border-bottom: 6px solid #ffffff; -position: absolute; -top: -6px; -left: 7px; -} -.bs-sc-datepicker table { -width: 100%; -margin: 0; -} -.bs-sc-datepicker th { -border-bottom: 1px solid #efefef; -} -.bs-sc-datepicker td, .bs-sc-datepicker th { -text-align: center; -width: 20px; -height: 20px; --webkit-border-radius: 4px; --moz-border-radius: 4px; -border-radius: 4px; -padding: 5px !important; -} -.bs-sc-datepicker td.day:hover { -background: #eeeeee; -cursor: pointer; -} -.bs-sc-datepicker td.old, .bs-sc-datepicker td.new { -color: #DDDDDD; -} -.bs-sc-datepicker td.active, .bs-sc-datepicker td.active:hover { -background-color: #006dcc; -background-image: -moz-linear-gradient(top, #0088cc, #0044cc); -background-image: -ms-linear-gradient(top, #0088cc, #0044cc); -background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); -background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); -background-image: -o-linear-gradient(top, #0088cc, #0044cc); -background-image: linear-gradient(top, #0088cc, #0044cc); -background-repeat: repeat-x; -filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); -border-color: #0044cc #0044cc #002a80; -border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); -filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); -color: #fff; -text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} -.bs-sc-datepicker td.active:hover, -.bs-sc-datepicker td.active:hover:hover, -.bs-sc-datepicker td.active:active, -.bs-sc-datepicker td.active:hover:active, -.bs-sc-datepicker td.active.active, -.bs-sc-datepicker td.active:hover.active, -.bs-sc-datepicker td.active.disabled, -.bs-sc-datepicker td.active:hover.disabled, -.bs-sc-datepicker td.active[disabled], -.bs-sc-datepicker td.active:hover[disabled] { -background-color: #0044cc; -} -.bs-sc-datepicker td.active:active, -.bs-sc-datepicker td.active:hover:active, -.bs-sc-datepicker td.active.active, -.bs-sc-datepicker td.active:hover.active { -background-color: #003399 \9; -} -.bs-sc-datepicker td span { -display: block; -width: 47px; -height: 54px; -line-height: 54px; -float: left; -margin: 2px; -cursor: pointer; --webkit-border-radius: 4px; --moz-border-radius: 4px; -border-radius: 4px; -} -.bs-sc-datepicker td span:hover { -background: #eeeeee; -} -.bs-sc-datepicker td span.active { -background-color: #006dcc; -background-image: -moz-linear-gradient(top, #0088cc, #0044cc); -background-image: -ms-linear-gradient(top, #0088cc, #0044cc); -background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); -background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); -background-image: -o-linear-gradient(top, #0088cc, #0044cc); -background-image: linear-gradient(top, #0088cc, #0044cc); -background-repeat: repeat-x; -filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); -border-color: #0044cc #0044cc #002a80; -border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); -filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); -color: #fff; -text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} -.bs-sc-datepicker td span.active:hover, -.bs-sc-datepicker td span.active:active, -.bs-sc-datepicker td span.active.active, -.bs-sc-datepicker td span.active.disabled, -.bs-sc-datepicker td span.active[disabled] { -background-color: #0044cc; -} -.bs-sc-datepicker td span.active:active, .bs-sc-datepicker td span.active.active { -background-color: #003399 \9; -} -.bs-sc-datepicker td span.old { -color: #999999; -} -.bs-sc-datepicker th.switch { -width: 145px; -} -.bs-sc-datepicker thead tr:first-child th { -cursor: pointer; -} -.bs-sc-datepicker thead tr:first-child th:hover { -background: #eeeeee; -} -.input-append.date .add-on i, .input-prepend.date .add-on i { -display: block; -cursor: pointer; -width: 16px; -height: 16px; -} \ No newline at end of file diff --git a/common/css/main.css b/common/css/main.css index a2ec63275e0..f3a182ef850 100644 --- a/common/css/main.css +++ b/common/css/main.css @@ -22,4 +22,33 @@ .pointer { cursor: pointer; +} + +.small { + font-size: 85%; +} + +.nomargin { + margin: 0px; +} + +.strong { + font-weight: bold; +} + +.btn-active { + background-color: #E6E6E6; + background-image: none; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15) inset, 0 1px 2px rgba(0, 0, 0, 0.05); + outline: 0 none +} + +.popover-title { display: none; } + +.input-smaller { + width: 75px; +} + +.tiny { + font-size: 50%; } \ No newline at end of file diff --git a/common/css/timepicker.css b/common/css/timepicker.css new file mode 100644 index 00000000000..8166d4da0e9 --- /dev/null +++ b/common/css/timepicker.css @@ -0,0 +1,91 @@ + +/* + Datepicker for Bootstrap + Copyright 2012 Stefan Petre + Licensed under the Apache License v2.0 + http://www.apache.org/licenses/LICENSE-2.0 +*/ +input[type="date"] { -webkit-appearance: none; } .datepicker { top: 0; left: 0; padding: 4px; margin-top: 1px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; /*.dow { border-top: 1px solid #ddd !important; }*/ } .datepicker:before { content: ''; display: inline-block; border-left: 7px solid transparent; border-right: 7px solid transparent; border-bottom: 7px solid #ccc; border-bottom-color: rgba(0, 0, 0, 0.2); position: absolute; top: -7px; left: 6px; } .datepicker:after { content: ''; display: inline-block; border-left: 6px solid transparent; border-right: 6px solid transparent; border-bottom: 6px solid #ffffff; position: absolute; top: -6px; left: 7px; } .datepicker > div { display: none; } .datepicker table { width: 100%; margin: 0; } .datepicker td, .datepicker th { text-align: center; width: 20px; height: 20px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } .datepicker td.day:hover { background: #eeeeee; cursor: pointer; } .datepicker td.old, .datepicker td.new { color: #999999; } .datepicker td.active, .datepicker td.active:hover { background-color: #006dcc; background-image: -moz-linear-gradient(top, #0088cc, #0044cc); background-image: -ms-linear-gradient(top, #0088cc, #0044cc); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); background-image: -o-linear-gradient(top, #0088cc, #0044cc); background-image: linear-gradient(top, #0088cc, #0044cc); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); border-color: #0044cc #0044cc #002a80; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); color: #fff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } .datepicker td.active:hover, .datepicker td.active:hover:hover, .datepicker td.active:active, .datepicker td.active:hover:active, .datepicker td.active.active, .datepicker td.active:hover.active, .datepicker td.active.disabled, .datepicker td.active:hover.disabled, .datepicker td.active[disabled], .datepicker td.active:hover[disabled] { background-color: #0044cc; } .datepicker td.active:active, .datepicker td.active:hover:active, .datepicker td.active.active, .datepicker td.active:hover.active { background-color: #003399 \9; } .datepicker td span { display: block; width: 47px; height: 54px; line-height: 54px; float: left; margin: 2px; cursor: pointer; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } .datepicker td span:hover { background: #eeeeee; } .datepicker td span.active { background-color: #006dcc; background-image: -moz-linear-gradient(top, #0088cc, #0044cc); background-image: -ms-linear-gradient(top, #0088cc, #0044cc); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); background-image: -o-linear-gradient(top, #0088cc, #0044cc); background-image: linear-gradient(top, #0088cc, #0044cc); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0); border-color: #0044cc #0044cc #002a80; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); color: #fff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } .datepicker td span.active:hover, .datepicker td span.active:active, .datepicker td span.active.active, .datepicker td span.active.disabled, .datepicker td span.active[disabled] { background-color: #0044cc; } .datepicker td span.active:active, .datepicker td span.active.active { background-color: #003399 \9; } .datepicker td span.old { color: #999999; } .datepicker th.switch { width: 145px; } .datepicker th.next, .datepicker th.prev { font-size: 19.5px; } .datepicker thead tr:first-child th { cursor: pointer; } .datepicker thead tr:first-child th:hover { background: #eeeeee; } .input-append.date .add-on i, .input-prepend.date .add-on i { display: block; cursor: pointer; width: 16px; height: 16px; } + +.bootstrap-timepicker.dropdown-menu { + border-radius: 4px 4px 4px 4px; + display: none; + left: 0; + margin-top: 1px; + padding: 4px; + top: 0; + min-width: 10px; + z-index: 99999; +} +.bootstrap-timepicker.dropdown-menu.open { + display: inline-block; +} +.bootstrap-timepicker.dropdown-menu:before { + border-bottom: 7px solid rgba(0, 0, 0, 0.2); + border-left: 7px solid transparent; + border-right: 7px solid transparent; + content: ""; + left: 6px; + position: absolute; + top: -7px; +} +.bootstrap-timepicker.dropdown-menu:after { + border-bottom: 6px solid #FFFFFF; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + content: ""; + left: 7px; + position: absolute; + top: -6px; +} +.bootstrap-timepicker.modal { + margin-left: -100px; + margin-top: 0; + top: 30%; + width: 200px; +} +.bootstrap-timepicker.modal .modal-content { + padding: 0; +} +.bootstrap-timepicker table { + margin: 0; + width: 100%; +} +.bootstrap-timepicker table td { + height: 30px; + margin: 0; + padding: 2px; + text-align: center; +} +.bootstrap-timepicker table td span { + width: 100%; +} +.bootstrap-timepicker table td a { + border: 1px solid transparent; + display: inline-block; + margin: 0; + outline: 0 none; + padding: 8px 0; + width: 3em; +} +.bootstrap-timepicker table td a:hover { + background-color: #EEEEEE; + border-color: #DDDDDD; + border-radius: 4px 4px 4px 4px; +} +.bootstrap-timepicker table td a i { + margin-top: 2px; +} +.bootstrap-timepicker table td input { + margin: 0; + text-align: center; + width: 25px; +} +.bootstrap-timepicker-component .add-on { + cursor: pointer; +} +.bootstrap-timepicker-component .add-on i { + display: block; + height: 16px; + width: 16px; +} diff --git a/common/lib/datepicker.js b/common/lib/datepicker.js index 671bbbf9eed..6c97ff522c2 100644 --- a/common/lib/datepicker.js +++ b/common/lib/datepicker.js @@ -1,188 +1,377 @@ /* ========================================================= * bootstrap-datepicker.js - * original by Stefan Petre - * tweaked by gus + * http://www.eyecon.ro/bootstrap-datepicker + * ========================================================= + * Copyright 2012 Stefan Petre + * Improvements by Andrew Rowls + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. * ========================================================= */ !function( $ ) { + function UTCDate(){ + return new Date(Date.UTC.apply(Date, arguments)); + } + function UTCToday(){ + var today = new Date(); + return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate()); + } + // Picker object - var Datepicker = function(element, options){ + var Datepicker = function(element, options) { + var that = this; + this.element = $(element); + this.language = options.language||this.element.data('date-language')||"en"; + this.language = this.language in dates ? this.language : "en"; + this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy'); + this.picker = $(DPGlobal.template) + .appendTo('body') + .on({ + click: $.proxy(this.click, this) + }); + this.isInput = this.element.is('input'); + this.component = this.element.is('.date') ? this.element.find('.add-on') : false; + this.hasInput = this.component && this.element.find('input').length; + if(this.component && this.component.length === 0) + this.component = false; - this.days = options.days||["sun","mon","tue","wed","thu","fri","sat"]; - this.months = options.months||["january","february","march","april","may","june","july","august","september","october","november","december"]; - this.format = options.format||$(element).data("datepicker-format")||'mm/dd/yyyy hh:ii:ss'; - this.noDefault = options.noDefault||$(element).data("datepicker-nodefault")||false; + if (this.isInput) { + this.element.on({ + focus: $.proxy(this.show, this), + keyup: $.proxy(this.update, this), + keydown: $.proxy(this.keydown, this) + }); + } else { + if (this.component && this.hasInput){ + // For components that are not readonly, allow keyboard nav + this.element.find('input').on({ + focus: $.proxy(this.show, this), + keyup: $.proxy(this.update, this), + keydown: $.proxy(this.keydown, this) + }); - this.picker = $(DPGlobal.template).appendTo("body").on({ - mousedown: $.proxy(this.click, this) - }); - - this.weekStart = options.weekStart||0; - this.weekEnd = this.weekStart == 0 ? 6 : this.weekStart - 1; - this.head(); - - if (!this.element.prop("value")&&!this.noDefault) { - this.element.prop("value",DPGlobal.formatDate(new Date(), this.format)); + this.component.on('click', $.proxy(this.show, this)); + } else { + this.element.on('click', $.proxy(this.show, this)); + } } - this.update(); - - this.element.on({ - focus: $.proxy(this.show, this), - click: $.proxy(this.show, this), - keyup: $.proxy(this.keyup, this) + $(document).on('mousedown', function (e) { + // Clicked outside the datepicker, hide it + if ($(e.target).closest('.datepicker').length == 0) { + that.hide(); + } }); + + this.autoclose = false; + if ('autoclose' in options) { + this.autoclose = options.autoclose; + } else if ('dateAutoclose' in this.element.data()) { + this.autoclose = this.element.data('date-autoclose'); + } + + this.keyboardNavigation = true; + if ('keyboardNavigation' in options) { + this.keyboardNavigation = options.keyboardNavigation; + } else if ('dateKeyboardNavigation' in this.element.data()) { + this.keyboardNavigation = this.element.data('date-keyboard-navigation'); + } + + switch(options.startView || this.element.data('date-start-view')){ + case 2: + case 'decade': + this.viewMode = this.startViewMode = 2; + break; + case 1: + case 'year': + this.viewMode = this.startViewMode = 1; + break; + case 0: + case 'month': + default: + this.viewMode = this.startViewMode = 0; + break; + } + + this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false); + this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false); + + this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7); + this.weekEnd = ((this.weekStart + 6) % 7); + this.startDate = -Infinity; + this.endDate = Infinity; + this.setStartDate(options.startDate||this.element.data('date-startdate')); + this.setEndDate(options.endDate||this.element.data('date-enddate')); + this.fillDow(); + this.fillMonths(); + this.update(); + this.showMode(); }; Datepicker.prototype = { constructor: Datepicker, show: function(e) { - this.update(); this.picker.show(); - this.height = this.element.outerHeight(); + this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); + this.update(); this.place(); - $(window).on("resize", $.proxy(this.place, this)); - if (e) { + $(window).on('resize', $.proxy(this.place, this)); + if (e ) { e.stopPropagation(); e.preventDefault(); } this.element.trigger({ - type: "show", + type: 'show', date: this.date }); - $("body").on("click.bs-sc-datepicker", $.proxy(this.hide, this)); }, hide: function(e){ - if (e && $(e.target).parents(".bs-sc-datepicker").length) return false; this.picker.hide(); - $(window).off("resize", this.place); - $("body").off("click.bs-sc-datepicker"); + $(window).off('resize', this.place); + this.viewMode = this.startViewMode; + this.showMode(); + if (!this.isInput) { + $(document).off('mousedown', this.hide); + } + if (e && e.currentTarget.value) + this.setValue(); + this.element.trigger({ + type: 'hide', + date: this.date + }); }, - setValue: function(val) { - if (typeof(val)!=='undefined') { - this.date = val; + getDate: function() { + var d = this.getUTCDate(); + return new Date(d.getTime() + (d.getTimezoneOffset()*60000)) + }, + + getUTCDate: function() { + return this.date; + }, + + setDate: function(d) { + this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000))); + }, + + setUTCDate: function(d) { + this.date = d; + this.setValue(); + }, + + setValue: function() { + var formatted = DPGlobal.formatDate(this.date, this.format, this.language); + if (!this.isInput) { + if (this.component){ + this.element.find('input').prop('value', formatted); + } + this.element.data('date', formatted); + } else { + this.element.prop('value', formatted); } - var formated = DPGlobal.formatDate(this.date, this.format); - this.element.prop("value", formated); + }, + + setStartDate: function(startDate){ + this.startDate = startDate||-Infinity; + if (this.startDate !== -Infinity) { + this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language); + } + this.update(); + this.updateNavArrows(); + }, + + setEndDate: function(endDate){ + this.endDate = endDate||Infinity; + if (this.endDate !== Infinity) { + this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language); + } + this.update(); + this.updateNavArrows(); }, place: function(){ - var offset = this.element.offset(); + var zIndex = parseInt(this.element.parents().filter(function() { + return $(this).css('z-index') != 'auto'; + }).first().css('z-index'))+10; + var offset = this.component ? this.component.offset() : this.element.offset(); this.picker.css({ top: offset.top + this.height, - left: offset.left + left: offset.left, + zIndex: zIndex }); }, update: function(){ - this.date = DPGlobal.parseDate(this.element.prop("value"), this.format); - this.viewDate = new Date(this.date); + this.date = DPGlobal.parseDate( + this.isInput ? this.element.prop('value') : this.element.data('date') || this.element.find('input').prop('value'), + this.format, this.language + ); + if (this.date < this.startDate) { + this.viewDate = new Date(this.startDate); + } else if (this.date > this.endDate) { + this.viewDate = new Date(this.endDate); + } else { + this.viewDate = new Date(this.date); + } this.fill(); }, - keyup: function() { - this.date = DPGlobal.parseDate(this.element.prop("value"), this.format); - this.element.trigger({ - type: 'changeDate', - date: this.date - }); - }, - - head: function(){ + fillDow: function(){ var dowCnt = this.weekStart; var html = ''; while (dowCnt < this.weekStart + 7) { - html += ''+this.days[(dowCnt++)%7]+''; + html += ''+dates[this.language].daysMin[(dowCnt++)%7]+''; } html += ''; - this.picker.find(".datepicker-days thead").append(html); + this.picker.find('.datepicker-days thead').append(html); + }, + + fillMonths: function(){ + var html = ''; + var i = 0 + while (i < 12) { + html += ''+dates[this.language].monthsShort[i++]+''; + } + this.picker.find('.datepicker-months td').html(html); }, fill: function() { var d = new Date(this.viewDate), - year = d.getFullYear(), - month = d.getMonth(), - day = d.getDay(); - - currentDate = new Date(this.date.getFullYear(), this.date.getMonth(), this.date.getDate(), 0, 0, 0, 0); - currentDate = currentDate.valueOf(); - - if (month > 0) { - var prevMonth = new Date(year, month-1, 1,0,0,0,0); - var prevMonthNr = prevMonth.getMonth(); - } else { - var prevMonth = new Date(year-1, 11, 1, 0, 0, 0, 0); - var prevMonthNr = prevMonth.getMonth(); - } - - if (month < 11) { - var nextMonthNr = month + 1; - } else { - var nextMonthNr = 0; - } - - var beginMonth = new Date(year, month, 1,0,0,0,0); - startAtWeekday = beginMonth.getDay() - this.weekStart; - if (startAtWeekday < 0) { - prevMonthDays = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth()); - startPrevMonthAtDate = prevMonthDays - (6 + startAtWeekday); - } else if (startAtWeekday > 0) { - prevMonthDays = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth()); - startPrevMonthAtDate = prevMonthDays - startAtWeekday + 1; - } else { - startPrevMonthAtDate = 1; - prevMonth.setMonth(month); - } - - prevMonth.setDate(startPrevMonthAtDate); - - d = prevMonth; - - html = []; allDone = false; x=0; - - while(!allDone) { - - if (d.getDay() == this.weekStart) { - + year = d.getUTCFullYear(), + month = d.getUTCMonth(), + startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity, + startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity, + endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity, + endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity, + currentDate = this.date.valueOf(), + today = new Date(); + this.picker.find('.datepicker-days thead th:eq(1)') + .text(dates[this.language].months[month]+' '+year); + this.picker.find('tfoot th.today') + .text(dates[this.language].today) + .toggle(this.todayBtn); + this.updateNavArrows(); + this.fillMonths(); + var prevMonth = UTCDate(year, month-1, 28,0,0,0,0), + day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); + prevMonth.setUTCDate(day); + prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7); + var nextMonth = new Date(prevMonth); + nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); + nextMonth = nextMonth.valueOf(); + var html = []; + var clsName; + while(prevMonth.valueOf() < nextMonth) { + if (prevMonth.getUTCDay() == this.weekStart) { html.push(''); } - clsName = ''; - if (d.getMonth() == prevMonthNr) { + if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) { clsName += ' old'; - } else if (d.getMonth() == nextMonthNr) { + } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) { clsName += ' new'; } - if (d.valueOf() == currentDate) { + // Compare internal UTC date with local today, not UTC today + if (this.todayHighlight && + prevMonth.getUTCFullYear() == today.getFullYear() && + prevMonth.getUTCMonth() == today.getMonth() && + prevMonth.getUTCDate() == today.getDate()) { + clsName += ' today'; + } + if (prevMonth.valueOf() == currentDate) { clsName += ' active'; } - clsName += ' ' + d.valueOf(); - html.push('' + d.getDate() + ''); - - if (d.getDay() == this.weekEnd) { + if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate) { + clsName += ' disabled'; + } + html.push(''+prevMonth.getUTCDate() + ''); + if (prevMonth.getUTCDay() == this.weekEnd) { html.push(''); } + prevMonth.setUTCDate(prevMonth.getUTCDate()+1); + } + this.picker.find('.datepicker-days tbody').empty().append(html.join('')); + var currentYear = this.date.getUTCFullYear(); - d.setDate(d.getDate()+1); - allDone = ((d.getDay() == this.weekStart) && (d.getMonth() == nextMonthNr)); - - x++; - if (x > 99) { - console.log("safety"); - return; - } + var months = this.picker.find('.datepicker-months') + .find('th:eq(1)') + .text(year) + .end() + .find('span').removeClass('active'); + if (currentYear == year) { + months.eq(this.date.getUTCMonth()).addClass('active'); + } + if (year < startYear || year > endYear) { + months.addClass('disabled'); + } + if (year == startYear) { + months.slice(0, startMonth).addClass('disabled'); + } + if (year == endYear) { + months.slice(endMonth+1).addClass('disabled'); } - this.picker.find('.datepicker-days tbody').empty().append(html.join('')); + html = ''; + year = parseInt(year/10, 10) * 10; + var yearCont = this.picker.find('.datepicker-years') + .find('th:eq(1)') + .text(year + '-' + (year + 9)) + .end() + .find('td'); + year -= 1; + for (var i = -1; i < 11; i++) { + html += ''+year+''; + year += 1; + } + yearCont.html(html); + }, - headerStr = this.months[this.viewDate.getMonth()] + ' ' + this.viewDate.getFullYear(); - this.picker.find('.datepicker-days thead .monthname').html(headerStr); + updateNavArrows: function() { + var d = new Date(this.viewDate), + year = d.getUTCFullYear(), + month = d.getUTCMonth(); + switch (this.viewMode) { + case 0: + if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) { + this.picker.find('.prev').css({visibility: 'hidden'}); + } else { + this.picker.find('.prev').css({visibility: 'visible'}); + } + if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) { + this.picker.find('.next').css({visibility: 'hidden'}); + } else { + this.picker.find('.next').css({visibility: 'visible'}); + } + break; + case 1: + case 2: + if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) { + this.picker.find('.prev').css({visibility: 'hidden'}); + } else { + this.picker.find('.prev').css({visibility: 'visible'}); + } + if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) { + this.picker.find('.next').css({visibility: 'hidden'}); + } else { + this.picker.find('.next').css({visibility: 'visible'}); + } + break; + } }, click: function(e) { @@ -190,62 +379,262 @@ e.preventDefault(); var target = $(e.target).closest('span, td, th'); if (target.length == 1) { - switch(target[0].nodeName.toLowerCase()) { - case 'th': switch(target[0].className) { - case 'prev': - if (this.viewDate.getMonth() > 0) { - this.viewDate.setMonth(this.viewDate.getMonth() - 1); - } else { - this.viewDate.setFullYear(this.viewDate.getFullYear() - 1); - this.viewDate.setMonth(11); - } + case 'switch': + this.showMode(1); break; - + case 'prev': case 'next': - if (this.viewDate.getMonth() < 11) { - this.viewDate.setMonth(this.viewDate.getMonth() + 1); - } else { - this.viewDate.setFullYear(this.viewDate.getFullYear() + 1); - this.viewDate.setMonth(0); + var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1); + switch(this.viewMode){ + case 0: + this.viewDate = this.moveMonth(this.viewDate, dir); + break; + case 1: + case 2: + this.viewDate = this.moveYear(this.viewDate, dir); + break; } + this.fill(); + break; + case 'today': + var date = new Date(); + date.setUTCHours(0); + date.setUTCMinutes(0); + date.setUTCSeconds(0); + date.setUTCMilliseconds(0); + this.showMode(-2); + var which = this.todayBtn == 'linked' ? null : 'view'; + this._setDate(date, which); break; } - this.fill(); + break; + case 'span': + if (!target.is('.disabled')) { + this.viewDate.setUTCDate(1); + if (target.is('.month')) { + var month = target.parent().find('span').index(target); + this.viewDate.setUTCMonth(month); + this.element.trigger({ + type: 'changeMonth', + date: this.viewDate + }); + } else { + var year = parseInt(target.text(), 10)||0; + this.viewDate.setUTCFullYear(year); + this.element.trigger({ + type: 'changeYear', + date: this.viewDate + }); + } + this.showMode(-1); + this.fill(); + } break; case 'td': - if (target.is('.day')){ - if (target.is('.old')) { - return; - } else if (target.is('.new')) { - return; - } - + if (target.is('.day') && !target.is('.disabled')){ var day = parseInt(target.text(), 10)||1; - var month = this.viewDate.getMonth(); - var year = this.viewDate.getFullYear(); - this.date = new Date(year, month, day,this.date.getHours(),this.date.getMinutes(),this.date.getSeconds(),0); - this.viewDate = new Date(year, month, day,0,0,0,0); - this.fill(); - this.setValue(); - this.element.trigger({ - type: 'changeDate', - date: this.date - }); - this.hide(); + var year = this.viewDate.getUTCFullYear(), + month = this.viewDate.getUTCMonth(); + if (target.is('.old')) { + if (month == 0) { + month = 11; + year -= 1; + } else { + month -= 1; + } + } else if (target.is('.new')) { + if (month == 11) { + month = 0; + year += 1; + } else { + month += 1; + } + } + this._setDate(UTCDate(year, month, day,0,0,0,0)); } break; } } - return false; }, + _setDate: function(date, which){ + if (!which || which == 'date') + this.date = date; + if (!which || which == 'view') + this.viewDate = date; + this.fill(); + this.setValue(); + this.element.trigger({ + type: 'changeDate', + date: this.date + }); + var element; + if (this.isInput) { + element = this.element; + } else if (this.component){ + element = this.element.find('input'); + } + if (element) { + element.change(); + if (this.autoclose) { + this.hide(); + } + } + }, + + moveMonth: function(date, dir){ + if (!dir) return date; + var new_date = new Date(date.valueOf()), + day = new_date.getUTCDate(), + month = new_date.getUTCMonth(), + mag = Math.abs(dir), + new_month, test; + dir = dir > 0 ? 1 : -1; + if (mag == 1){ + test = dir == -1 + // If going back one month, make sure month is not current month + // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) + ? function(){ return new_date.getUTCMonth() == month; } + // If going forward one month, make sure month is as expected + // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) + : function(){ return new_date.getUTCMonth() != new_month; }; + new_month = month + dir; + new_date.setUTCMonth(new_month); + // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 + if (new_month < 0 || new_month > 11) + new_month = (new_month + 12) % 12; + } else { + // For magnitudes >1, move one month at a time... + for (var i=0; i= this.startDate && date <= this.endDate; + }, + + keydown: function(e){ + if (this.picker.is(':not(:visible)')){ + if (e.keyCode == 27) // allow escape to hide and re-show picker + this.show(); + return; + } + var dateChanged = false, + dir, day, month, + newDate, newViewDate; + switch(e.keyCode){ + case 27: // escape + this.hide(); + e.preventDefault(); + break; + case 37: // left + case 39: // right + if (!this.keyboardNavigation) break; + dir = e.keyCode == 37 ? -1 : 1; + if (e.ctrlKey){ + newDate = this.moveYear(this.date, dir); + newViewDate = this.moveYear(this.viewDate, dir); + } else if (e.shiftKey){ + newDate = this.moveMonth(this.date, dir); + newViewDate = this.moveMonth(this.viewDate, dir); + } else { + newDate = new Date(this.date); + newDate.setUTCDate(this.date.getUTCDate() + dir); + newViewDate = new Date(this.viewDate); + newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir); + } + if (this.dateWithinRange(newDate)){ + this.date = newDate; + this.viewDate = newViewDate; + this.setValue(); + this.update(); + e.preventDefault(); + dateChanged = true; + } + break; + case 38: // up + case 40: // down + if (!this.keyboardNavigation) break; + dir = e.keyCode == 38 ? -1 : 1; + if (e.ctrlKey){ + newDate = this.moveYear(this.date, dir); + newViewDate = this.moveYear(this.viewDate, dir); + } else if (e.shiftKey){ + newDate = this.moveMonth(this.date, dir); + newViewDate = this.moveMonth(this.viewDate, dir); + } else { + newDate = new Date(this.date); + newDate.setUTCDate(this.date.getUTCDate() + dir * 7); + newViewDate = new Date(this.viewDate); + newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7); + } + if (this.dateWithinRange(newDate)){ + this.date = newDate; + this.viewDate = newViewDate; + this.setValue(); + this.update(); + e.preventDefault(); + dateChanged = true; + } + break; + case 13: // enter + this.hide(); + e.preventDefault(); + break; + case 9: // tab + this.hide(); + break; + } + if (dateChanged){ + this.element.trigger({ + type: 'changeDate', + date: this.date + }); + var element; + if (this.isInput) { + element = this.element; + } else if (this.component){ + element = this.element.find('input'); + } + if (element) { + element.change(); + } + } + }, + + showMode: function(dir) { + if (dir) { + this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir)); + } + this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); + this.updateNavArrows(); + } }; $.fn.datepicker = function ( option ) { + var args = Array.apply(null, arguments); + args.shift(); return this.each(function () { var $this = $(this), data = $this.data('datepicker'), @@ -253,117 +642,195 @@ if (!data) { $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options)))); } - if (typeof option == 'string') data[option](); + if (typeof option == 'string' && typeof data[option] == 'function') { + data[option].apply(data, args); + } }); }; $.fn.datepicker.defaults = { }; $.fn.datepicker.Constructor = Datepicker; + var dates = $.fn.datepicker.dates = { + en: { + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], + daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], + daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], + months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + today: "Today" + } + } var DPGlobal = { + modes: [ + { + clsName: 'days', + navFnc: 'Month', + navStep: 1 + }, + { + clsName: 'months', + navFnc: 'FullYear', + navStep: 1 + }, + { + clsName: 'years', + navFnc: 'FullYear', + navStep: 10 + }], isLeapYear: function (year) { return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) }, getDaysInMonth: function (year, month) { return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month] }, - parseDate: function(dateStr, format) { //convert str into date - dateStr = dateStr.replace(/:/g, '/'); - dateStr = dateStr.replace(/ /g, '/'); - strParts = dateStr.split('/'); - - format = format.replace(/:/g, '/'); - format = format.replace(/ /g, '/'); - formatParts = format.split('/'); - - date = new Date(), - date.setHours(0); date.setMinutes(0); date.setSeconds(0); date.setMilliseconds(0); - - for (var key in formatParts) { - if (typeof strParts[key] != 'undefined') { - val = strParts[key]; - switch(formatParts[key]) { - case 'dd': + validParts: /dd?|mm?|MM?|yy(?:yy)?/g, + nonpunctuation: /[^ -\/:-@\[-`{-~\t\n\r]+/g, + parseFormat: function(format){ + // IE treats \0 as a string end in inputs (truncating the value), + // so it's a bad format delimiter, anyway + var separators = format.replace(this.validParts, '\0').split('\0'), + parts = format.match(this.validParts); + if (!separators || !separators.length || !parts || parts.length == 0){ + throw new Error("Invalid date format."); + } + return {separators: separators, parts: parts}; + }, + parseDate: function(date, format, language) { + if (date instanceof Date) return date; + if (/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(date)) { + var part_re = /([-+]\d+)([dmwy])/, + parts = date.match(/([-+]\d+)([dmwy])/g), + part, dir; + date = new Date(); + for (var i=0; i'+ ''+ - ''+ - ''+ - ''+ + ''+ + ''+ + ''+ ''+ '', - contTemplate: '' + contTemplate: '', + footTemplate: '' }; - DPGlobal.template = ' diff --git a/js/app.js b/js/app.js index 48171eb9fbc..90bf28a8ac6 100644 --- a/js/app.js +++ b/js/app.js @@ -9,6 +9,7 @@ var modules = [ 'kibana.services', 'kibana.directives', 'elasticjs.service', + '$strap.directives', 'kibana.panels', ] @@ -17,7 +18,10 @@ var scripts = [] var labjs = $LAB .script("common/lib/jquery-1.8.0.min.js").wait() .script("common/lib/modernizr-2.6.1.min.js") - .script("common/lib/underscore.min.js") + .script("common/lib/underscore.min.js") + .script("common/lib/bootstrap.min.js") + .script('common/lib/datepicker.js') + .script('common/lib/timepicker.js') .script("common/lib/angular.min.js") .script("common/lib/angular-strap.min.js") .script("common/lib/elastic.min.js") @@ -26,12 +30,12 @@ var labjs = $LAB .script("common/lib/date.js") .script("common/lib/datepicker.js") .script("common/lib/shared.js") + .script("dashboards.js") .script("js/services.js") .script("js/controllers.js") .script("js/filters.js") .script("js/directives.js") .script("js/panels.js") - .script("dashboards.js"); _.each(config.modules, function(v) { labjs = labjs.script('panels/'+v+'/module.js').wait() @@ -49,5 +53,8 @@ labjs.wait(function(){ redirectTo: '/dashboard' }); }]); - + angular.element(document).ready(function() { + $('body').attr('ng-controller', 'DashCtrl') + angular.bootstrap(document, ['kibana']); + }); }); diff --git a/js/controllers.js b/js/controllers.js index 2605feb1fe4..8c2ac37d45b 100644 --- a/js/controllers.js +++ b/js/controllers.js @@ -9,17 +9,20 @@ angular.module('kibana.controllers', []) $scope.config = config; $scope.dashboards = dashboards $scope.timespan = config.timespan - $scope.from = time_ago($scope.timespan); - $scope.to = new Date(); - - $scope.time_options = ['5m','15m','1h','6h','12h','24h','2d','7d','30d']; + $scope.time = { + from : time_ago($scope.timespan), + to : new Date() + } + // I'm leaving in all this refresh stuff until I figure out how index + // list caching should work. Maybe it should be handled by each time panel? + // That would require dashboard to contain a time panel. Hmm. $scope.counter = 0; $scope.playing = true; $scope.play = function(){ $scope.counter++; - $scope.to = new Date(); - $scope.from = time_ago($scope.timespan); + $scope.time.to = new Date(); + $scope.time.from = time_ago($scope.timespan); $scope.$root.$eval() mytimeout = $timeout($scope.play,config.refresh); } @@ -37,9 +40,9 @@ angular.module('kibana.controllers', []) // If from/to to change, update index list $scope.$watch(function() { - return angular.toJson([$scope.from, $scope.to]) + return angular.toJson([$scope.time.from, $scope.time.to]) }, function(){ - indices($scope.from,$scope.to).then(function (p) { + indices($scope.time.from,$scope.time.to).then(function (p) { $scope.index = p.join(); }); }); @@ -54,7 +57,7 @@ angular.module('kibana.controllers', []) $scope.set_timespan = function(timespan) { $scope.timespan = timespan; - $scope.from = time_ago($scope.timespan); + $scope.time.from = time_ago($scope.timespan); } // returns a promise containing an array of all indices matching the index diff --git a/panels/histogram/module.js b/panels/histogram/module.js index 540b2de6f29..0295eb33d70 100644 --- a/panels/histogram/module.js +++ b/panels/histogram/module.js @@ -1,5 +1,5 @@ angular.module('kibana.histogram', []) -.controller('histogram', function($scope, $location) { +.controller('histogram', function($scope, $rootScope) { // Set and populate defaults var _d = { @@ -14,14 +14,11 @@ angular.module('kibana.histogram', []) ? _d[k] : $scope.panel[k]; }); - if (!(_.isUndefined($scope.panel.group))) { - $scope.$on($scope.panel.group+"-query", function(event, query) { - $scope.panel.query[0].query = query; - $scope.get_data(); - }); - } - $scope.get_data = function() { + // Make sure we have everything for the request to complete + if(_.isUndefined($scope.panel.time)) + return + var request = $scope.ejs.Request().indices($scope.index); // Build the question part of the query @@ -30,8 +27,8 @@ angular.module('kibana.histogram', []) queries.push($scope.ejs.FilteredQuery( ejs.QueryStringQuery(v.query || '*'), ejs.RangeFilter(config.timefield) - .from($scope.from) - .to($scope.to) + .from($scope.panel.time.from) + .to($scope.panel.time.to) .cache(false)) ) }); @@ -53,15 +50,14 @@ angular.module('kibana.histogram', []) results.then(function(results) { $scope.hits = results.hits.total; // Null values at each end of the time range make sure we see entire range - $scope.data = []; _.each(results.facets, function(v, k) { var series = {}; - var data = [[$scope.from.getTime(), null]]; + var data = [[$scope.panel.time.from.getTime(), null]]; _.each(v.entries, function(v, k) { data.push([v['time'],v['count']]) }); - data.push([$scope.to.getTime(), null]) + data.push([$scope.panel.time.to.getTime(), null]) series.data = { label: $scope.panel.query[k].label, data: data, @@ -73,13 +69,22 @@ angular.module('kibana.histogram', []) }); } - $scope.$watch(function() { - return angular.toJson([$scope.from, $scope.to]) - }, function(){ - $scope.panel.interval = secondsToHms( - calculate_interval($scope.from,$scope.to,50,0)/1000), - $scope.get_data(); - }); + if (!(_.isUndefined($scope.panel.group))) { + $scope.$on($scope.panel.group+"-query", function(event, query) { + $scope.panel.query[0].query = query; + $scope.get_data(); + }); + $scope.$on($scope.panel.group+"-time", function(event, time) { + $scope.panel.time = time; + $scope.panel.interval = secondsToHms( + calculate_interval(time.from,time.to,50,0)/1000), + $scope.get_data(); + }); + } + + // Now that we're all setup, request the time from our group + $rootScope.$broadcast($scope.panel.group+"-get_time") + }) .directive('histogram', function() { diff --git a/panels/map/module.js b/panels/map/module.js index 55e9b66fbd4..eee56cfa886 100644 --- a/panels/map/module.js +++ b/panels/map/module.js @@ -1,5 +1,5 @@ angular.module('kibana.map', []) -.controller('map', function($scope, $location) { +.controller('map', function($scope, $rootScope) { // Set and populate defaults var _d = { @@ -14,15 +14,11 @@ angular.module('kibana.map', []) ? _d[k] : $scope.panel[k]; }); - - if (!(_.isUndefined($scope.panel.group))) { - $scope.$on($scope.panel.group+"-query", function(event, query) { - $scope.panel.query = query; - $scope.get_data(); - }); - } - $scope.get_data = function() { + // Make sure we have everything for the request to complete + if(_.isUndefined($scope.panel.time)) + return + var request = $scope.ejs.Request().indices($scope.index); // Then the insert into facet and make the request @@ -35,8 +31,8 @@ angular.module('kibana.map', []) ejs.FilteredQuery( ejs.QueryStringQuery($scope.panel.query || '*'), ejs.RangeFilter(config.timefield) - .from($scope.from) - .to($scope.to) + .from($scope.panel.time.from) + .to($scope.panel.time.to) .cache(false) )))).size(0) .doSearch(); @@ -51,11 +47,19 @@ angular.module('kibana.map', []) }); } - $scope.$watch(function() { - return angular.toJson([$scope.from, $scope.to]) - }, function(){ - $scope.get_data(); - }); + if (!(_.isUndefined($scope.panel.group))) { + $scope.$on($scope.panel.group+"-query", function(event, query) { + $scope.panel.query = query; + $scope.get_data(); + }); + $scope.$on($scope.panel.group+"-time", function(event, time) { + $scope.panel.time = time; + $scope.get_data(); + }); + } + + // Now that we're all setup, request the time from our group + $rootScope.$broadcast($scope.panel.group+"-get_time") }) .directive('map', function() { diff --git a/panels/pie/module.js b/panels/pie/module.js index a054ffcf7ea..672372cc495 100644 --- a/panels/pie/module.js +++ b/panels/pie/module.js @@ -2,7 +2,7 @@ labjs = labjs.script("common/lib/panels/jquery.flot.js") .script("common/lib/panels/jquery.flot.pie.js") angular.module('kibana.pie', []) -.controller('pie', function($scope, $location) { +.controller('pie', function($scope, $rootScope) { // Set and populate defaults var _d = { @@ -26,6 +26,9 @@ angular.module('kibana.pie', []) } $scope.get_data = function() { + if(_.isUndefined($scope.panel.time)) + return + var request = $scope.ejs.Request().indices($scope.index); // If we have an array, use query facet @@ -36,8 +39,8 @@ angular.module('kibana.pie', []) queries.push(ejs.FilteredQuery( ejs.QueryStringQuery(v.query || '*'), ejs.RangeFilter(config.timefield) - .from($scope.from) - .to($scope.to) + .from($scope.panel.time.from) + .to($scope.panel.time.to) .cache(false)) ) }); @@ -74,8 +77,8 @@ angular.module('kibana.pie', []) ejs.FilteredQuery( ejs.QueryStringQuery($scope.panel.query.query || '*'), ejs.RangeFilter(config.timefield) - .from($scope.from) - .to($scope.to) + .from($scope.panel.time.from) + .to($scope.panel.time.to) .cache(false) )))).size(0) .doSearch(); @@ -100,11 +103,19 @@ angular.module('kibana.pie', []) } } - $scope.$watch(function() { - return angular.toJson([$scope.from, $scope.to]) - }, function(){ - $scope.get_data(); - }); + if (!(_.isUndefined($scope.panel.group))) { + $scope.$on($scope.panel.group+"-query", function(event, query) { + $scope.panel.query.query = query; + $scope.get_data(); + }); + $scope.$on($scope.panel.group+"-time", function(event, time) { + $scope.panel.time = time; + $scope.get_data(); + }); + } + + // Now that we're all setup, request the time from our group + $rootScope.$broadcast($scope.panel.group+"-get_time") }) .directive('pie', function() { diff --git a/panels/sort/module.html b/panels/sort/module.html index 3fc93f1a13d..44b9d8667cf 100644 --- a/panels/sort/module.html +++ b/panels/sort/module.html @@ -1,5 +1,6 @@

{{panel.title}}

+ + -
\ No newline at end of file diff --git a/panels/sort/module.js b/panels/sort/module.js index 871728b3375..eb728932239 100644 --- a/panels/sort/module.js +++ b/panels/sort/module.js @@ -3,6 +3,7 @@ angular.module('kibana.sort', []) // Set and populate defaults var _d = { + label : "Sort", query : "*", size : 100, sort : [config.timefield,'desc'], diff --git a/panels/stringquery/module.html b/panels/stringquery/module.html index 3a119b2eac2..d273a1659e2 100644 --- a/panels/stringquery/module.html +++ b/panels/stringquery/module.html @@ -1,7 +1,8 @@

{{panel.title}}

-
\ No newline at end of file diff --git a/panels/stringquery/module.js b/panels/stringquery/module.js index 804d34c01df..690b5bb796f 100644 --- a/panels/stringquery/module.js +++ b/panels/stringquery/module.js @@ -3,6 +3,7 @@ angular.module('kibana.stringquery', []) // Set and populate defaults var _d = { + label : "Search", query : "*", size : 100, sort : [config.timefield,'desc'], diff --git a/panels/table/module.js b/panels/table/module.js index 9fd6a982fcf..09b70ab02d2 100644 --- a/panels/table/module.js +++ b/panels/table/module.js @@ -12,28 +12,23 @@ angular.module('kibana.table', []) ? _d[k] : $scope.panel[k]; }); - // Events which this panel receives and sends - if (!(_.isUndefined($scope.panel.group))) { - // Receives these events - $scope.$on($scope.panel.group+"-query", function(event, query) { - $scope.panel.query = query; - $scope.get_data(); - }); - } - $scope.toggle_sort = function() { $scope.panel.sort[1] = $scope.panel.sort[1] == 'asc' ? 'desc' : 'asc'; } $scope.get_data = function() { + // Make sure we have everything for the request to complete + if(_.isUndefined($scope.panel.time)) + return + var request = $scope.ejs.Request().indices($scope.index); var results = request .query(ejs.FilteredQuery( ejs.QueryStringQuery($scope.panel.query || '*'), ejs.RangeFilter(config.timefield) - .from($scope.from) - .to($scope.to) + .from($scope.panel.time.from) + .to($scope.panel.time.to) .cache(false) ) ) @@ -63,10 +58,22 @@ angular.module('kibana.table', []) }); } - $scope.$watch(function() { - return angular.toJson([$scope.from, $scope.to, $scope.panel.sort]) - }, function(){ - $scope.get_data(); - }); + $scope.$watch(function() { + return angular.toJson($scope.panel.sort) + }, function(){$scope.get_data()}); + + if (!(_.isUndefined($scope.panel.group))) { + $scope.$on($scope.panel.group+"-query", function(event, query) { + $scope.panel.query = query; + $scope.get_data(); + }); + $scope.$on($scope.panel.group+"-time", function(event, time) { + $scope.panel.time = time; + $scope.get_data(); + }); + } + + // Now that we're all setup, request the time from our group + $rootScope.$broadcast($scope.panel.group+"-get_time") }) diff --git a/panels/timepicker/module.html b/panels/timepicker/module.html new file mode 100644 index 00000000000..8d66b2a791f --- /dev/null +++ b/panels/timepicker/module.html @@ -0,0 +1,64 @@ +
+

{{panel.title}}

+
+
+
+
+ + + +
+
+
+
+ + + +
+
+
+
+ + +
+
+
+
+
+
+ + + +
+
+
+
+ + +
+
+
+
+
+
+ + + +
+
+
+
+
+
+ Relative | + Absolute | + Since + | + Auto-refresh + + every {{panel.refresh.interval}}s. + + +
+
+
\ No newline at end of file diff --git a/panels/timepicker/module.js b/panels/timepicker/module.js new file mode 100644 index 00000000000..072e3248600 --- /dev/null +++ b/panels/timepicker/module.js @@ -0,0 +1,168 @@ +angular.module('kibana.timepicker', []) +.controller('timepicker', function($scope, $rootScope, $timeout) { + + // Set and populate defaults + var _d = { + mode : "relative", + time_options : ['5m','15m','1h','6h','12h','24h','2d','7d','30d'], + timespan : '15m', + refresh : { + enable: false, + interval: 3, + min: 3 + }, + time : { + from : $scope.time.from, + to : $scope.time.to + } + } + _.each(_d, function(v, k) { + $scope.panel[k] = _.isUndefined($scope.panel[k]) + ? _d[k] : $scope.panel[k]; + }); + + // Private refresh interval that we can use for view display without causing + // unnecessary refreshes during changes + $scope.refresh_interval = $scope.panel.refresh.interval + + // Init a private time object with Date() objects depending on mode + switch($scope.panel.mode) { + case 'absolute': + $scope.time = { + from : Date.parse($scope.panel.time.from), + to : Date.parse($scope.panel.time.to) + } + break; + case 'since': + $scope.time = { + from : Date.parse($scope.panel.time.from), + to : new Date() + } + break; + case 'relative': + $scope.time = { + from : time_ago($scope.panel.timespan), + to : new Date() + } + break; + } + + // Init the values for the time/date pickers + $scope.timepicker = { + from : { + time : $scope.time.from.format("HH:MM:ss"), + date : $scope.time.from.format("mm/dd/yyyy") + }, + to : { + time : $scope.time.to.format("HH:MM:ss"), + date : $scope.time.to.format("mm/dd/yyyy") + } + } + + // In the case that a panel is not ready to receive a time event, it may + // request one be sent by broadcasting a 'get_time' even to its group + if (!(_.isUndefined($scope.panel.group))) { + // Broadcast time when initializing + $rootScope.$broadcast($scope.panel.group+"-time", $scope.time) + + // And whenever it is requested + $scope.$on($scope.panel.group+"-get_time", function(event) { + $rootScope.$broadcast($scope.panel.group+"-time", $scope.time) + }); + } + + $scope.$watch('panel.refresh.enable', function() {$scope.refresh()}); + $scope.$watch('panel.refresh.interval', function() { + $timeout(function(){ + if(_.isNumber($scope.panel.refresh.interval)) { + if($scope.panel.refresh.interval < $scope.panel.refresh.min) { + $scope.panel.refresh.interval = $scope.panel.refresh.min + $timeout.cancel($scope.panel.refresh.timer) + return; + } + $timeout.cancel($scope.panel.refresh.timer) + $scope.refresh() + } else { + $timeout.cancel($scope.panel.refresh.timer) + } + }); + }); + + + $scope.refresh = function() { + if ($scope.panel.refresh.enable) { + $scope.time_apply(); + $scope.panel.refresh.timer = $timeout( + $scope.refresh, + $scope.panel.refresh.interval*1000 + ); + } else { + $timeout.cancel($scope.panel.refresh.timer) + } + } + + $scope.set_mode = function(mode) { + $scope.panel.mode = mode; + $scope.panel.refresh.enable = mode === 'absolute' ? + false : $scope.panel.refresh.enable + } + + $scope.to_now = function() { + $scope.timepicker.to = { + time : new Date().format("HH:MM:ss"), + date : new Date().format("mm/dd/yyyy") + } + } + + $scope.set_timespan = function(timespan) { + $scope.panel.timespan = timespan; + $scope.timepicker.from = { + time : time_ago(timespan).format("HH:MM:ss"), + date : time_ago(timespan).format("mm/dd/yyyy") + } + $scope.time_apply(); + } + + $scope.time_check = function(){ + var from = $scope.panel.mode === 'relative' ? time_ago($scope.panel.timespan) : + Date.parse($scope.timepicker.from.date + " " + $scope.timepicker.from.time) + var to = $scope.panel.mode !== 'absolute' ? new Date() : + Date.parse($scope.timepicker.to.date + " " + $scope.timepicker.to.time) + + if (from.getTime() >= to.getTime()) + from = new Date(to.getTime() - 1000) + + // Janky 0s timeout to get around $scope queue processing view issue + $timeout(function(){ + $scope.timepicker = { + from : { + time : from.format("HH:MM:ss"), + date : from.format("mm/dd/yyyy") + }, + to : { + time : to.format("HH:MM:ss"), + date : to.format("mm/dd/yyyy") + } + } + }); + } + + $scope.time_apply = function() { + $scope.time_check(); + // Update internal time object + $scope.time = { + from : Date.parse($scope.timepicker.from.date + " " + $scope.timepicker.from.time), + to : Date.parse($scope.timepicker.to.date + " " + $scope.timepicker.to.time) + }; + + // Broadcast time + $rootScope.$broadcast($scope.panel.group+"-time", $scope.time) + + // Update panel's string representation of the time object + $scope.panel.time = { + from : $scope.time.from.format("mm/dd/yyyy HH:MM:ss"), + to : $scope.time.to.format("mm/dd/yyyy HH:MM:ss") + }; + }; + +}) \ No newline at end of file diff --git a/panels/timepicker/refreshctrl.html b/panels/timepicker/refreshctrl.html new file mode 100644 index 00000000000..07d2a8603b6 --- /dev/null +++ b/panels/timepicker/refreshctrl.html @@ -0,0 +1,5 @@ +
+ + + +
\ No newline at end of file