>From 993ec482a64699a14d24a71267f268fc79d0f0e8 Mon Sep 17 00:00:00 2001 From: Tomas Sedovic Date: Tue, 16 Aug 2011 20:10:19 +0200 Subject: [PATCH conductor 2/2] Live status update for instances under deployments https://www.aeolusproject.org/redmine/issues/2025 This periodically refreshes the instance details on the deployments#show page. --- aeolus-conductor.spec.in | 3 +- src/app/controllers/instances_controller.rb | 4 +- src/app/models/instance.rb | 8 +++ src/app/views/deployments/_pretty_view_show.haml | 11 ++++- src/app/views/instances/_list.haml | 13 +++++ src/app/views/layouts/application.haml | 7 +++ src/config/routes.rb | 1 + src/public/javascripts/application.js | 54 ++++++++++++++++++++- src/public/javascripts/backbone/models.js | 25 ++++++++++ src/public/javascripts/backbone/routers.js | 46 ++++++++++++++++++ src/public/javascripts/backbone/views.js | 33 +++++++++++++ 11 files changed, 199 insertions(+), 6 deletions(-) create mode 100644 src/public/javascripts/backbone/models.js create mode 100644 src/public/javascripts/backbone/routers.js create mode 100644 src/public/javascripts/backbone/views.js diff --git a/aeolus-conductor.spec.in b/aeolus-conductor.spec.in index 84e354a..c10d307 100644 --- a/aeolus-conductor.spec.in +++ b/aeolus-conductor.spec.in @@ -140,7 +140,8 @@ ico="public" jpg="public/images public/stylesheets/images" js="public/javascripts public/javascripts/jquery-svg \ public/javascripts/jquery.ui-1.8.1 \ - public/javascripts/jquery.ui-1.8.1/ui/minified" + public/javascripts/jquery.ui-1.8.1/ui/minified \ + public/javascripts/backbone" json="spec/fixtures" key="features/upload_files" opts="spec" diff --git a/src/app/controllers/instances_controller.rb b/src/app/controllers/instances_controller.rb index a315db9..617d309 100644 --- a/src/app/controllers/instances_controller.rb +++ b/src/app/controllers/instances_controller.rb @@ -230,8 +230,10 @@ class InstancesController < ApplicationController end def load_instances + conditions = { :pool_id => @pools } + conditions[:deployment_id] = params[:deployment_id] unless params[:deployment_id].blank? @instances = Instance.all(:include => [:owner], - :conditions => {:pool_id => @pools}, + :conditions => conditions, :order => (params[:order_field] || 'name') +' '+ (params[:order_dir] || 'asc') ) end diff --git a/src/app/models/instance.rb b/src/app/models/instance.rb index 6199c15..b220e29 100644 --- a/src/app/models/instance.rb +++ b/src/app/models/instance.rb @@ -355,6 +355,14 @@ class Instance < ActiveRecord::Base {:include => :hardware_profile} } + def as_json(options={}) + super(options).merge({ + :owner => owner.name, + :provider => provider_account ? provider_account.provider.name : '', + :href => Rails.application.routes.url_helpers.instance_path(id), + }) + end + private def key_name diff --git a/src/app/views/deployments/_pretty_view_show.haml b/src/app/views/deployments/_pretty_view_show.haml index 853c8b9..c849622 100644 --- a/src/app/views/deployments/_pretty_view_show.haml +++ b/src/app/views/deployments/_pretty_view_show.haml @@ -18,4 +18,13 @@ :javascript Conductor.setupPrettyFilterURL( '#{url_for(:action => :show, :details_tab => 'instances', :view => 'filter' )}', - '#{url_for(:action => :show, :details_tab => 'instances', :view => 'pretty' )}'); \ No newline at end of file + '#{url_for(:action => :show, :details_tab => 'instances', :view => 'pretty' )}'); + +%script#instanceTemplate{ :type => 'text/x-jquery-tmpl' } + :plain +
  • ${name} + +
  • diff --git a/src/app/views/instances/_list.haml b/src/app/views/instances/_list.haml index 7092b88..f9545e4 100644 --- a/src/app/views/instances/_list.haml +++ b/src/app/views/instances/_list.haml @@ -26,3 +26,16 @@ Conductor.setupPrettyFilterURL( '#{url_for(:action => :show, :details_tab => 'instances', :view => 'filter' )}', '#{url_for(:action => :show, :details_tab => 'instances', :view => 'pretty' )}'); + +%script#instanceTemplate{ :type => 'text/x-jquery-tmpl' } + :plain + + + + ${name} + ${public_addresses} + ${state} + ${provider} + ${owner} + diff --git a/src/app/views/layouts/application.haml b/src/app/views/layouts/application.haml index 31dcf1f..7fa81e7 100644 --- a/src/app/views/layouts/application.haml +++ b/src/app/views/layouts/application.haml @@ -18,8 +18,15 @@ = javascript_include_tag "jquery.tmpl.min.js" = javascript_include_tag "underscore-min.js" = javascript_include_tag "backbone-min.js" + :javascript + window.Conductor = {} + window.Conductor.PATH_PREFIX = "#{root_path}" = javascript_include_tag "application.js" = javascript_include_tag "yetii-min.js" + + = javascript_include_tag "backbone/models.js" + = javascript_include_tag "backbone/views.js" + = javascript_include_tag "backbone/routers.js" = yield :scripts %body #primary-container diff --git a/src/config/routes.rb b/src/config/routes.rb index 1d6857e..aa0668b 100644 --- a/src/config/routes.rb +++ b/src/config/routes.rb @@ -86,6 +86,7 @@ Conductor::Application.routes.draw do get 'launch_new' get 'check_name' end + resources :instances end resources :instances do diff --git a/src/public/javascripts/application.js b/src/public/javascripts/application.js index 1265923..11e2413 100644 --- a/src/public/javascripts/application.js +++ b/src/public/javascripts/application.js @@ -1,13 +1,14 @@ // Place your application-specific JavaScript functions and classes here // This file is automatically included by javascript_include_tag :defaults -var Conductor = { + +$.extend(Conductor, { extendTable: function(options) { var table = $(options.id); var wrapper = $(".wrapper", table); // show searchfield (it's disabled by default because with JS off, - // when enter is pressed in search field, form is submited + // when enter is pressed in search field, form is submited) $(".search_field", table).css('display', 'block'); // make column head links ajax @@ -172,6 +173,52 @@ var Conductor = { }); }, + prefixedPath: function(path) { + var prefix = this.PATH_PREFIX; + if(path.length === 0) return prefix; + if(prefix.length === 0) return path; + + if(prefix[prefix.length-1] !== '/') prefix += '/'; + if(path[0] === '/') path = path.slice(1); + + return prefix + path; + }, + + AJAX_REFRESH_INTERVAL: 5000, + + initializeBackbone: function() { + for(router in Conductor.Routers) { + if(Conductor.Routers.hasOwnProperty(router) && + router[0] === router.toUpperCase()[0]) { + new Conductor.Routers[router](); + } + } + Backbone.history.start({pushState: true, root: Conductor.PATH_PREFIX}) + }, + + idFromURLFragment: function(urlFragment) { + return urlFragment.split('?')[0]; + }, + + saveCheckboxes: function(checkboxSelector, $scope) { + if(!$scope) $scope = $; + + var result = []; + $scope.find(checkboxSelector + ':checked').each(function() { + result.push(this.value) + }); + return result; + }, + + restoreCheckboxes: function(checkedIds, checkboxSelector, $scope) { + if(!$scope) $scope = $; + + $.each(checkedIds, function(index, id) { + var $checkbox = $scope.find(checkboxSelector + '[value="' + id + '"]'); + $checkbox.prop('checked', true); + }); + }, + enhanceUserMenu: function() { var $userDropdown = $('#system a.user-dropdown'); if($userDropdown.length == 0) return; @@ -185,7 +232,7 @@ var Conductor = { }); }, -}; +}); /* custom methods */ (function($){ @@ -280,4 +327,5 @@ $(document).ready(function () { Conductor.closeNotification(); Conductor.enhanceUserMenu(); Conductor.tabAjaxRequest(); + Conductor.initializeBackbone(); }); diff --git a/src/public/javascripts/backbone/models.js b/src/public/javascripts/backbone/models.js new file mode 100644 index 0000000..1d48502 --- /dev/null +++ b/src/public/javascripts/backbone/models.js @@ -0,0 +1,25 @@ +Conductor.Models = Conductor.Models || {} + + +Conductor.Models.Pool = Backbone.Model.extend({ + urlRoot: Conductor.prefixedPath('/pools'), +}); + + +Conductor.Models.Instance = Backbone.Model.extend({ + urlRoot: Conductor.prefixedPath('/instances'), +}); + +Conductor.Models.Instances = Backbone.Collection.extend({ + model: Backbone.Model.Instance, +}); + +Conductor.Models.Deployment = Backbone.Model.extend({ + urlRoot: Conductor.prefixedPath('/deployments'), + + initialize: function() { + this.instances = new Conductor.Models.Instances(); + this.instances.url = this.urlRoot + '/' + this.id + '/instances'; + }, + +}); diff --git a/src/public/javascripts/backbone/routers.js b/src/public/javascripts/backbone/routers.js new file mode 100644 index 0000000..ead13ab --- /dev/null +++ b/src/public/javascripts/backbone/routers.js @@ -0,0 +1,46 @@ +Conductor.Routers = Conductor.Routers || {} + + +Conductor.Routers.Pools = Backbone.Router.extend({ + routes: { + '': 'index', + 'pools': 'index', + 'pools/:id': 'show', + }, + + index: function() { + }, + + show: function(id) { + var pool = new Conductor.Models.Pool({ id: id }); + var view = new Conductor.Views.PoolsShow({ model: pool }); + pool.bind('change', function() { view.render() }); + + setInterval(function() { pool.fetch() }, Conductor.AJAX_REFRESH_INTERVAL); + }, +}); + + +Conductor.Routers.Deployments = Backbone.Router.extend({ + routes: { + 'deployments': 'index', + 'deployments/:id': 'show', + }, + + index: function() { + }, + + show: function(id) { + id = Conductor.idFromURLFragment(id); + var deployment = new Conductor.Models.Deployment({ id: id }); + var view = new Conductor.Views.DeploymentsShow({ model: deployment, + collection: deployment.instances }); + deployment.bind('change', function() { view.render() }); + + setInterval(function() { + deployment.instances.fetch({success: function(instances) { + deployment.change(); + } }) + }, Conductor.AJAX_REFRESH_INTERVAL); + }, +}); diff --git a/src/public/javascripts/backbone/views.js b/src/public/javascripts/backbone/views.js new file mode 100644 index 0000000..a96da46 --- /dev/null +++ b/src/public/javascripts/backbone/views.js @@ -0,0 +1,33 @@ +Conductor.Views = Conductor.Views || {} + + +Conductor.Views.PoolsShow = Backbone.View.extend({ + el: '#content', + + render: function() { + this.$('h1.pools').text(this.model.get('name') + ' Pool'); + return this; + }, +}); + + +Conductor.Views.DeploymentsShow = Backbone.View.extend({ + + el: '#content', + + render: function() { + var $instances = this.$('ul.instances_list'); + if($instances.length === 0) { + $instances = this.$('table.checkbox_table > tbody'); + } + if($instances.length === 0) return; + + var checked_instances = Conductor.saveCheckboxes('td :checkbox', $instances); + $instances.empty(); + $('#instanceTemplate').tmpl(this.collection.toJSON()).appendTo($instances); + Conductor.restoreCheckboxes(checked_instances, 'td :checkbox', $instances); + $instances.find('tr:even').addClass('nostripe'); + $instances.find('tr:odd').addClass('stripe'); + }, + +}); -- 1.7.6