Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add metric name typeahead hints to the sensor input.

Note that this commit requires patching bootstrap Typeahead to support
showing the typeahead when no text has been entered yet.
  • Loading branch information...
commit a42c12df02d7827f7444c61f94e6fc3849c70efd 1 parent 9dc0c1d
@oldpatricka oldpatricka authored
View
297 phantomweb/static/js/bootstrap-hacks.js
@@ -0,0 +1,297 @@
+
+
+/* Make Typeahead be able to work with minLength == 0
+ Should not be neccessary ud https://github.com/twitter/bootstrap/pull/5063 is
+ ever reopened and fixed
+*/
+
+!function($){
+
+ "use strict"; // jshint ;_;
+
+
+ /* TYPEAHEAD PUBLIC CLASS DEFINITION
+ * ================================= */
+
+ var Typeahead = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.typeahead.defaults, options)
+ this.matcher = this.options.matcher || this.matcher
+ this.sorter = this.options.sorter || this.sorter
+ this.highlighter = this.options.highlighter || this.highlighter
+ this.updater = this.options.updater || this.updater
+ this.$menu = $(this.options.menu).appendTo('body')
+ this.source = this.options.source
+ this.shown = false
+ this.listen()
+ }
+
+ Typeahead.prototype = {
+
+ constructor: Typeahead
+
+ , select: function () {
+ var val = this.$menu.find('.active').attr('data-value')
+ this.$element
+ .val(this.updater(val))
+ .change()
+ return this.hide()
+ }
+
+ , updater: function (item) {
+ return item
+ }
+
+ , show: function () {
+ var pos = $.extend({}, this.$element.offset(), {
+ height: this.$element[0].offsetHeight
+ })
+
+ this.$menu.css({
+ top: pos.top + pos.height
+ , left: pos.left
+ })
+
+ this.$menu.show()
+ this.shown = true
+ return this
+ }
+
+ , hide: function () {
+ this.$menu.hide()
+ this.shown = false
+ return this
+ }
+
+ , lookup: function (event) {
+ var items
+
+ this.query = this.$element.val() || ''
+
+ if (this.query.length < this.options.minLength) {
+ return this.shown ? this.hide() : this
+ }
+
+ items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
+
+ return items ? this.process(items) : this
+ }
+
+ , process: function (items) {
+ var that = this
+
+ items = $.grep(items, function (item) {
+ return that.matcher(item)
+ })
+
+ items = this.sorter(items)
+
+ if (!items.length) {
+ return this.shown ? this.hide() : this
+ }
+
+ return this.render(items.slice(0, this.options.items)).show()
+ }
+
+ , matcher: function (item) {
+ return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+ }
+
+ , sorter: function (items) {
+ var beginswith = []
+ , caseSensitive = []
+ , caseInsensitive = []
+ , item
+
+ while (item = items.shift()) {
+ if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+ else if (~item.indexOf(this.query)) caseSensitive.push(item)
+ else caseInsensitive.push(item)
+ }
+
+ return beginswith.concat(caseSensitive, caseInsensitive)
+ }
+
+ , highlighter: function (item) {
+ var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
+ return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+ return '<strong>' + match + '</strong>'
+ })
+ }
+
+ , render: function (items) {
+ var that = this
+
+ items = $(items).map(function (i, item) {
+ i = $(that.options.item).attr('data-value', item)
+ i.find('a').html(that.highlighter(item))
+ return i[0]
+ })
+
+ items.first().addClass('active')
+ this.$menu.html(items)
+ return this
+ }
+
+ , next: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , next = active.next()
+
+ if (!next.length) {
+ next = $(this.$menu.find('li')[0])
+ }
+
+ next.addClass('active')
+ }
+
+ , prev: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , prev = active.prev()
+
+ if (!prev.length) {
+ prev = this.$menu.find('li').last()
+ }
+
+ prev.addClass('active')
+ }
+
+ , listen: function () {
+ this.$element
+ .on('blur', $.proxy(this.blur, this))
+ .on('keypress', $.proxy(this.keypress, this))
+ .on('keyup', $.proxy(this.keyup, this))
+
+ if (this.eventSupported('keydown')) {
+ this.$element.on('keydown', $.proxy(this.keydown, this))
+ }
+
+ this.$menu
+ .on('click', $.proxy(this.click, this))
+ .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+ }
+
+ , eventSupported: function(eventName) {
+ var isSupported = eventName in this.$element
+ if (!isSupported) {
+ this.$element.setAttribute(eventName, 'return;')
+ isSupported = typeof this.$element[eventName] === 'function'
+ }
+ return isSupported
+ }
+
+ , move: function (e) {
+ if (!this.shown) return
+
+ switch(e.keyCode) {
+ case 9: // tab
+ case 13: // enter
+ case 27: // escape
+ e.preventDefault()
+ break
+
+ case 38: // up arrow
+ e.preventDefault()
+ this.prev()
+ break
+
+ case 40: // down arrow
+ e.preventDefault()
+ this.next()
+ break
+ }
+
+ e.stopPropagation()
+ }
+
+ , keydown: function (e) {
+ this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
+ this.move(e)
+ }
+
+ , keypress: function (e) {
+ if (this.suppressKeyPressRepeat) return
+ this.move(e)
+ }
+
+ , keyup: function (e) {
+ switch(e.keyCode) {
+ case 40: // down arrow
+ case 38: // up arrow
+ case 16: // shift
+ case 17: // ctrl
+ case 18: // alt
+ break
+
+ case 9: // tab
+ case 13: // enter
+ if (!this.shown) return
+ this.select()
+ break
+
+ case 27: // escape
+ if (!this.shown) return
+ this.hide()
+ break
+
+ default:
+ this.lookup()
+ }
+
+ e.stopPropagation()
+ e.preventDefault()
+ }
+
+ , blur: function (e) {
+ var that = this
+ setTimeout(function () { that.hide() }, 150)
+ }
+
+ , click: function (e) {
+ e.stopPropagation()
+ e.preventDefault()
+ this.select()
+ }
+
+ , mouseenter: function (e) {
+ this.$menu.find('.active').removeClass('active')
+ $(e.currentTarget).addClass('active')
+ }
+
+ }
+
+
+ /* TYPEAHEAD PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.typeahead = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('typeahead')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.typeahead.defaults = {
+ source: []
+ , items: 8
+ , menu: '<ul class="typeahead dropdown-menu"></ul>'
+ , item: '<li><a href="#"></a></li>'
+ , minLength: 1
+ }
+
+ $.fn.typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD DATA-API
+ * ================== */
+
+ $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+ var $this = $(this)
+ if ($this.data('typeahead')) return
+ e.preventDefault()
+ $this.typeahead($this.data())
+ })
+
+}(window.jQuery);
View
36 phantomweb/static/js/phantom_domain.js
@@ -8,8 +8,10 @@ var g_decision_engines_by_type = {'sensor': 'Sensor', 'multicloud': 'Multi Cloud
var g_current_details_request = null;
var g_current_details_timer = null;
var g_selected_instance = null;
+var g_available_sensors = [];
var DEFAULT_DECISION_ENGINE = 'Multi Cloud';
var DETAILS_TIMER_MS = 5000;
+var SENSOR_HINT_ITEMS = 25;
$(document).ready(function() {
@@ -17,7 +19,19 @@ $(document).ready(function() {
$("#phantom_domain_main_combined_pane_inner").hide();
- $("#phantom_domain_sensors_input").tagsManager();
+ var $sensor_input = $("#phantom_domain_sensors_input").tagsManager({
+ typeahead: true,
+ typeaheadDelegate: {
+ source: function() {
+ return g_available_sensors;
+ },
+ minLength: 0,
+ items: SENSOR_HINT_ITEMS
+ }
+ });
+
+ //enable showing hints on click
+ $sensor_input.on('focus', $sensor_input.typeahead.bind($sensor_input, 'lookup'));
$("input[name=hidden-tags]").change(function() {
phantom_update_sensors();
@@ -118,6 +132,7 @@ $(document).ready(function() {
return false;
});
+ get_available_sensors();
});
@@ -137,6 +152,25 @@ function phantom_domain_buttons(enabled) {
}
}
+function get_available_sensors(query, callback) {
+ var success = function(response) {
+ try {
+ g_available_sensors = JSON.parse(response);
+ }
+ catch(e) {
+ console.log("problem parsing: " + e);
+ }
+ }
+
+ var failure = function(response) {
+ console.log("Failure getting sensors");
+ }
+
+ var url = make_url('api/sensors/load')
+ ajaxCallREST(url, success, failure);
+}
+
+
function phantom_domain_details_buttons(enabled) {
if (enabled) {
View
1  phantomweb/templates/base.html
@@ -16,6 +16,7 @@
<script src="/static/js/jquery-1.9.0.js"></script>
<script src="/static/js/jquery-ui-1.10.0.custom.js"></script>
<script src="/static/js/bootstrap.js"></script>
+ <script src="/static/js/bootstrap-hacks.js"></script>
{% block headscripts %}{% endblock %}
View
2  phantomweb/urls.py
@@ -34,6 +34,8 @@
url(r'^phantom/api/launchconfig/save$', 'phantomweb.views.django_lc_save'),
url(r'^phantom/api/launchconfig/delete$', 'phantomweb.views.django_lc_delete'),
+ url(r'^phantom/api/sensors/load$', 'phantomweb.views.django_sensors_load'),
+
url(r'^phantom/domain$', 'phantomweb.views.django_domain_html'),
url(r'^phantom/api/domain/load$', 'phantomweb.views.django_domain_load'),
url(r'^phantom/api/domain/start$', 'phantomweb.views.django_domain_start'),
View
12 phantomweb/views.py
@@ -7,7 +7,7 @@
from django.contrib.auth.models import User
from phantomweb.phantom_web_exceptions import PhantomWebException, PhantomRedirectException
from phantomweb.util import PhantomWebDecorator, get_user_object, LogEntryDecorator
-from phantomweb.workload import terminate_iaas_instance, phantom_lc_load, phantom_sites_add, phantom_sites_delete, phantom_sites_load, phantom_lc_delete, phantom_lc_save, phantom_domain_load, phantom_domain_terminate, phantom_domain_resize, phantom_domain_start, phantom_domain_details, phantom_instance_terminate
+from phantomweb.workload import terminate_iaas_instance, phantom_lc_load, phantom_sites_add, phantom_sites_delete, phantom_sites_load, phantom_lc_delete, phantom_lc_save, phantom_domain_load, phantom_domain_terminate, phantom_domain_resize, phantom_domain_start, phantom_domain_details, phantom_instance_terminate, phantom_sensors_load
from django.contrib import admin
@@ -37,6 +37,16 @@ def django_domain_html(request):
return HttpResponseRedirect(ex.redir)
return HttpResponse(t.render(c))
+@LogEntryDecorator
+@login_required
+def django_sensors_load(request):
+ user_obj = get_user_object(request.user.username)
+ try:
+ response_dict = phantom_sensors_load(request.GET, user_obj)
+ h = HttpResponse(simplejson.dumps(response_dict), mimetype='application/javascript')
+ finally:
+ user_obj.close()
+ return h
@LogEntryDecorator
@login_required
View
12 phantomweb/workload.py
@@ -1,8 +1,9 @@
-import hashlib
import boto
from boto.ec2.autoscale import Tag
from boto.exception import EC2ResponseError
from boto.regioninfo import RegionInfo
+
+import json
import logging
import urlparse
import boto.ec2.autoscale
@@ -11,6 +12,7 @@
from phantomweb.util import PhantomWebDecorator, LogEntryDecorator
from phantomsql import phantom_get_default_key_name
+
import logging # import the required logging module
g_general_log = logging.getLogger('phantomweb.general')
@@ -25,6 +27,8 @@
PHANTOM_REGION = 'phantom'
+OPENTSDB_METRICS = ["df.1kblocks.free","df.1kblocks.total","df.1kblocks.used","df.inodes.free","df.inodes.total","df.inodes.used","iostat.part.ios_in_progress","iostat.part.msec_read","iostat.part.msec_total","iostat.part.msec_weighted_total","iostat.part.msec_write","iostat.part.read_merged","iostat.part.read_requests","iostat.part.read_sectors","iostat.part.write_merged","iostat.part.write_requests","iostat.part.write_sectors","net.sockstat.ipfragqueues","net.sockstat.memory","net.sockstat.num_orphans","net.sockstat.num_sockets","net.sockstat.num_timewait","net.sockstat.sockets_inuse","net.stat.tcp.abort","net.stat.tcp.abort.failed","net.stat.tcp.congestion.recovery","net.stat.tcp.delayedack","net.stat.tcp.failed_accept","net.stat.tcp.memory.pressure","net.stat.tcp.memory.prune","net.stat.tcp.packetloss.recovery","net.stat.tcp.reording","net.stat.tcp.syncookies","proc.kernel.entropy_avail","proc.loadavg.15min","proc.loadavg.1min","proc.loadavg.5min","proc.loadavg.runnable","proc.loadavg.total_threads","proc.meminfo.active","proc.meminfo.anonpages","proc.meminfo.bounce","proc.meminfo.buffers","proc.meminfo.cached","proc.meminfo.commitlimit","proc.meminfo.committed_as","proc.meminfo.dirty","proc.meminfo.highfree","proc.meminfo.hightotal","proc.meminfo.inactive","proc.meminfo.lowfree","proc.meminfo.lowtotal","proc.meminfo.mapped","proc.meminfo.memfree","proc.meminfo.memtotal","proc.meminfo.nfs_unstable","proc.meminfo.pagetables","proc.meminfo.slab","tcollector.collector.lines_invalid","tcollector.collector.lines_received","tcollector.collector.lines_sent","tcollector.reader.lines_collected","tcollector.reader.lines_dropped"]
+
#
# we are only dealing with launch configurations that were made with the web app
#
@@ -203,7 +207,6 @@ def sensor_tags_from_de_params(phantom_con, domain_name, de_params):
return tags
-
@LogEntryDecorator
def _start_domain(phantom_con, domain_name, lc_name, de_name, de_params, host_list_str, a_cloudname):
@@ -780,6 +783,11 @@ def phantom_instance_terminate(request_params, userobj):
@PhantomWebDecorator
@LogEntryDecorator
+def phantom_sensors_load(request_params, userobj):
+ return json.dumps(OPENTSDB_METRICS)
+
+@PhantomWebDecorator
+@LogEntryDecorator
def phantom_domain_details(request_params, userobj):
params = ['name',]
for p in params:
Please sign in to comment.
Something went wrong with that request. Please try again.