diff --git a/web/static/js/graph.js b/web/static/js/graph.js
index aa87e9a0f7..e2bc92fce5 100644
--- a/web/static/js/graph.js
+++ b/web/static/js/graph.js
@@ -71,6 +71,14 @@ Prometheus.Graph.prototype.initialize = function() {
self.stacked = self.queryForm.find("input[name=stacked]");
self.insertMetric = self.queryForm.find("select[name=insert_metric]");
+ // Return moves focus back to expr instead of submitting.
+ self.insertMetric.bind('keydown', 'return', function(e) {
+ self.expr.focus();
+ self.expr.val(self.expr.val());
+
+ return e.preventDefault();
+ })
+
self.graph = graphWrapper.find(".graph");
self.legend = graphWrapper.find(".legend");
self.spinner = graphWrapper.find(".spinner");
@@ -96,8 +104,11 @@ Prometheus.Graph.prototype.initialize = function() {
self.queryForm.find("input[name=dec_end]").click(function() { self.decreaseEnd(); });
self.insertMetric.change(function() {
- self.expr.val(self.expr.val() + self.insertMetric.val());
+ self.expr.selection('replace', {text: self.insertMetric.val(), mode: 'before'})
+ self.insertMetric.focus(); // refocusing
});
+ self.insertMetric.click(function() { self.expr.focus() })
+
self.expr.focus(); // TODO: move to external Graph method.
self.populateInsertableMetrics();
@@ -114,9 +125,12 @@ Prometheus.Graph.prototype.populateInsertableMetrics = function() {
url: "/api/metrics",
dataType: "json",
success: function(json, textStatus) {
+ var availableMetrics = []
for (var i = 0; i < json.length; i++) {
self.insertMetric[0].options.add(new Option(json[i], json[i]));
+ availableMetrics.push(json[i])
}
+ self.expr.autocomplete({source: availableMetrics})
},
error: function() {
alert("Error loading available metrics!");
@@ -321,8 +335,8 @@ Prometheus.Graph.prototype.showGraph = function() {
var self = this;
self.rickshawGraph = new Rickshaw.Graph({
element: self.graph[0],
- height: Math.max($(window).height() - 200, 100),
- width: Math.max($(window).width() - 200, 200),
+ height: Math.max(self.graph.innerHeight(), 100),
+ width: Math.max(self.graph.innerWidth(), 200),
renderer: (self.stacked.is(":checked") ? "stack" : "line"),
interpolation: "linear",
series: self.data
@@ -371,6 +385,15 @@ Prometheus.Graph.prototype.updateGraph = function(reloadGraph) {
self.changeHandler();
};
+Prometheus.Graph.prototype.resizeGraph = function() {
+ var self = this;
+ self.rickshawGraph.configure({
+ height: Math.max(self.graph.innerHeight(), 100),
+ width: Math.max(self.graph.innerWidth(), 200),
+ });
+ self.rickshawGraph.render();
+};
+
function parseGraphOptionsFromUrl() {
var hashOptions = window.location.hash.slice(1);
if (!hashOptions) {
@@ -395,6 +418,9 @@ function addGraph(options) {
graph.onChange(function() {
storeGraphOptionsInUrl();
});
+ $(window).resize(function() {
+ graph.resizeGraph();
+ });
}
function init() {
diff --git a/web/static/vendor/js/jquery.hotkeys.js b/web/static/vendor/js/jquery.hotkeys.js
new file mode 100644
index 0000000000..5905f9db2a
--- /dev/null
+++ b/web/static/vendor/js/jquery.hotkeys.js
@@ -0,0 +1,100 @@
+/*
+ * jQuery Hotkeys Plugin
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Based upon the plugin by Tzury Bar Yochay:
+ * http://github.com/tzuryby/hotkeys
+ *
+ * Original idea by:
+ * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
+*/
+
+(function(jQuery){
+
+ jQuery.hotkeys = {
+ version: "0.8",
+
+ specialKeys: {
+ 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
+ 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
+ 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
+ 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
+ 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
+ 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
+ 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
+ },
+
+ shiftNums: {
+ "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
+ "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
+ ".": ">", "/": "?", "\\": "|"
+ }
+ };
+
+ function keyHandler( handleObj ) {
+ // Only care when a possible input has been specified
+ if ( typeof handleObj.data !== "string" ) {
+ return;
+ }
+
+ var origHandler = handleObj.handler,
+ keys = handleObj.data.toLowerCase().split(" "),
+ textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color"];
+
+ handleObj.handler = function( event ) {
+ // Don't fire in text-accepting inputs that we didn't directly bind to
+ if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
+ jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) {
+ return;
+ }
+
+ // Keypress represents characters, not special keys
+ var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
+ character = String.fromCharCode( event.which ).toLowerCase(),
+ key, modif = "", possible = {};
+
+ // check combinations (alt|ctrl|shift+anything)
+ if ( event.altKey && special !== "alt" ) {
+ modif += "alt+";
+ }
+
+ if ( event.ctrlKey && special !== "ctrl" ) {
+ modif += "ctrl+";
+ }
+
+ // TODO: Need to make sure this works consistently across platforms
+ if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
+ modif += "meta+";
+ }
+
+ if ( event.shiftKey && special !== "shift" ) {
+ modif += "shift+";
+ }
+
+ if ( special ) {
+ possible[ modif + special ] = true;
+
+ } else {
+ possible[ modif + character ] = true;
+ possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
+
+ // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
+ if ( modif === "shift+" ) {
+ possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
+ }
+ }
+
+ for ( var i = 0, l = keys.length; i < l; i++ ) {
+ if ( possible[ keys[i] ] ) {
+ return origHandler.apply( this, arguments );
+ }
+ }
+ };
+ }
+
+ jQuery.each([ "keydown", "keyup", "keypress" ], function() {
+ jQuery.event.special[ this ] = { add: keyHandler };
+ });
+
+})( jQuery );
\ No newline at end of file
diff --git a/web/static/vendor/js/jquery.selection.js b/web/static/vendor/js/jquery.selection.js
new file mode 100644
index 0000000000..86b100c602
--- /dev/null
+++ b/web/static/vendor/js/jquery.selection.js
@@ -0,0 +1,360 @@
+/*!
+ * jQuery.selection - jQuery Plugin
+ *
+ * Copyright (c) 2010-2012 IWASAKI Koji (@madapaja).
+ * http://blog.madapaja.net/
+ * Under The MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+(function($, win, doc) {
+ /**
+ * 要素の文字列選択状態を取得します
+ *
+ * @param {Element} element 対象要素
+ * @return {Object} return
+ * @return {String} return.text 選択されている文字列
+ * @return {Integer} return.start 選択開始位置
+ * @return {Integer} return.end 選択終了位置
+ */
+ var _getCaretInfo = function(element){
+ var res = {
+ text: '',
+ start: 0,
+ end: 0
+ };
+
+ if (!element.value) {
+ /* 値がない、もしくは空文字列 */
+ return res;
+ }
+
+ try {
+ if (win.getSelection) {
+ /* IE 以外 */
+ res.start = element.selectionStart;
+ res.end = element.selectionEnd;
+ res.text = element.value.slice(res.start, res.end);
+ } else if (doc.selection) {
+ /* for IE */
+ element.focus();
+
+ var range = doc.selection.createRange(),
+ range2 = doc.body.createTextRange(),
+ tmpLength;
+
+ res.text = range.text;
+
+ try {
+ range2.moveToElementText(element);
+ range2.setEndPoint('StartToStart', range);
+ } catch (e) {
+ range2 = element.createTextRange();
+ range2.setEndPoint('StartToStart', range);
+ }
+
+ res.start = element.value.length - range2.text.length;
+ res.end = res.start + range.text.length;
+ }
+ } catch (e) {
+ /* あきらめる */
+ }
+
+ return res;
+ };
+
+ /**
+ * 要素に対するキャレット操作
+ * @type {Object}
+ */
+ var _CaretOperation = {
+ /**
+ * 要素のキャレット位置を取得します
+ *
+ * @param {Element} element 対象要素
+ * @return {Object} return
+ * @return {Integer} return.start 選択開始位置
+ * @return {Integer} return.end 選択終了位置
+ */
+ getPos: function(element) {
+ var tmp = _getCaretInfo(element);
+ return {start: tmp.start, end: tmp.end};
+ },
+
+ /**
+ * 要素のキャレット位置を設定します
+ *
+ * @param {Element} element 対象要素
+ * @param {Object} toRange 設定するキャレット位置
+ * @param {Integer} toRange.start 選択開始位置
+ * @param {Integer} toRange.end 選択終了位置
+ * @param {String} caret キャレットモード "keep" | "start" | "end" のいずれか
+ */
+ setPos: function(element, toRange, caret) {
+ caret = this._caretMode(caret);
+
+ if (caret == 'start') {
+ toRange.end = toRange.start;
+ } else if (caret == 'end') {
+ toRange.start = toRange.end;
+ }
+
+ element.focus();
+ try {
+ if (element.createTextRange) {
+ var range = element.createTextRange();
+
+ if (win.navigator.userAgent.toLowerCase().indexOf("msie") >= 0) {
+ toRange.start = element.value.substr(0, toRange.start).replace(/\r/g, '').length;
+ toRange.end = element.value.substr(0, toRange.end).replace(/\r/g, '').length;
+ }
+
+ range.collapse(true);
+ range.moveStart('character', toRange.start);
+ range.moveEnd('character', toRange.end - toRange.start);
+
+ range.select();
+ } else if (element.setSelectionRange) {
+ element.setSelectionRange(toRange.start, toRange.end);
+ }
+ } catch (e) {
+ /* あきらめる */
+ }
+ },
+
+ /**
+ * 要素内の選択文字列を取得します
+ *
+ * @param {Element} element 対象要素
+ * @return {String} return 選択文字列
+ */
+ getText: function(element) {
+ return _getCaretInfo(element).text;
+ },
+
+ /**
+ * キャレットモードを選択します
+ *
+ * @param {String} caret キャレットモード
+ * @return {String} return "keep" | "start" | "end" のいずれか
+ */
+ _caretMode: function(caret) {
+ caret = caret || "keep";
+ if (caret == false) {
+ caret = 'end';
+ }
+
+ switch (caret) {
+ case 'keep':
+ case 'start':
+ case 'end':
+ break;
+
+ default:
+ caret = 'keep';
+ }
+
+ return caret;
+ },
+
+ /**
+ * 選択文字列を置き換えます
+ *
+ * @param {Element} element 対象要素
+ * @param {String} text 置き換える文字列
+ * @param {String} caret キャレットモード "keep" | "start" | "end" のいずれか
+ */
+ replace: function(element, text, caret) {
+ var tmp = _getCaretInfo(element),
+ orig = element.value,
+ pos = $(element).scrollTop(),
+ range = {start: tmp.start, end: tmp.start + text.length};
+
+ element.value = orig.substr(0, tmp.start) + text + orig.substr(tmp.end);
+
+ $(element).scrollTop(pos);
+ this.setPos(element, range, caret);
+ },
+
+ /**
+ * 文字列を選択文字列の前に挿入します
+ *
+ * @param {Element} element 対象要素
+ * @param {String} text 挿入文字列
+ * @param {String} caret キャレットモード "keep" | "start" | "end" のいずれか
+ */
+ insertBefore: function(element, text, caret) {
+ var tmp = _getCaretInfo(element),
+ orig = element.value,
+ pos = $(element).scrollTop(),
+ range = {start: tmp.start + text.length, end: tmp.end + text.length};
+
+ element.value = orig.substr(0, tmp.start) + text + orig.substr(tmp.start);
+
+ $(element).scrollTop(pos);
+ this.setPos(element, range, caret);
+ },
+
+ /**
+ * 文字列を選択文字列の後に挿入します
+ *
+ * @param {Element} element 対象要素
+ * @param {String} text 挿入文字列
+ * @param {String} caret キャレットモード "keep" | "start" | "end" のいずれか
+ */
+ insertAfter: function(element, text, caret) {
+ var tmp = _getCaretInfo(element),
+ orig = element.value,
+ pos = $(element).scrollTop(),
+ range = {start: tmp.start, end: tmp.end};
+
+ element.value = orig.substr(0, tmp.end) + text + orig.substr(tmp.end);
+
+ $(element).scrollTop(pos);
+ this.setPos(element, range, caret);
+ }
+ };
+
+ /* jQuery.selection を追加 */
+ $.extend({
+ /**
+ * ウィンドウの選択されている文字列を取得
+ *
+ * @param {String} mode 選択モード "text" | "html" のいずれか
+ * @return {String} return
+ */
+ selection: function(mode) {
+ var getText = ((mode || 'text').toLowerCase() == 'text');
+
+ try {
+ if (win.getSelection) {
+ if (getText) {
+ // get text
+ return win.getSelection().toString();
+ } else {
+ // get html
+ var sel = win.getSelection(), range;
+
+ if (sel.getRangeAt) {
+ range = sel.getRangeAt(0);
+ } else {
+ range = doc.createRange();
+ range.setStart(sel.anchorNode, sel.anchorOffset);
+ range.setEnd(sel.focusNode, sel.focusOffset);
+ }
+
+ return $('
').append(range.cloneContents()).html();
+ }
+ } else if (doc.selection) {
+ if (getText) {
+ // get text
+ return doc.selection.createRange().text;
+ } else {
+ // get html
+ return doc.selection.createRange().htmlText;
+ }
+ }
+ } catch (e) {
+ /* あきらめる */
+ }
+
+ return '';
+ }
+ });
+
+ /* selection を追加 */
+ $.fn.extend({
+ selection: function(mode, opts) {
+ opts = opts || {};
+
+ switch (mode) {
+ /**
+ * selection('getPos')
+ * キャレット位置を取得します
+ *
+ * @return {Object} return
+ * @return {Integer} return.start 選択開始位置
+ * @return {Integer} return.end 選択終了位置
+ */
+ case 'getPos':
+ return _CaretOperation.getPos(this[0]);
+ break;
+
+ /**
+ * selection('setPos', opts)
+ * キャレット位置を設定します
+ *
+ * @param {Integer} opts.start 選択開始位置
+ * @param {Integer} opts.end 選択終了位置
+ */
+ case 'setPos':
+ return this.each(function() {
+ _CaretOperation.setPos(this, opts);
+ });
+ break;
+
+ /**
+ * selection('replace', opts)
+ * 選択文字列を置き換えます
+ *
+ * @param {String} opts.text 置き換える文字列
+ * @param {String} opts.caret キャレットモード "keep" | "start" | "end" のいずれか
+ */
+ case 'replace':
+ return this.each(function() {
+ _CaretOperation.replace(this, opts.text, opts.caret);
+ });
+ break;
+
+ /**
+ * selection('insert', opts)
+ * 選択文字列の前、もしくは後に文字列を挿入えます
+ *
+ * @param {String} opts.text 挿入文字列
+ * @param {String} opts.caret キャレットモード "keep" | "start" | "end" のいずれか
+ * @param {String} opts.mode 挿入モード "before" | "after" のいずれか
+ */
+ case 'insert':
+ return this.each(function() {
+ if (opts.mode == 'before') {
+ _CaretOperation.insertBefore(this, opts.text, opts.caret);
+ } else {
+ _CaretOperation.insertAfter(this, opts.text, opts.caret);
+ }
+ });
+
+ break;
+
+ /**
+ * selection('get')
+ * 選択されている文字列を取得
+ *
+ * @return {String} return
+ */
+ case 'get':
+ default:
+ return _CaretOperation.getText(this[0]);
+ break;
+ }
+
+ return this;
+ }
+ });
+})(jQuery, window, window.document);