From bf5d312a7433f782b534210bb210ecbf28e93a81 Mon Sep 17 00:00:00 2001 From: Johannes 'fish' Ziemke Date: Thu, 21 Mar 2013 13:55:59 +0100 Subject: [PATCH 1/9] Add flag to read assets from local files. --- web/status.go | 15 +++++++++++---- web/web.go | 7 ++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/web/status.go b/web/status.go index 7f6bf6396..887867c5a 100644 --- a/web/status.go +++ b/web/status.go @@ -40,11 +40,18 @@ func (h *StatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { Status: "TODO: add status information here", TargetPools: h.appState.TargetManager.Pools(), } - templateFile, err := blob.GetFile(blob.TemplateFiles, "status.html") - if err != nil { - log.Fatalf("Could not read template: %s", err) + + var t *template.Template + if *localAssets { + t, _ = template.ParseFiles("web/templates/status.html") + } else { + templateFile, err := blob.GetFile(blob.TemplateFiles, "status.html") + if err != nil { + log.Fatalf("Could not read template: %s", err) + } + + t, _ = template.New("status").Parse(string(templateFile)) } - t, _ := template.New("status").Parse(string(templateFile)) t.Execute(w, status) } diff --git a/web/web.go b/web/web.go index b7a9f4350..6e167e3e6 100644 --- a/web/web.go +++ b/web/web.go @@ -27,6 +27,7 @@ import ( // Commandline flags. var ( listenAddress = flag.String("listenAddress", ":9090", "Address to listen on for web interface.") + localAssets = flag.Bool("localAssets", false, "Read assets/templates from file instead of binary.") ) func StartServing(appState *appstate.ApplicationState) { @@ -36,7 +37,11 @@ func StartServing(appState *appstate.ApplicationState) { http.Handle("/status", &StatusHandler{appState: appState}) http.Handle("/api/", gorest.Handle()) http.Handle("/metrics.json", exporter) - http.Handle("/static/", http.StripPrefix("/static/", new(blob.Handler))) + if *localAssets { + http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static")))) + } else { + http.Handle("/static/", http.StripPrefix("/static/", new(blob.Handler))) + } go http.ListenAndServe(*listenAddress, nil) } From f9b4df42840a041daf30aa84601f9bdb91bf7678 Mon Sep 17 00:00:00 2001 From: Johannes 'fish' Ziemke Date: Thu, 21 Mar 2013 14:26:13 +0100 Subject: [PATCH 2/9] Add a date/time picker to graph UI. - It's using https://github.com/mugifly/jquery-simple-datetimepicker - If input is empty, it returns the most recent data --- web/static/graph.html | 5 + web/static/js/graph.js | 53 +- .../jquery.simple-dtpicker.css | 269 ++++++++ .../jquery.simple-dtpicker.js | 574 ++++++++++++++++++ 4 files changed, 900 insertions(+), 1 deletion(-) create mode 100644 web/static/vendor/jquery-simple-datetimepicker/jquery.simple-dtpicker.css create mode 100644 web/static/vendor/jquery-simple-datetimepicker/jquery.simple-dtpicker.js diff --git a/web/static/graph.html b/web/static/graph.html index 592f5a3c5..37b2b7033 100644 --- a/web/static/graph.html +++ b/web/static/graph.html @@ -5,9 +5,14 @@ Prometheus Expression Browser + + + + + diff --git a/web/static/js/graph.js b/web/static/js/graph.js index aa48b31e1..b9d938741 100644 --- a/web/static/js/graph.js +++ b/web/static/js/graph.js @@ -76,12 +76,21 @@ Prometheus.Graph.prototype.initialize = function() { self.spinner = graphWrapper.find(".spinner"); self.evalStats = graphWrapper.find(".eval_stats"); + self.endDate = graphWrapper.find("input[name=end]"); + self.endDate.appendDtpicker(); + self.endDate.val("") + self.endDate.change(function() { self.submitQuery() }); + self.stacked.change(function() { self.updateGraph(); }); self.queryForm.submit(function() { self.submitQuery(); return false; }); self.spinner.hide(); self.queryForm.find("input[name=inc_range]").click(function() { self.increaseRange(); }); self.queryForm.find("input[name=dec_range]").click(function() { self.decreaseRange(); }); + + self.queryForm.find("input[name=inc_end]").click(function() { self.increaseEnd(); }); + self.queryForm.find("input[name=dec_end]").click(function() { self.decreaseEnd(); }); + self.insertMetric.change(function() { self.expr.val(self.expr.val() + self.insertMetric.val()); }); @@ -179,6 +188,42 @@ Prometheus.Graph.prototype.decreaseRange = function() { } }; +Prometheus.Graph.prototype.getEndDate = function() { + if (!self.endDate || !self.endDate.val()) { + return null; + } + return new Date(self.endDate.val()).getTime() +}; + +Prometheus.Graph.prototype.getOrSetEndDate = function() { + var date = self.getEndDate(); + if (date) { + return date + } + date = new Date() + self.setEndDate(date) + return date +} + +Prometheus.Graph.prototype.setEndDate = function(date) { + var self = this; + date_s = date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate() + ' ' + + date.getHours() + ':' + date.getMinutes() + self.endDate.val("") + self.endDate.appendDtpicker({"current": date_s}) + self.submitQuery(); +}; + +Prometheus.Graph.prototype.increaseEnd = function() { + var self = this; + self.setEndDate(new Date(self.getOrSetEndDate() + self.parseRange(self.rangeInput.val()) * 1000)) +}; + +Prometheus.Graph.prototype.decreaseEnd = function() { + var self = this; + self.setEndDate(new Date(self.getOrSetEndDate() - self.parseRange(self.rangeInput.val()) * 1000)) +}; + Prometheus.Graph.prototype.submitQuery = function() { var self = this; @@ -192,11 +237,17 @@ Prometheus.Graph.prototype.submitQuery = function() { var resolution = self.queryForm.find("input[name=step_input]").val() || Math.max(Math.floor(rangeSeconds / 250), 1); self.queryForm.find("input[name=step]").val(resolution); + var data = self.queryForm.serialize(); + var endDate = self.getEndDate() + + if (endDate) { + data = data + "&end=" + endDate/1000 + } $.ajax({ method: self.queryForm.attr("method"), url: self.queryForm.attr("action"), dataType: "json", - data: self.queryForm.serialize(), + data: data, success: function(json, textStatus) { if (json.Type == "error") { alert(json.Value); diff --git a/web/static/vendor/jquery-simple-datetimepicker/jquery.simple-dtpicker.css b/web/static/vendor/jquery-simple-datetimepicker/jquery.simple-dtpicker.css new file mode 100644 index 000000000..906df4289 --- /dev/null +++ b/web/static/vendor/jquery-simple-datetimepicker/jquery.simple-dtpicker.css @@ -0,0 +1,269 @@ +/** + * Style-sheet for dtpicker + * https://github.com/mugifly/jquery-simple-datetimepicker + */ + +.datepicker { + display: inline-block; + + border: 2px solid #c8c8c8; + + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + box-shadow: 0.5px 0.5px 3px #c8c8c8; + -webkit-box-shadow: 0.5px 0.5px 3px #c8c8c8; + -moz-box-shadow: 0.5px 0.5px 3px #c8c8c8; +} + +/* + * datepicker_header +*/ + +.datepicker > .datepicker_header{ + padding-top: 2px; + padding-bottom: 2px; + padding-left: 5px; + padding-right: 5px; + background-color: #d2d2d2; + color: #3f3f3f; + text-align: center; + font-size: 9pt; + font-weight: bold; +} + +.datepicker > .datepicker_header > a{ + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + cursor: pointer; + color: #559abd; +} + +.datepicker > .datepicker_header > a:hover { + color: #000000; + background-color: #c8c8c8; +} + +.datepicker > .datepicker_header > a:active { + color: #ffffff; + background-color: #808080; +} + +.datepicker > .datepicker_header > span { + margin-left: 20px; + margin-right: 20px; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; +} + +/* + * datepicker_inner_container +*/ + +.datepicker > .datepicker_inner_container { + margin: -2px -2px -2px -2px; + background-color: #d2d2d2; + border: 2px solid #c8c8c8; + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + + box-shadow: 0.5px 0px 5px #c8c8c8; + -webkit-box-shadow: 0.5px 0px 5px #c8c8c8; + -moz-box-shadow: 0.5px 0px 5px #c8c8c8; +} + +.datepicker > .datepicker_inner_container:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; +} + +/* + * datepicker_inner_container > datepicker_calendar +*/ + +.datepicker > .datepicker_inner_container > .datepicker_calendar { + float: left; + width: auto; + + margin-top: -0.5px; + margin-left: -1px; + margin-bottom: -2px; + + background-color: #ffffff; + border: 1px solid #c8c8c8; + + border-top:none; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + -webkit-border-top-left-radius: 5px; + -webkit-border-bottom-left-radius: 5px; + -moz-border-radius-topleft: 5px; + -moz-border-radius-bottomleft: 5px; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > table { + padding: 10px; +} + +/* + * datepicker_inner_container > datepicker_calendar > datepicker_table > tbody > tr > th (WDay-cell) +*/ + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > th { + color: #646464; + width: 18px; + font-size: small; + font-weight: normal; + text-align:center; +} + +/* + * datepicker_inner_container > datepicker_calendar > datepicker_table > tbody > tr > td (Day-cell) +*/ + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td { + color: #000000; + font-size: small; + text-align:center; + + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + cursor: pointer; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.today { + border-bottom: #bfbfbf solid 2px; + margin-bottom: -2px; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.wday_sat { + color: #0044aa; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.wday_sun { + color: #e13b00; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.day_another_month { + color: #cccccc; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.active { + color: #ffffff; + background-color: #808080; +} + +.datepicker > .datepicker_inner_container > .datepicker_calendar > .datepicker_table > tbody > tr > td.hover { + color: #000000; + background-color: #c8c8c8; +} + +/* + * datepicker_inner_container > datepicker_timelist +*/ + +.datepicker > .datepicker_inner_container > .datepicker_timelist { + float: left; + width: 4.2em; + height: 118px; + + margin-top: -0.5px; + padding: 5px; + padding-left: 0px; + padding-right: 0px; + + overflow: auto; + overflow-x: hidden; + + background-color: #ffffff; + + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + -webkit-border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -moz-border-radius-topright: 4px; + -moz-border-radius-bottomright: 4px; +} + +/* +.datepicker > .datepicker_inner_container > .datepicker_timelist::after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; +} +*/ + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar { + overflow: hidden; + width: 6px; + background: #fafafa; + + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + -webkit-border-top-right-radius: 5px; + -webkit-border-bottom-right-radius: 5px; + -moz-border-radius-topright: 5px; + -moz-border-radius-bottomright: 5px; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar:horizontal { + height: 1px; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-button { + display: none; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-piece { + background: #eee; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-piece:start { + background: #eee; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-thumb { + background: #aaaaaa; + border-radius: 3px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist::-webkit-scrollbar-corner { + background: #333; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item { + padding-top: 1px; + padding-bottom:1px; + padding-left: 7px; + padding-right: 25px; + margin-top: 5px; + margin-bottom: 2px; + font-size: small; + + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + cursor: pointer; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item.active { + color: #ffffff; + background-color: #808080; +} + +.datepicker > .datepicker_inner_container > .datepicker_timelist > div.timelist_item.hover { + color: #000000; + background-color: #c8c8c8; +} + diff --git a/web/static/vendor/jquery-simple-datetimepicker/jquery.simple-dtpicker.js b/web/static/vendor/jquery-simple-datetimepicker/jquery.simple-dtpicker.js new file mode 100644 index 000000000..cee249d03 --- /dev/null +++ b/web/static/vendor/jquery-simple-datetimepicker/jquery.simple-dtpicker.js @@ -0,0 +1,574 @@ +/** + * dtpicker (jquery-simple-datetimepicker) + * (c) Masanori Ohgita - 2013. + * https://github.com/mugifly/jquery-simple-datetimepicker + */ + + (function($) { + var DAYS_OF_WEEK_EN = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']; + var DAYS_OF_WEEK_JA = ['日', '月', '火', '水', '木', '金', '土']; + var MONTHS_EN = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]; + + var PickerObjects = []; + var InputObjects = []; + var ActivePickerId = -1; + + var getParentPickerObject = function(obj) { + var $obj = $(obj); + var $picker; + if ($obj.hasClass('datepicker')) { + $picker = $obj; + } else { + var parents = $obj.parents(); + for (var i = 0; i < parents.length; i++) { + if ($(parents[i]).hasClass('datepicker')) { + $picker = $(parents[i]); + } + } + } + return $picker; + }; + + var getPickersInputObject = function($obj) { + var $picker = getParentPickerObject($obj); + if ($picker.data("inputObjectId") != null) { + return $(InputObjects[$picker.data("inputObjectId")]); + } + return null; + } + var beforeMonth = function($obj) { + var $picker = getParentPickerObject($obj); + var date = getPickedDate($picker); + var targetMonth_lastDay = new Date(date.getYear() + 1900, date.getMonth(), 0).getDate(); + if (targetMonth_lastDay < date.getDate()) { + date.setDate(targetMonth_lastDay); + } + draw($picker, { + "isAnim": true, + "isOutputToInputObject": true + }, date.getYear() + 1900, date.getMonth() - 1, date.getDate(), date.getHours(), date.getMinutes()); + }; + + var nextMonth = function($obj) { + var $picker = getParentPickerObject($obj); + var date = getPickedDate($picker); + var targetMonth_lastDay = new Date(date.getYear() + 1900, date.getMonth() + 1, 0).getDate(); + if (targetMonth_lastDay < date.getDate()) { + date.setDate(targetMonth_lastDay); + } + draw($picker, { + "isAnim": true, + "isOutputToInputObject": true + }, date.getYear() + 1900, date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes()); + }; + + var getDate = function (str) { + var re = /^(\d{2,4})[-/](\d{1,2})[-/](\d{1,2}) (\d{1,2}):(\d{1,2})$/; + var m = re.exec(str); + // change year for 4 digits + if (m[1] < 99) { + var date = new Date(); + m[1] = parseInt(m[1]) + parseInt(date.getFullYear().toString().substr(0, 2) + "00"); + } + // return + return new Date(m[1], m[2] - 1, m[3], m[4], m[5]); + } + + var outputToInputObject = function($picker) { + var date = getPickedDate($picker); + var $inp = getPickersInputObject($picker); + var dateFormat = $picker.data("dateFormat"); + var locale = $picker.data("locale"); + var str = ""; + if ($inp == null) { + return; + } + + if (dateFormat == "default"){ + if(locale == "ja"){ + dateFormat = "YYYY/MM/DD hh:mm"; + }else{ + dateFormat = "YYYY-MM-DD hh:mm"; + } + } + + str = dateFormat; + var y = date.getYear() + 1900; + var m = date.getMonth() + 1; + var d = date.getDate(); + var hou = date.getHours(); + var min = date.getMinutes(); + + str = str.replace(/YYYY/gi, y) + .replace(/YY/g, y - 2000)/* century */ + .replace(/MM/g, zpadding(m)) + .replace(/M/g, m) + .replace(/DD/g, zpadding(d)) + .replace(/D/g, d) + .replace(/hh/g, zpadding(hou)) + .replace(/h/g, hou) + .replace(/mm/g, zpadding(min)) + .replace(/m/g, min); + $inp.val(str); + }; + + var getPickedDate = function($obj) { + var $picker = getParentPickerObject($obj); + return $picker.data("pickedDate"); + }; + + var zpadding = function(num) { + num = ("0" + num).slice(-2); + return num + }; + + var draw_date = function($picker, option, date) { + draw($picker, option, date.getYear() + 1900, date.getMonth(), date.getDate(), date.getHours(), date.getMinutes()); + }; + + var draw = function($picker, option, year, month, day, hour, min) { + var date = new Date(); + + if (hour != null) { + date = new Date(year, month, day, hour, min, 0); + } else if (year != null) { + date = new Date(year, month, day); + } else { + date = new Date(); + } + //console.log("dtpicker - draw()..." + year + "," + month + "," + day + " " + hour + ":" + min + " -> " + date); + + /* Read options */ + var isScroll = option.isAnim; /* It same with isAnim */ + + var isAnim = option.isAnim; + if($picker.data("animation") == false){ // If disabled by user option. + isAnim = false; + } + + var isOutputToInputObject = option.isOutputToInputObject; + + /* Read locale option */ + var locale = $picker.data("locale"); + var daysOfWeek = DAYS_OF_WEEK_EN; + if(locale == "ja"){ + daysOfWeek = DAYS_OF_WEEK_JA; + } + + /* Calculate dates */ + var todayDate = new Date(); + var firstWday = new Date(date.getYear() + 1900, date.getMonth(), 1).getDay(); + var lastDay = new Date(date.getYear() + 1900, date.getMonth() + 1, 0).getDate(); + var beforeMonthLastDay = new Date(date.getYear() + 1900, date.getMonth(), 0).getDate(); + var dateBeforeMonth = new Date(date.getYear() + 1900, date.getMonth(), 0); + var dateNextMonth = new Date(date.getYear() + 1900, date.getMonth() + 2, 0); + + /* Collect each part */ + var $header = $picker.children('.datepicker_header'); + var $inner = $picker.children('.datepicker_inner_container'); + var $calendar = $picker.children('.datepicker_inner_container').children('.datepicker_calendar'); + var $table = $calendar.children('.datepicker_table'); + var $timelist = $picker.children('.datepicker_inner_container').children('.datepicker_timelist'); + + /* Grasp a point that will be changed */ + var changePoint = ""; + var oldDate = getPickedDate($picker); + if(oldDate != null){ + if(oldDate.getMonth() != date.getMonth() || oldDate.getDate() != date.getDate()){ + changePoint = "calendar"; + } else if (oldDate.getHours() != date.getHours() || oldDate.getMinutes() != date.getMinutes()){ + if(date.getMinutes() == 0 || date.getMinutes() == 30){ + changePoint = "timelist"; + } + } + } + + /* Save newly date to Picker data */ + $($picker).data("pickedDate", date); + + /* Fade-out animation */ + if (isAnim == true) { + if(changePoint == "calendar"){ + $calendar.stop().queue([]); + $calendar.fadeTo("fast", 0.8); + }else if(changePoint == "timelist"){ + $timelist.stop().queue([]); + $timelist.fadeTo("fast", 0.8); + } + } + /* Remind timelist scroll state */ + var drawBefore_timeList_scrollTop = $timelist.scrollTop(); + + /* New timelist */ + var timelist_activeTimeCell_offsetTop = -1; + + /* Header ----- */ + $header.children().remove(); + var $link_before_month = $(''); + $link_before_month.text('<'); + $link_before_month.click(function() { + beforeMonth($picker); + }); + + var $now_month = $(''); + if(locale == "en"){ + $now_month.text((date.getYear() + 1900) + " - " + MONTHS_EN[date.getMonth()]); + }else if(locale == "ja"){ + $now_month.text((date.getYear() + 1900) + " / " + zpadding(date.getMonth() + 1)); + } + + var $link_next_month = $(''); + $link_next_month.text('>'); + $link_next_month.click(function() { + nextMonth($picker); + }); + + $header.append($link_before_month); + $header.append($now_month); + $header.append($link_next_month); + + /* Calendar > Table ----- */ + $table.children().remove(); + var $tr = $(''); + $table.append($tr); + + /* Output wday cells */ + for (var i = 0; i < 7; i++) { + var $td = $(''); + $td.text(daysOfWeek[i]); + $tr.append($td); + } + + /* Output day cells */ + var cellNum = Math.ceil((firstWday + lastDay) / 7) * 7; + for (var i = 0; i < cellNum; i++) { + var realDay = i + 1 - firstWday; + if (i % 7 == 0) { + $tr = $(''); + $table.append($tr); + } + + var $td = $(''); + $td.data("day", realDay); + + $tr.append($td); + + if (firstWday > i) {/* Before months day */ + $td.text(beforeMonthLastDay + realDay); + $td.addClass('day_another_month'); + $td.data("dateStr", dateBeforeMonth.getYear() + 1900 + "/" + (dateBeforeMonth.getMonth() + 1) + "/" + (beforeMonthLastDay + realDay)); + } else if (i < firstWday + lastDay) {/* Now months day */ + $td.text(realDay); + $td.data("dateStr", (date.getYear() + 1900) + "/" + (date.getMonth() + 1) + "/" + realDay); + } else {/* Next months day */ + $td.text(realDay - lastDay); + $td.addClass('day_another_month'); + $td.data("dateStr", dateNextMonth.getYear() + 1900 + "/" + (dateNextMonth.getMonth() + 1) + "/" + (realDay - lastDay)); + } + + if (i % 7 == 0) {/* Sunday */ + $td.addClass('wday_sun'); + } else if (i % 7 == 6) {/* Saturday */ + $td.addClass('wday_sat'); + } + + if (realDay == date.getDate()) {/* selected day */ + $td.addClass('active'); + } + + if (date.getMonth() == todayDate.getMonth() && realDay == todayDate.getDate()) {/* today */ + $td.addClass('today'); + } + + /* Set event-handler to day cell */ + + $td.click(function() { + if ($(this).hasClass('hover')) { + $(this).removeClass('hover'); + } + $(this).addClass('active'); + + var $picker = getParentPickerObject($(this)); + var targetDate = new Date($(this).data("dateStr")); + var selectedDate = getPickedDate($picker); + draw($picker, { + "isAnim": false, + "isOutputToInputObject": true + }, targetDate.getYear() + 1900, targetDate.getMonth(), targetDate.getDate(), selectedDate.getHours(), selectedDate.getMinutes()); + }); + + $td.hover(function() { + if (! $(this).hasClass('active')) { + $(this).addClass('hover'); + } + }, function() { + if ($(this).hasClass('hover')) { + $(this).removeClass('hover'); + } + }); + } + + /* Timelist ----- */ + $timelist.children().remove(); + + /* Set height to Timelist (Calendar innerHeight - Calendar padding) */ + $timelist.css("height", $calendar.innerHeight() - 10 + 'px'); + + /* Output time cells */ + for (var hour = 0; hour < 24; hour++) { + for (var min = 0; min <= 30; min += 30) { + var $o = $('
'); + $o.addClass('timelist_item'); + $o.text(zpadding(hour) + ":" + zpadding(min)); + + $o.data("hour", hour); + $o.data("min", min); + + $timelist.append($o); + + if (hour == date.getHours() && min == date.getMinutes()) {/* selected time */ + $o.addClass('active'); + timelist_activeTimeCell_offsetTop = $o.offset().top; + } + + /* Set event handler to time cell */ + + $o.click(function() { + if ($(this).hasClass('hover')) { + $(this).removeClass('hover'); + } + $(this).addClass('active'); + + var $picker = getParentPickerObject($(this)); + var date = getPickedDate($picker); + var hour = $(this).data("hour"); + var min = $(this).data("min"); + draw($picker, { + "isAnim": false, + "isOutputToInputObject": true + }, date.getYear() + 1900, date.getMonth(), date.getDate(), hour, min); + }); + + $o.hover(function() { + if (! $(this).hasClass('active')) { + $(this).addClass('hover'); + } + }, function() { + if ($(this).hasClass('hover')) { + $(this).removeClass('hover'); + } + }); + } + } + + /* Scroll the timelist */ + if(isScroll == true){ + /* Scroll to new active time-cell position */ + $timelist.scrollTop(timelist_activeTimeCell_offsetTop - $timelist.offset().top); + }else{ + /* Scroll to position that before redraw. */ + $timelist.scrollTop(drawBefore_timeList_scrollTop); + } + + /* Fade-in animation */ + if (isAnim == true) { + if(changePoint == "calendar"){ + $calendar.fadeTo("fast", 1.0); + }else if(changePoint == "timelist"){ + $timelist.fadeTo("fast", 1.0); + } + } + + /* Output to InputForm */ + if (isOutputToInputObject == true) { + outputToInputObject($picker); + } + }; + + var init = function($obj, opt) { + /* Container */ + var $picker = $('
'); + $picker.addClass('datepicker') + $obj.append($picker); + + /* Set options data to container object */ + if (opt.inputObjectId != null) { + $picker.data("inputObjectId", opt.inputObjectId); + } + $picker.data("pickerId", PickerObjects.length); + $picker.data("dateFormat", opt.dateFormat); + $picker.data("locale", opt.locale); + $picker.data("animation", opt.animation); + + /* Header */ + var $header = $('
'); + $header.addClass('datepicker_header'); + $picker.append($header); + /* InnerContainer*/ + var $inner = $('
'); + $inner.addClass('datepicker_inner_container'); + $picker.append($inner); + /* Calendar */ + var $calendar = $('
'); + $calendar.addClass('datepicker_calendar'); + var $table = $(''); + $table.addClass('datepicker_table'); + $calendar.append($table); + $inner.append($calendar); + /* Timelist */ + var $timelist = $('
'); + $timelist.addClass('datepicker_timelist'); + $inner.append($timelist); + + /* Set event handler to picker */ + $picker.hover( + function(){ + ActivePickerId = $(this).data("pickerId"); + }, + function(){ + ActivePickerId = -1; + } + ); + + PickerObjects.push($picker); + + draw_date($picker, { + "isAnim": true, + "isOutputToInputObject": true + }, opt.current); + }; + + /** + * Initialize dtpicker + */ + $.fn.dtpicker = function(config) { + var date = new Date(); + var defaults = { + "inputObjectId": undefined, + "current": date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes(), + "dateFormat": "default", + "locale": "en", + "animation": true + }; + + var options = $.extend(defaults, config); + options.current = getDate(options.current); + return this.each(function(i) { + init($(this), options); + }); + }; + + /** + * Initialize dtpicker, append to Text input field + * */ + $.fn.appendDtpicker = function(config) { + var date = new Date(); + var defaults = { + "inline": false, + "current": date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes(), + "dateFormat": "default", + "locale": "en", + "animation": true + } + var options = $.extend(defaults, config); + return this.each(function(i) { + + /* Add input-field with inputsObjects array */ + var input = this; + var inputObjectId = InputObjects.length; + InputObjects.push(input); + + options.inputObjectId = inputObjectId; + + /* Current date */ + var date, strDate, strTime; + if($(input).val() != null && $(input).val() != ""){ + options.current = $(input).val(); + } + + /* Make parent-div for picker */ + var $d = $('
'); + if(options.inline == false){ + /* float mode */ + $d.css("position","absolute"); + } + $d.insertAfter(input); + + /* Initialize picker */ + + var pickerId = PickerObjects.length; + + var $picker_parent = $($d).dtpicker(options); // call dtpicker() method + + var $picker = $picker_parent.children('.datepicker'); + + /* Link input-field with picker*/ + $(input).data('pickerId', pickerId); + + /* Set event handler to input-field */ + + $(input).keyup(function() { + var $input = $(this); + var $picker = $(PickerObjects[$input.data('pickerId')]); + if ($input.val() != null && ( + $input.data('beforeVal') == null || + ( $input.data('beforeVal') != null && $input.data('beforeVal') != $input.val()) ) + ) { /* beforeValue == null || beforeValue != nowValue */ + var date = getDate($input.val()); + if (isNaN(date.getDate()) == false) {/* Valid format... */ + draw_date($picker, { + "isAnim":true, + "isOutputToInputObject":false + }, date); + } + } + $input.data('beforeVal',$input.val()) + }); + + $(input).change(function(){ + $(this).trigger('keyup'); + }); + + if(options.inline == true){ + /* inline mode */ + $picker.data('isInline',true); + }else{ + /* float mode */ + $picker.data('isInline',false); + $picker_parent.css({ + "zIndex": 100 + }); + $picker.css("width","auto"); + + /* Hide this picker */ + $picker.hide(); + + /* Set onClick event handler for input-field */ + $(input).click(function(){ + var $input = $(this); + var $picker = $(PickerObjects[$input.data('pickerId')]); + ActivePickerId = $input.data('pickerId'); + $picker.show(); + $picker.parent().css("top", $input.offset().top + $input.outerHeight() + 2 + "px"); + $picker.parent().css("left", $input.offset().left + "px"); + }); + } + }); +}; + +/* Set event handler to Body element, for hide a floated-picker */ +$(function(){ + $('body').click(function(){ + for(var i=0;i Date: Thu, 21 Mar 2013 15:55:48 +0100 Subject: [PATCH 3/9] Rename localAssets to useLocalAssets. --- web/status.go | 2 +- web/web.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/status.go b/web/status.go index 887867c5a..07f310b3d 100644 --- a/web/status.go +++ b/web/status.go @@ -42,7 +42,7 @@ func (h *StatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } var t *template.Template - if *localAssets { + if *useLocalAssets { t, _ = template.ParseFiles("web/templates/status.html") } else { templateFile, err := blob.GetFile(blob.TemplateFiles, "status.html") diff --git a/web/web.go b/web/web.go index 6e167e3e6..74e5f1222 100644 --- a/web/web.go +++ b/web/web.go @@ -27,7 +27,7 @@ import ( // Commandline flags. var ( listenAddress = flag.String("listenAddress", ":9090", "Address to listen on for web interface.") - localAssets = flag.Bool("localAssets", false, "Read assets/templates from file instead of binary.") + useLocalAssets = flag.Bool("localAssets", false, "Read assets/templates from file instead of binary.") ) func StartServing(appState *appstate.ApplicationState) { @@ -37,7 +37,7 @@ func StartServing(appState *appstate.ApplicationState) { http.Handle("/status", &StatusHandler{appState: appState}) http.Handle("/api/", gorest.Handle()) http.Handle("/metrics.json", exporter) - if *localAssets { + if *useLocalAssets { http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static")))) } else { http.Handle("/static/", http.StripPrefix("/static/", new(blob.Handler))) From 1f484b03c78fb519c1b6da3c05a2c482e42f5455 Mon Sep 17 00:00:00 2001 From: Johannes 'fish' Ziemke Date: Thu, 21 Mar 2013 15:56:41 +0100 Subject: [PATCH 4/9] Renamed date_s to dateString. To be more explicit. --- web/static/js/graph.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/static/js/graph.js b/web/static/js/graph.js index b9d938741..a3db2030b 100644 --- a/web/static/js/graph.js +++ b/web/static/js/graph.js @@ -207,10 +207,10 @@ Prometheus.Graph.prototype.getOrSetEndDate = function() { Prometheus.Graph.prototype.setEndDate = function(date) { var self = this; - date_s = date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate() + ' ' + + dateString = date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes() self.endDate.val("") - self.endDate.appendDtpicker({"current": date_s}) + self.endDate.appendDtpicker({"current": dateString}) self.submitQuery(); }; From 8e55ff0a8d46073f05ec792289f26abfdc8ac4f8 Mon Sep 17 00:00:00 2001 From: Johannes 'fish' Ziemke Date: Thu, 21 Mar 2013 17:06:37 +0100 Subject: [PATCH 5/9] Save and restore end date in/from url. --- web/static/graph.html | 2 +- web/static/js/graph.js | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/web/static/graph.html b/web/static/graph.html index 37b2b7033..b0a99c8ec 100644 --- a/web/static/graph.html +++ b/web/static/graph.html @@ -43,7 +43,7 @@ - + diff --git a/web/static/js/graph.js b/web/static/js/graph.js index a3db2030b..5f5329937 100644 --- a/web/static/js/graph.js +++ b/web/static/js/graph.js @@ -76,9 +76,14 @@ Prometheus.Graph.prototype.initialize = function() { self.spinner = graphWrapper.find(".spinner"); self.evalStats = graphWrapper.find(".eval_stats"); - self.endDate = graphWrapper.find("input[name=end]"); - self.endDate.appendDtpicker(); - self.endDate.val("") + self.endDate = graphWrapper.find("input[name=end_input]"); + console.log(self.options) + if (self.options["end_input"]) { + self.endDate.appendDtpicker({"current": self.options["end_input"]}) + } else { + self.endDate.appendDtpicker(); + self.endDate.val("") + } self.endDate.change(function() { self.submitQuery() }); self.stacked.change(function() { self.updateGraph(); }); @@ -131,7 +136,7 @@ Prometheus.Graph.prototype.getOptions = function() { var optionInputs = [ "expr", "range_input", - "end", + "end_input", "step_input", "stacked" ]; @@ -189,6 +194,7 @@ Prometheus.Graph.prototype.decreaseRange = function() { }; Prometheus.Graph.prototype.getEndDate = function() { + var self = this if (!self.endDate || !self.endDate.val()) { return null; } @@ -196,6 +202,7 @@ Prometheus.Graph.prototype.getEndDate = function() { }; Prometheus.Graph.prototype.getOrSetEndDate = function() { + var self = this var date = self.getEndDate(); if (date) { return date @@ -239,7 +246,6 @@ Prometheus.Graph.prototype.submitQuery = function() { var data = self.queryForm.serialize(); var endDate = self.getEndDate() - if (endDate) { data = data + "&end=" + endDate/1000 } From f170720f61b61a863118591f26d9d86b21ebb56e Mon Sep 17 00:00:00 2001 From: Johannes 'fish' Ziemke Date: Thu, 21 Mar 2013 17:36:04 +0100 Subject: [PATCH 6/9] Use hidden input for end. Instead of appending to url manually. --- web/static/graph.html | 1 + web/static/js/graph.js | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/web/static/graph.html b/web/static/graph.html index b0a99c8ec..46360e674 100644 --- a/web/static/graph.html +++ b/web/static/graph.html @@ -45,6 +45,7 @@ + diff --git a/web/static/js/graph.js b/web/static/js/graph.js index 5f5329937..44ce58934 100644 --- a/web/static/js/graph.js +++ b/web/static/js/graph.js @@ -243,17 +243,14 @@ Prometheus.Graph.prototype.submitQuery = function() { self.queryForm.find("input[name=range]").val(rangeSeconds); var resolution = self.queryForm.find("input[name=step_input]").val() || Math.max(Math.floor(rangeSeconds / 250), 1); self.queryForm.find("input[name=step]").val(resolution); + var endDate = self.getEndDate() / 1000; + self.queryForm.find("input[name=end]").val(endDate); - var data = self.queryForm.serialize(); - var endDate = self.getEndDate() - if (endDate) { - data = data + "&end=" + endDate/1000 - } $.ajax({ method: self.queryForm.attr("method"), url: self.queryForm.attr("action"), dataType: "json", - data: data, + data: self.queryForm.serialize(), success: function(json, textStatus) { if (json.Type == "error") { alert(json.Value); From c0b5ba6512c1bf777870ffdac5b06c559f77221c Mon Sep 17 00:00:00 2001 From: Johannes 'fish' Ziemke Date: Thu, 21 Mar 2013 17:44:21 +0100 Subject: [PATCH 7/9] Prevent unnecessary reload. --- web/static/js/graph.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/static/js/graph.js b/web/static/js/graph.js index 44ce58934..e59e64808 100644 --- a/web/static/js/graph.js +++ b/web/static/js/graph.js @@ -218,17 +218,18 @@ Prometheus.Graph.prototype.setEndDate = function(date) { date.getHours() + ':' + date.getMinutes() self.endDate.val("") self.endDate.appendDtpicker({"current": dateString}) - self.submitQuery(); }; Prometheus.Graph.prototype.increaseEnd = function() { var self = this; self.setEndDate(new Date(self.getOrSetEndDate() + self.parseRange(self.rangeInput.val()) * 1000)) + self.submitQuery(); }; Prometheus.Graph.prototype.decreaseEnd = function() { var self = this; self.setEndDate(new Date(self.getOrSetEndDate() - self.parseRange(self.rangeInput.val()) * 1000)) + self.submitQuery(); }; Prometheus.Graph.prototype.submitQuery = function() { From 72bd75185703a7cbb8b826b5cfebccaca7788502 Mon Sep 17 00:00:00 2001 From: Johannes 'fish' Ziemke Date: Thu, 21 Mar 2013 17:47:40 +0100 Subject: [PATCH 8/9] Increase/decrease end date by 1/2 range. --- web/static/js/graph.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/static/js/graph.js b/web/static/js/graph.js index e59e64808..282139d37 100644 --- a/web/static/js/graph.js +++ b/web/static/js/graph.js @@ -222,13 +222,13 @@ Prometheus.Graph.prototype.setEndDate = function(date) { Prometheus.Graph.prototype.increaseEnd = function() { var self = this; - self.setEndDate(new Date(self.getOrSetEndDate() + self.parseRange(self.rangeInput.val()) * 1000)) + self.setEndDate(new Date(self.getOrSetEndDate() + self.parseRange(self.rangeInput.val()) * 1000/2 )) // increase by 1/2 range & convert ms in s self.submitQuery(); }; Prometheus.Graph.prototype.decreaseEnd = function() { var self = this; - self.setEndDate(new Date(self.getOrSetEndDate() - self.parseRange(self.rangeInput.val()) * 1000)) + self.setEndDate(new Date(self.getOrSetEndDate() - self.parseRange(self.rangeInput.val()) * 1000/2 )) self.submitQuery(); }; From 6213c0f1ee25ebc79adaedc42cd9f8288920750c Mon Sep 17 00:00:00 2001 From: Johannes 'fish' Ziemke Date: Thu, 21 Mar 2013 17:54:03 +0100 Subject: [PATCH 9/9] Add missing semicolons. --- web/static/js/graph.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/web/static/js/graph.js b/web/static/js/graph.js index 282139d37..aa87e9a0f 100644 --- a/web/static/js/graph.js +++ b/web/static/js/graph.js @@ -77,12 +77,11 @@ Prometheus.Graph.prototype.initialize = function() { self.evalStats = graphWrapper.find(".eval_stats"); self.endDate = graphWrapper.find("input[name=end_input]"); - console.log(self.options) if (self.options["end_input"]) { - self.endDate.appendDtpicker({"current": self.options["end_input"]}) + self.endDate.appendDtpicker({"current": self.options["end_input"]}); } else { self.endDate.appendDtpicker(); - self.endDate.val("") + self.endDate.val(""); } self.endDate.change(function() { self.submitQuery() }); @@ -194,30 +193,30 @@ Prometheus.Graph.prototype.decreaseRange = function() { }; Prometheus.Graph.prototype.getEndDate = function() { - var self = this + var self = this; if (!self.endDate || !self.endDate.val()) { return null; } - return new Date(self.endDate.val()).getTime() + return new Date(self.endDate.val()).getTime(); }; Prometheus.Graph.prototype.getOrSetEndDate = function() { - var self = this + var self = this; var date = self.getEndDate(); if (date) { - return date + return date; } - date = new Date() - self.setEndDate(date) - return date + date = new Date(); + self.setEndDate(date); + return date; } Prometheus.Graph.prototype.setEndDate = function(date) { var self = this; dateString = date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate() + ' ' + - date.getHours() + ':' + date.getMinutes() - self.endDate.val("") - self.endDate.appendDtpicker({"current": dateString}) + date.getHours() + ':' + date.getMinutes(); + self.endDate.val(""); + self.endDate.appendDtpicker({"current": dateString}); }; Prometheus.Graph.prototype.increaseEnd = function() { @@ -291,7 +290,7 @@ Prometheus.Graph.prototype.parseValue = function(value) { if (value == "NaN" || value == "Inf" || value == "-Inf") { return 0; // TODO: what should we really do here? } else { - return parseFloat(value) + return parseFloat(value); } };