Scott Seago
2008-May-29 19:54 UTC
[Ovirt-devel] [PATCH] refactored popups and grids to pull more into the layouts, and static js functions into ovirt.js.
Also, the children and all_children methods on Pool now accept find parameters. Signed-off-by: Scott Seago <sseago at redhat.com> --- wui/src/app/controllers/application.rb | 24 +- wui/src/app/controllers/hardware_controller.rb | 46 ++- wui/src/app/controllers/host_controller.rb | 3 - wui/src/app/controllers/storage_controller.rb | 2 +- wui/src/app/models/pool.rb | 11 +- wui/src/app/views/dashboard/index.html.erb | 50 -- wui/src/app/views/hardware/move.rhtml | 13 +- wui/src/app/views/hardware/new.html.erb | 38 +- wui/src/app/views/hardware/show_hosts.rhtml | 1 + wui/src/app/views/hardware/show_storage.rhtml | 1 + wui/src/app/views/host/_grid.rhtml | 21 +- wui/src/app/views/host/addhost.html.erb | 71 +-- wui/src/app/views/layouts/popup.rhtml | 7 +- wui/src/app/views/layouts/redux.rhtml | 64 +-- wui/src/app/views/permission/new.rhtml | 26 +- wui/src/app/views/resources/new.rhtml | 27 +- wui/src/app/views/resources/show.rhtml | 78 --- wui/src/app/views/storage/_grid.rhtml | 11 +- wui/src/app/views/storage/addstorage.html.erb | 48 +- wui/src/app/views/storage/new.rhtml | 42 +- wui/src/app/views/vm/new.rhtml | 28 +- wui/src/public/javascripts/ovirt.js | 138 +++++ .../betternestedset/lib/#better_nested_set.rb# | 583 -------------------- .../betternestedset/lib/better_nested_set.rb | 33 +- 24 files changed, 350 insertions(+), 1016 deletions(-) create mode 100644 wui/src/public/javascripts/ovirt.js delete mode 100644 wui/src/vendor/plugins/betternestedset/lib/#better_nested_set.rb# diff --git a/wui/src/app/controllers/application.rb b/wui/src/app/controllers/application.rb index 5f20be3..3ca5125 100644 --- a/wui/src/app/controllers/application.rb +++ b/wui/src/app/controllers/application.rb @@ -84,18 +84,30 @@ class ApplicationController < ActionController::Base end end end - def json_list(full_items, attributes) + + # don't define find_opts for array inputs + def json_list(full_items, attributes, arg_list=[], find_opts={}) page = params[:page].to_i - item_list = full_items.paginate(:page => page, - :order => "#{params[:sortname]} #{params[:sortorder]}", - :per_page => params[:rp]) + paginate_opts = {:page => page, + :order => "#{params[:sortname]} #{params[:sortorder]}", + :per_page => params[:rp]} + arg_list << find_opts.merge(paginate_opts) + item_list = full_items.paginate(*arg_list) json_hash = {} json_hash[:page] = page - json_hash[:total] = full_items.size + json_hash[:total] = item_list.total_entries json_hash[:rows] = item_list.collect do |item| item_hash = {} item_hash[:id] = item.id - item_hash[:cell] = attributes.collect {|attr| item.send(attr)} + item_hash[:cell] = attributes.collect do |attr| + if attr.is_a? Array + value = item + attr.each { |attr_item| value = value.send(attr_item)} + value + else + item.send(attr) + end + end item_hash end render :json => json_hash.to_json diff --git a/wui/src/app/controllers/hardware_controller.rb b/wui/src/app/controllers/hardware_controller.rb index ad857f1..0f0c904 100644 --- a/wui/src/app/controllers/hardware_controller.rb +++ b/wui/src/app/controllers/hardware_controller.rb @@ -229,17 +229,27 @@ class HardwareController < ApplicationController if params[:id] pre_json hosts = @pool.hosts + find_opts = {} + include_pool = false else - # FIXME: no permissions checks here yet, no filtering of current pool yet - hosts = Host.find(:all) + # FIXME: no permissions or usage checks here yet + # filtering on which pool to exclude + id = params[:exclude_id] + hosts = Host + find_opts = {:include => :hardware_pool, + :conditions => ["pools.id != ?", id]} + include_pool = true end - json_list(hosts, - [:id, :hostname, :uuid, :hypervisor_type, :num_cpus, :cpu_speed, :arch, :memory_in_mb, :is_disabled_str]) + attr_list = [:id, :hostname, :uuid, :hypervisor_type, :num_cpus, :cpu_speed, :arch, :memory_in_mb, :is_disabled_str] + attr_list.insert(2, [:hardware_pool, :name]) if include_pool + json_list(hosts, attr_list, [:all], find_opts) end def vm_pools_json - json_list(@pool.sub_vm_resource_pools, - [:id, :name]) + json_list(Pool, + [:id, :name], + [@pool, :children], + {:finder => 'call_finder', :conditions => ["type = 'VmResourcePool'"]}) end def users_json @@ -251,12 +261,20 @@ class HardwareController < ApplicationController if params[:id] pre_json storage_pools = @pool.storage_pools + find_opts = {} + include_pool = false else - # FIXME: no permissions checks here yet, no filtering of current pool yet - storage_pools = StoragePool.find(:all) + # FIXME: no permissions or usage checks here yet + # filtering on which pool to exclude + id = params[:exclude_id] + storage_pools = StoragePool + find_opts = {:include => :hardware_pool, + :conditions => ["pools.id != ?", id]} + include_pool = true end - json_list(storage_pools, - [:id, :display_name, :ip_addr, :get_type_label]) + attr_list = [:id, :display_name, :ip_addr, :get_type_label] + attr_list.insert(2, [:hardware_pool, :name]) if include_pool + json_list(storage_pools, attr_list, [:all], find_opts) end def storage_volumes_json @@ -283,8 +301,10 @@ class HardwareController < ApplicationController resource_ids = resource_ids_str.split(",").collect {|x| x.to_i} if resource_ids_str begin @pool.create_with_resources(@parent, resource_type, resource_ids) - render :json => { :object => "pool", :success => true, + reply = { :object => "pool", :success => true, :alert => "Hardware Pool was successfully created." } + reply[:resource_type] = resource_type if resource_type + render :json => reply rescue render :json => { :object => "pool", :success => false, :errors => @pool.errors.localize_error_messages.to_a } @@ -308,7 +328,7 @@ class HardwareController < ApplicationController # in addition to the current pool (which is checked). We also need to fail # for hosts that aren't currently empty def add_hosts - host_ids_str = params[:host_ids] + host_ids_str = params[:resource_ids] host_ids = host_ids_str.split(",").collect {|x| x.to_i} begin @@ -347,7 +367,7 @@ class HardwareController < ApplicationController # in addition to the current pool (which is checked). We also need to fail # for storage that aren't currently empty def add_storage - storage_pool_ids_str = params[:storage_pool_ids] + storage_pool_ids_str = params[:resource_ids] storage_pool_ids = storage_pool_ids_str.split(",").collect {|x| x.to_i} begin diff --git a/wui/src/app/controllers/host_controller.rb b/wui/src/app/controllers/host_controller.rb index f89997f..623cc0c 100644 --- a/wui/src/app/controllers/host_controller.rb +++ b/wui/src/app/controllers/host_controller.rb @@ -58,9 +58,6 @@ class HostController < ApplicationController def addhost @hardware_pool = Pool.find(params[:hardware_pool_id]) - @unassigned = Pool.root.hosts.size - # FIXME: @assigned should match the updated assigned hosts query when that's done - @assigned = Host.find(:all).size render :layout => 'popup' end diff --git a/wui/src/app/controllers/storage_controller.rb b/wui/src/app/controllers/storage_controller.rb index 1c5b392..c8831e6 100644 --- a/wui/src/app/controllers/storage_controller.rb +++ b/wui/src/app/controllers/storage_controller.rb @@ -88,7 +88,7 @@ class StorageController < ApplicationController def new2 @storage_pools = @storage_pool.hardware_pool.storage_volumes - render :layout => 'popup' + render :layout => false end def insert_refresh_task diff --git a/wui/src/app/models/pool.rb b/wui/src/app/models/pool.rb index 490c5e6..5390beb 100644 --- a/wui/src/app/models/pool.rb +++ b/wui/src/app/models/pool.rb @@ -69,10 +69,10 @@ class Pool < ActiveRecord::Base end def sub_hardware_pools - Pool.select_hardware_pools(children) + children({:conditions => "type='HardwarePool'"}) end def sub_vm_resource_pools - Pool.select_vm_pools(children) + children({:conditions => "type='VmResourcePool'"}) end def self_and_like_siblings self_and_siblings.select {|pool| pool[:type] == self.class.name} @@ -156,7 +156,12 @@ class Pool < ActiveRecord::Base hash end end - + + def self.call_finder(*args) + obj = args.shift + method = args.shift + obj.send(method, *args) + end protected def traverse_parents diff --git a/wui/src/app/views/dashboard/index.html.erb b/wui/src/app/views/dashboard/index.html.erb index 8715261..92cb4da 100644 --- a/wui/src/app/views/dashboard/index.html.erb +++ b/wui/src/app/views/dashboard/index.html.erb @@ -1,57 +1,7 @@ - <div id="data"> - <div class="inside"> - - <div id="dataTableWrapper"> - - <%= render :partial => "/layouts/quick_stats", :locals => { :hpool => @default_pool, :vms => @vms } %> - - <div class="dataTable"> - - <%= render :partial => "/layouts/hardware_pool_type_list", :locals => { :hardware_pools => @hardware_pools } %> - - </div> <!-- end #dataTable --> - - <div class="data-section"> - <div class="data-section-header"><strong>Available Hosts (<%= @available_hosts.size %>)</strong></div> - <div class="data-section-stats">Statistics Data</div> - <div class="data-section-table"> - <div class="inside"> - <%= render :partial => "/host/list", :locals => { :hosts => @available_hosts } %> - </div> - </div> - </div> - - <div class="data-section"> - <div class="data-section-header"><strong>Available Storage (<%= @available_storage_volumes.size %>)</strong></div> - <div class="data-section-header"><strong>Storage Pools (<%= @storage_pools.size %>)</strong></div> - <div class="data-section-stats">Statistics Data</div> - <div class="data-section-table"> - <div class="inside"> - <%= render :partial => "/storage/list_volumes", :locals => { :storage_volumes => @available_storage_volumes } %> - </div> - </div> - </div> - - </div> <!-- end #dataTableWrapper --> - - </div> <!-- end #data.inside --> - </div> <!-- end #data --> - - </td> <td id="right"> - <div class="heading"> </div> <div class="tools"> <h3>Actions</h3> - <%if @can_modify -%> - <div class="actions"> -<!-- - <%= link_to 'Add Storage', { :controller => 'storage', :action => 'new', :hardware_pool_id => @default_pool}, { :class => "create" } %> - <hr/> ---> - <%= link_to 'Create New Pool', { :controller => "hardware", :action => 'new', :parent_id => @default_pool}, { :class => "create" } %> - </div> - <% end -%> <div class="actions"> <div><%= link_to_if @can_set_perms, 'User Permissions', { :controller => 'permission', :action => 'new', :pool_id => @default_pool }, { :class => "edit" } %></div> diff --git a/wui/src/app/views/hardware/move.rhtml b/wui/src/app/views/hardware/move.rhtml index 190fc03..f5824c6 100644 --- a/wui/src/app/views/hardware/move.rhtml +++ b/wui/src/app/views/hardware/move.rhtml @@ -1,3 +1,10 @@ +<%- content_for :title do -%> + Move <%= @resource_type.capitalize %> +<%- end -%> +<%- content_for :description do -%> + Select an existing hardware pool or create a new pool for selected <%= @resource_type %>. +<%- end -%> + <script type="text/javascript"> $(document).ready(function(){ $("#move_tree").treeview({ @@ -34,12 +41,7 @@ $('#move_to_new_pool').click(function(){ }); </script> -<div id="window"> -<div class="dialog_title_small"> - <div class="header">Move <%= @resource_type.capitalize %></div> - <div>Select an existing hardware pool or create a new pool for selected <%= @resource_type %>. </div> -</div> <div class="dialog_tree"> <ul id="move_tree" class="filetree treeview-famfamfam treeview"></ul> @@ -63,4 +65,3 @@ $('#move_to_new_pool').click(function(){ <div class="button_right_blue"></div> </div> </div> -</div> diff --git a/wui/src/app/views/hardware/new.html.erb b/wui/src/app/views/hardware/new.html.erb index a533871..f594009 100644 --- a/wui/src/app/views/hardware/new.html.erb +++ b/wui/src/app/views/hardware/new.html.erb @@ -1,40 +1,30 @@ -<div class="dialog_title_small"> - <div class="header">Add New Hardware Pool</div> - <div>Add a new Hardware Pool to the <%= @parent.name %> pool. - <%= "Checked #{@resource_type} will be added to the new pool." if @resource_type %> - </div> -</div> +<%- content_for :title do -%> + <%= _("Add New Hardware Pool") %> +<%- end -%> +<%- content_for :description do -%> + Add a new Hardware Pool to the <%= @parent.name %> pool. + <%= "Checked #{@resource_type} will be added to the new pool." if @resource_type %> +<%- end -%> <form method="POST" action="<%= url_for :action => 'create' %>" id="pool_form" > -<div class="dialog_form"> - <%= render :partial => 'form' %> -</div> -<%= popup_footer("$('#pool_form').submit()", "Create Hardware Pool") %> + <div class="dialog_form"> + <%= render :partial => 'form' %> + </div> + <%= popup_footer("$('#pool_form').submit()", "Create Hardware Pool") %> </form> - <script type="text/javascript"> + +<script type="text/javascript"> $(function() { var hwpooloptions = { target: '<%= url_for :action => 'create' %>', // target element to update dataType: 'json', - success: afterNewHwPool // post-submit callback + success: afterHwPool // post-submit callback }; // bind form using 'ajaxForm' $('#pool_form').ajaxForm(hwpooloptions); }); -function afterNewHwPool(response, status){ - ajax_validation(response, status) - if (response.success) { - jQuery(document).trigger('close.facebox'); - // FIXME do we need to reload the tree here - <%= "$('##{@resource_type}_grid').flexReload()" if @resource_type %> - //$("#vmpools_grid").flexReload() - } -} </script> -<%- content_for :title do -%> -<%= _("New Hardware Pool") %> -<%- end -%> diff --git a/wui/src/app/views/hardware/show_hosts.rhtml b/wui/src/app/views/hardware/show_hosts.rhtml index ae0e8af..bcbc2b8 100644 --- a/wui/src/app/views/hardware/show_hosts.rhtml +++ b/wui/src/app/views/hardware/show_hosts.rhtml @@ -51,6 +51,7 @@ <div class="data_section"> <%= render :partial => "/host/grid", :locals => { :table_id => "hosts_grid", :hwpool_id => @pool.id, + :exclude_id => nil, :on_select => "hosts_select" } %> </div> <div class="selection_detail" id="hosts_selection"> diff --git a/wui/src/app/views/hardware/show_storage.rhtml b/wui/src/app/views/hardware/show_storage.rhtml index 3d547db..f0b14bc 100644 --- a/wui/src/app/views/hardware/show_storage.rhtml +++ b/wui/src/app/views/hardware/show_storage.rhtml @@ -68,6 +68,7 @@ <div class="data_section"> <%= render :partial => "/storage/grid", :locals => { :table_id => "storage_grid", :hwpool_id => @pool.id, + :exclude_id => nil, :on_select => "storage_select" } %> </div> diff --git a/wui/src/app/views/host/_grid.rhtml b/wui/src/app/views/host/_grid.rhtml index 338bc78..25bdfd7 100644 --- a/wui/src/app/views/host/_grid.rhtml +++ b/wui/src/app/views/host/_grid.rhtml @@ -7,18 +7,19 @@ $("#<%= table_id %>").flexigrid ( { - url: '<%= url_for :controller => "hardware", :action => "hosts_json", :id => hwpool_id %>', + url: '<%= url_for :controller => "hardware", :action => "hosts_json", :id => hwpool_id, :exclude_id => exclude_id %>', dataType: 'json', colModel : [ - {display: '', name : 'id', width : 20, sortable : false, align: 'left', process: <%= table_id %>checkbox}, - {display: 'Hostname', name : 'hostname', width : 60, sortable : true, align: 'left'}, - {display: 'UUID', name : 'uuid', width : 180, sortable : true, align: 'left'}, - {display: 'Hypervisor Type', name : 'hypervisor_type', width : 80, sortable : true, align: 'left'}, - {display: 'CPUs', name : 'num_cpus', width : 40, sortable : true, align: 'left'}, - {display: 'Speed (MHz)', name : 'cpu_speed', width : 60, sortable : true, align: 'right'}, - {display: 'Arch', name : 'arch', width : 50, sortable : true, align: 'right'}, - {display: 'RAM (MB)', name : 'memory', width : 60, sortable : true, align: 'right'}, - {display: 'Disabled', name : 'is_disabled', width : 50, sortable : true, align: 'right'} + {display: '', width : 20, align: 'left', process: <%= table_id %>checkbox}, + {display: 'Hostname', name : 'hostname', width : 60, align: 'left'}, + <%= "{display: 'Hardware Pool', name : 'pools.name', width : 100, align: 'left'}," if exclude_id %> + {display: 'UUID', name : 'uuid', width : 180, align: 'left'}, + {display: 'Hypervisor Type', name : 'hypervisor_type', width : 80, align: 'left'}, + {display: 'CPUs', name : 'num_cpus', width : 40, align: 'left'}, + {display: 'Speed (MHz)', name : 'cpu_speed', width : 60, align: 'right'}, + {display: 'Arch', name : 'arch', width : 50, align: 'right'}, + {display: 'RAM (MB)', name : 'memory', width : 60, align: 'right'}, + {display: 'Disabled', name : 'is_disabled', width : 50, align: 'right'} ], sortname: "hostname", sortorder: "asc", diff --git a/wui/src/app/views/host/addhost.html.erb b/wui/src/app/views/host/addhost.html.erb index 69e0e66..bc3c797 100644 --- a/wui/src/app/views/host/addhost.html.erb +++ b/wui/src/app/views/host/addhost.html.erb @@ -1,55 +1,18 @@ -<div class="dialog_title"> - <div class="header">Add Host</div> - <div>Select hosts from the list below to add to the <%= @hardware_pool.name %> hardware pool. <a href="#">Learn how to manage hosts</a></div> +<%- content_for :title do -%> + <%= _("Add Host") %> +<%- end -%> +<%- content_for :description do -%> + Select hosts from the list below to add to the <%= @hardware_pool.name %> hardware pool. <a href="#">Learn how to manage hosts</a> +<%- end -%> + +<div class="panel_header"></div> +<div class="dialog_body"> + <%= render :partial => "/host/grid", :locals => { :table_id => "addhosts_grid", + :hwpool_id => nil, :exclude_id => @hardware_pool.id, + :on_select => "false" } %> </div> -<div id="toolbar_nav"> -<ul> - <li class="current" id="unassigned_tab"><a href="#" onClick = "show_unassigned()"><img style="vertical-align:middle;" src="" /> Unassigned Hosts (<%= @unassigned %>)</a></li> - <li id="assigned_tab"><a href="#" onClick = "show_assigned()"><img style="vertical-align:middle;" src="" /> Assigned Hosts (<%= @assigned %>)</a></li> -</ul> -</div> -<script type="text/javascript"> - function show_unassigned() - { - $("#assigned_tab").removeClass('current'); - $("#unassigned_tab").addClass('current'); - $("#addhosts_assigned_grid_div").hide() - $("#addhosts_unassigned_grid_div").show() - } - function show_assigned() - { - $("#assigned_tab").addClass('current'); - $("#unassigned_tab").removeClass('current'); - $("#addhosts_unassigned_grid_div").hide() - $("#addhosts_assigned_grid_div").show() - } - function add_hosts() - { - var assigned_selected= get_selected_checkboxes(document.addhosts_assigned_grid_form) - var unassigned_selected= get_selected_checkboxes(document.addhosts_unassigned_grid_form) - var hosts = Array.concat(assigned_selected,unassigned_selected) - if (validate_selected(hosts, "host")) { - $.post('<%= url_for :controller => "hardware", :action => "add_hosts", :id => @hardware_pool %>', - { host_ids: hosts.toString() }, - function(data,status){ - jQuery(document).trigger('close.facebox'); - $("#hosts_grid").flexReload() - if (data.alert) { - alert(data.alert); - } - }, 'json'); - } - } -</script> - <div class="panel_header"></div> - <div class="dialog_body"> - <%= render :partial => "/host/grid", :locals => { :table_id => "addhosts_unassigned_grid", - :hwpool_id => Pool.root.id, :on_select => "false" } %> - <%= render :partial => "/host/grid", :locals => { :table_id => "addhosts_assigned_grid", - :hwpool_id => nil, :on_select => "false" } %> - </div> -</div> -<script type="text/javascript"> - $("#addhosts_assigned_grid_div").hide() -</script> -<%= popup_footer("add_hosts()", "Add Hosts") %> + +<%= popup_footer("add_hosts('#{url_for :controller => "hardware", + :action => "add_hosts", + :id => @hardware_pool}')", + "Add Hosts") %> diff --git a/wui/src/app/views/layouts/popup.rhtml b/wui/src/app/views/layouts/popup.rhtml index 736517c..55dda58 100644 --- a/wui/src/app/views/layouts/popup.rhtml +++ b/wui/src/app/views/layouts/popup.rhtml @@ -1,2 +1,7 @@ -<%# currently nothing for popups here. %> +<div id="window"> +<div class="dialog_title_small"> + <div class="header"><%= yield :title -%></div> + <div><%= yield :description -%></div> +</div> <%= yield %> +</div> diff --git a/wui/src/app/views/layouts/redux.rhtml b/wui/src/app/views/layouts/redux.rhtml index 72a49f8..63a9a56 100644 --- a/wui/src/app/views/layouts/redux.rhtml +++ b/wui/src/app/views/layouts/redux.rhtml @@ -27,6 +27,9 @@ <%= javascript_include_tag "ui.slider.js" -%> <%= javascript_include_tag "jquery.cookie.js" -%> <%= javascript_include_tag "jquery.form.js" -%> + + <!-- ovirt-specific functions defined here + <%= javascript_include_tag "ovirt.js" -%> <script type="text/javascript"> $(document).ready(function(){ $("#tree").treeview({ @@ -39,67 +42,10 @@ action_type: "hyperlink" }) }); - - function get_selected_checkboxes(obj_form) - { - var selected_array = new Array() - var selected_index = 0 - var checkboxes - if (obj_form.grid_checkbox) { - if (obj_form.grid_checkbox.length == undefined) { - checkboxes = [obj_form.grid_checkbox] - } else { - checkboxes = obj_form.grid_checkbox - } - for(var i=0; i < checkboxes.length; i++){ - if(checkboxes[i].checked) - { - selected_array[selected_index]= checkboxes[i].value - selected_index++ - } - } - } - return selected_array - } - function validate_selected(selected_array, name) - { - if (selected_array.length == 0) { - alert("Please select at least one " + name + " to continue") - return false - } else { - return true - } - } - function ajax_validation(response, status) - { - if (response.object) { - $(".fieldWithErrors").removeClass("fieldWithErrors"); - $("div.errorExplanation").remove(); - if (!response.success) { - for(i=0; i<response.errors.length; i++) { - var element = $("div.form_field:has(#"+response.object + "_" + response.errors[i][0]+")"); - if (element) { - element.addClass("fieldWithErrors"); - for(j=0; j<response.errors[i][1].length; j++) { - element.append('<div class="errorExplanation">'+response.errors[i][1][j]+'</div>'); - } - } - } - } - if (response.alert) { - alert(response.alert) - } - } - } - - </script> - <script type="text/javascript"> - jQuery(document).ready(function($) { + $(document).ready(function(){ $('a[rel*=facebox]').facebox() }) - </script> - <script type="text/javascript"> - jQuery(document).ready(function($) { + $(document).ready(function(){ $('a[rel*=close]').trigger('close.facebox') }) </script> diff --git a/wui/src/app/views/permission/new.rhtml b/wui/src/app/views/permission/new.rhtml index 45dc239..70fb66b 100644 --- a/wui/src/app/views/permission/new.rhtml +++ b/wui/src/app/views/permission/new.rhtml @@ -1,7 +1,9 @@ -<div class="dialog_title_small"> - <div class="header">Add New User</div> - <div>Add a new user to <%= @permission.pool.name %> pool.</div> -</div> +<%- content_for :title do -%> + Add New User +<%- end -%> +<%- content_for :description do -%> + Add a new user to <%= @permission.pool.name %> pool. +<%- end -%> <form method="POST" action="<%= url_for :action => 'create' %>" id="permission_form" > <div class="dialog_form"> @@ -9,28 +11,16 @@ </div> <%= popup_footer("$('#permission_form').submit()", "Create User Permission") %> </form> + <script type="text/javascript"> $(function() { var permissionoptions = { target: '<%= url_for :action => 'create' %>', // target element to update dataType: 'json', - success: afterNewPermission // post-submit callback + success: afterPermission // post-submit callback }; // bind form using 'ajaxForm' $('#permission_form').ajaxForm(permissionoptions); }); -function afterNewPermission(response, status){ - ajax_validation(response, status) - if (response.success) { - jQuery(document).trigger('close.facebox'); - $("#users_grid").flexReload() - } -} </script> - -<%- content_for :title do -%> -<%= "Edit Permissions for #{@permission.pool.name}" %> -<%- end -%> - - diff --git a/wui/src/app/views/resources/new.rhtml b/wui/src/app/views/resources/new.rhtml index 5897a5e..6d856f7 100644 --- a/wui/src/app/views/resources/new.rhtml +++ b/wui/src/app/views/resources/new.rhtml @@ -1,7 +1,9 @@ -<div class="dialog_title_small"> - <div class="header">Add New Virtual Machine Pool</div> - <div>Add a new Virtual Machine Pool to the <%= @parent.name %> pool.</div> -</div> +<%- content_for :title do -%> + Add New Virtual Machine Pool +<%- end -%> +<%- content_for :description do -%> + Add a new Virtual Machine Pool to the <%= @parent.name %> pool. +<%- end -%> <form method="POST" action="<%= url_for :action => 'create' %>" id="vm_pool_form" > <div class="dialog_form"> @@ -9,27 +11,16 @@ </div> <%= popup_footer("$('#vm_pool_form').submit()", "Create Virtual Machine Pool") %> </form> - <script type="text/javascript"> + +<script type="text/javascript"> $(function() { var vmpooloptions = { target: '<%= url_for :action => 'create' %>', // target element to update dataType: 'json', - success: afterNewVmPool // post-submit callback + success: afterVmPool // post-submit callback }; // bind form using 'ajaxForm' $('#vm_pool_form').ajaxForm(vmpooloptions); }); -function afterNewVmPool(response, status){ - ajax_validation(response, status) - if (response.success) { - jQuery(document).trigger('close.facebox'); - $("#vmpools_grid").flexReload() - } -} </script> - -<%- content_for :title do -%> -<%= _("New VM Resource Pool") %> -<%- end -%> - diff --git a/wui/src/app/views/resources/show.rhtml b/wui/src/app/views/resources/show.rhtml index c42ebeb..ecf8fc6 100644 --- a/wui/src/app/views/resources/show.rhtml +++ b/wui/src/app/views/resources/show.rhtml @@ -1,87 +1,9 @@ -<div class="panel_header"></div> - <div id="data"> - <div class="inside"> - - <%= error_messages_for 'vm_actions' %> - - <div id="dataTableWrapper"> - - <div class="dataTable"> - <div class="inside"> - - <div class="data-table-column"> - - <% form_tag( {:controller => 'resources', :action => 'vm_actions'}, {:name => "vm_actions", :method => "post"}) do -%> - <%= tag :input, { "type" => "hidden", "name" => "vm_actions[vm_resource_pool_id]", "value" => "@vm_resource_pool.id"} %> - <%= tag :input, { "type" => "submit", "name" => "vm_actions[#{VmTask::ACTION_START_VM}]", "value" => "Start"} %> - <%= tag :input, { "type" => "submit", "name" => "vm_actions[#{VmTask::ACTION_SHUTDOWN_VM}]", "value" => "Stop"} %> - <select onchange="return confirm_and_submit(this)" name="vm_actions[other_actions]"> - <option class="other">Other Actions</option> - <option></option> - <% for action in @action_values %> - <option value="<%= action[1]%>"><%= action[0]%></option> - <% end %> - <option>-------------------</option> - <option class="remove" value="destroy" onSelect="confirm('Are you sure?'">Destroy</option> - </select> - - <br/> - <%= render :partial => '/layouts/alertbox' %> - <br/> - - <%= render :partial => "/vm/list", :locals => { :vms => @vm_resource_pool.vms } %> - <% end -%> - - </div> - - </div> <!-- end #data-table.inside --> - </div> <!-- end #dataTable --> - - <div class="data-section"> - <div class="data-section-header"><strong>VM Resource Pool Quota</strong></div> - <div class="data-section-stats">Statistics Data</div> - <div class="data-section-table"> - <div class="inside"> - - <table> - <tr> - <th></th> - <td> in use / awaiting use / total allowed</td> - </tr> - <% resources = @vm_resource_pool.full_resources %> - <% for item in resources[:labels] %> - <% total_limit = resources[:total][item[1]] - total_limit = "unlimited" if total_limit.nil? %> - <tr> - <th><%= item[0]%>:</th> - <td><%= resources[:allocated][:current][item[1]] %> / <%= resources[:allocated][:pending][item[1]] %> / <%= total_limit %> - <%= item[2]%></td> - </tr> - <% end %> - </table> - - </div> - </div> - </div> - - - </div> <!-- end #dataTableWrapper --> - - </div> <!-- end #data.inside --> - </div> <!-- end #data --> - - </td> <td id="right"> <div class="heading"> </div> <div class="tools"> <h3>Actions</h3> - <%if @can_modify -%> - <div class="actions create"> - <%= link_to_if @vm_resource_pool, 'Create New VM', {:controller => "vm", :action => 'new', :vm_resource_pool_id => @vm_resource_pool}, { :class => "create" } %> - </div> - <% end -%> <div class="actions"> <%if @is_hwpool_admin -%> <%= link_to 'Edit VM Resource Pool', { :action => 'edit', :id => @vm_resource_pool }, { :class => "edit" } %> diff --git a/wui/src/app/views/storage/_grid.rhtml b/wui/src/app/views/storage/_grid.rhtml index 56fb621..feb855a 100644 --- a/wui/src/app/views/storage/_grid.rhtml +++ b/wui/src/app/views/storage/_grid.rhtml @@ -7,13 +7,14 @@ $("#<%= table_id %>").flexigrid ( { - url: '<%= url_for :controller => "hardware", :action => "storage_pools_json", :id => hwpool_id %>', + url: '<%= url_for :controller => "hardware", :action => "storage_pools_json", :id => hwpool_id, :exclude_id => exclude_id %>', dataType: 'json', colModel : [ - {display: '', name : 'id', width : 20, sortable : false, align: 'left', process: <%= table_id %>checkbox}, - {display: 'Alias', name : 'display_name', width : 180, sortable : false, align: 'left'}, - {display: 'IP', name : 'ip_addr', width : 80, sortable : true, align: 'left'}, - {display: 'Type', name : 'type', width : 80, sortable : true, align: 'left'} + {display: '', width : 20, align: 'left', process: <%= table_id %>checkbox}, + {display: 'Alias', width : 180, align: 'left'}, + <%= "{display: 'Hardware Pool', name : 'pools.name', width : 100, align: 'left'}," if exclude_id %> + {display: 'IP', name : 'ip_addr', width : 80, align: 'left'}, + {display: 'Type', name : 'storage_pools.type', width : 80, align: 'left'} ], sortname: "ip_addr", sortorder: "asc", diff --git a/wui/src/app/views/storage/addstorage.html.erb b/wui/src/app/views/storage/addstorage.html.erb index a747502..e465d12 100644 --- a/wui/src/app/views/storage/addstorage.html.erb +++ b/wui/src/app/views/storage/addstorage.html.erb @@ -1,14 +1,17 @@ -<div class="dialog_title"> - <div class="header">Add Storage</div> - <div>Select storage pools from the list below to add to the <%= @hardware_pool.name %> hardware pool.</div> -</div> +<%- content_for :title do -%> + Add Storage +<%- end -%> +<%- content_for :description do -%> + Select storage pools from the list below to add to the <%= @hardware_pool.name %> hardware pool. +<%- end -%> + +<!-- we'll need this when we combine add/create for storage <div id="toolbar_nav"> <ul> <li class="current" id="unassigned_tab"><a href="#" onClick = "show_unassigned()"><img style="vertical-align:middle;" src="" /> Unassigned Storage (<%= @unassigned %>)</a></li> <li id="assigned_tab"><a href="#" onClick = "show_assigned()"><img style="vertical-align:middle;" src="" /> Assigned Storage (<%= @assigned %>)</a></li> </ul> </div> -<script type="text/javascript"> function show_unassigned() { $("#assigned_tab").removeClass('current'); @@ -23,34 +26,17 @@ $("#addstorage_unassigned_grid_div").hide() $("#addstorage_assigned_grid_div").show() } - function add_storage() - { - var assigned_selected= get_selected_checkboxes(document.addstorage_assigned_grid_form) - var unassigned_selected= get_selected_checkboxes(document.addstorage_unassigned_grid_form) - var storage = Array.concat(assigned_selected,unassigned_selected) - if (validate_selected(storage, "storage pool")) { - $.post('<%= url_for :controller => "hardware", :action => "add_storage", :id => @hardware_pool %>', - { storage_pool_ids: storage.toString() }, - function(data,status){ - jQuery(document).trigger('close.facebox'); - $("#storage_grid").flexReload() - if (data.alert) { - alert(data.alert); - } - }, 'json'); - } - } -</script> + $("#addstorage_assigned_grid_div").hide() +--> <div class="panel_header"></div> <div class="dialog_body"> - <%= render :partial => "/storage/grid", :locals => { :table_id => "addstorage_unassigned_grid", - :hwpool_id => Pool.root.id, :on_select => "false" } %> - <%= render :partial => "/storage/grid", :locals => { :table_id => "addstorage_assigned_grid", - :hwpool_id => nil, :on_select => "false" } %> + <%= render :partial => "/storage/grid", :locals => { :table_id => "addstorage_grid", + :hwpool_id => nil, :exclude_id => @hardware_pool.id, + :on_select => "false" } %> </div> </div> -<script type="text/javascript"> - $("#addstorage_assigned_grid_div").hide() -</script> -<%= popup_footer("add_storage()", "Add Selected Storage Pools") %> +<%= popup_footer("add_storage('#{url_for :controller => 'hardware', + :action => 'add_storage', + :id => @hardware_pool}')", + "Add Selected Storage Pools") %> diff --git a/wui/src/app/views/storage/new.rhtml b/wui/src/app/views/storage/new.rhtml index 6f88ce3..13dedb9 100644 --- a/wui/src/app/views/storage/new.rhtml +++ b/wui/src/app/views/storage/new.rhtml @@ -1,51 +1,47 @@ -<div class="dialog_title_small"> - <div class="header">Add New Storage Pool</div> - <div>Add a new Storage Pool to this oVirt server.</div> -</div> +<%- content_for :title do -%> + Add New Storage Pool +<%- end -%> +<%- content_for :description do -%> + Add a new Storage Pool to this oVirt server. +<%- end -%> <div class="dialog_form"> <%= error_messages_for 'storage_pool' %> - <% form_tag do %> + <% form_tag do %> <!--[form:storage_pool]--> <%= hidden_field_tag 'hardware_pool_id', @hardware_pool.id %> - <%= select_tag_with_label "Storage Type:", 'storage_type', @storage_types, :onChange => "load_details()" %> + <%= select_tag_with_label "Storage Type:", 'storage_type', @storage_types, :onChange => "load_storage_subform()" %> <!--[eoform:storage_pool]--> <% end %> <div class="clear_row"></div> <div class="clear_row"></div> </div> -<form method="POST" action="<%= url_for :action => 'create' %>" id="storage_pool_form" > -<div class="dialog_form"> - <div id="new_storage_pool"/> -</div> -<%= popup_footer("$('#storage_pool_form').submit()", "New Storage Pool") %> +<form method="POST" action="<%= url_for :action => 'create' %>" id="storage_pool_form" > + <div class="dialog_form"> + <div id="new_storage_pool"/> + </div> + <%= popup_footer("$('#storage_pool_form').submit()", "New Storage Pool") %> </form> - <script type="text/javascript"> - function load_details() + +<script type="text/javascript"> + function load_storage_subform() { $('#new_storage_pool').load('<%= url_for :controller => "storage", :action => "new2" %>', { hardware_pool_id: $('[name=hardware_pool_id]').val(), storage_type: $('[name=storage_type]').val()}) } - load_details() + load_storage_subform() $(function() { var storageoptions = { target: '<%= url_for :action => 'create' %>', // target element to update //beforeSubmit: showStorageRequest, // pre-submit callback dataType: 'json', - success: afterNewStoragePool // post-submit callback + success: afterStoragePool // post-submit callback }; // bind form using 'ajaxForm' $('#storage_pool_form').ajaxForm(storageoptions); }); -function afterNewStoragePool(response, status){ - ajax_validation(response, status) - if (response.success) { - jQuery(document).trigger('close.facebox'); - $("#storage_grid").flexReload() - } -} - </script> +</script> diff --git a/wui/src/app/views/vm/new.rhtml b/wui/src/app/views/vm/new.rhtml index 60dbcd6..767f806 100644 --- a/wui/src/app/views/vm/new.rhtml +++ b/wui/src/app/views/vm/new.rhtml @@ -1,12 +1,9 @@ -<div id="window"> - +<%- content_for :title do -%> + Add Virtual Machine +<%- end -%> +<%- content_for :description do -%> +<%- end -%> - <!-- DIALOG TITLE --> - <div class="dialog_title_small"> - <div class="header">Add Virtual Machine</div> - </div> - - <!-- DIALOG BODY --> <form method="POST" action="<%= url_for :action => 'create' %>" id="vm_form" > <div class="dialog_form"> @@ -15,9 +12,6 @@ <!-- DIALOG FOOTER --> <%= popup_footer("$('#vm_form').submit()", "Add Virtual Machine") %> </form> -</div> - - <script type="text/javascript"> $(function() { @@ -25,21 +19,11 @@ $(function() { target: '<%= url_for :action => 'create' %>', // target element to update //beforeSubmit: showStorageRequest, // pre-submit callback dataType: 'json', - success: afterNewVm // post-submit callback + success: afterVm // post-submit callback }; // bind form using 'ajaxForm' $('#vm_form').ajaxForm(vmoptions); }); -function afterNewVm(response, status){ - ajax_validation(response, status) - if (response.success) { - jQuery(document).trigger('close.facebox'); - $("#vms_grid").flexReload() - } -} </script> -<%- content_for :title do -%> -<%= "New Virtual Machine" %> -<%- end -%> diff --git a/wui/src/public/javascripts/ovirt.js b/wui/src/public/javascripts/ovirt.js new file mode 100644 index 0000000..675f20d --- /dev/null +++ b/wui/src/public/javascripts/ovirt.js @@ -0,0 +1,138 @@ +// ovirt-specific javascript functions are defined here + + +// helper functions for dialogs and action links + + +// returns an array of selected values for flexigrid checkboxes +function get_selected_checkboxes(obj_form) +{ + var selected_array = new Array() + var selected_index = 0 + var checkboxes + if (obj_form.grid_checkbox) { + if (obj_form.grid_checkbox.length == undefined) { + checkboxes = [obj_form.grid_checkbox] + } else { + checkboxes = obj_form.grid_checkbox + } + for(var i=0; i < checkboxes.length; i++){ + if(checkboxes[i].checked) + { + selected_array[selected_index]= checkboxes[i].value + selected_index++ + } + } + } + return selected_array +} + + +// make sure that at least one item is selected to continue +function validate_selected(selected_array, name) +{ + if (selected_array.length == 0) { + alert("Please select at least one " + name + " to continue") + return false + } else { + return true + } +} + +function add_hosts(url) +{ + hosts= get_selected_checkboxes(document.addhosts_grid_form) + if (validate_selected(hosts, "host")) { + $.post(url, + { resource_ids: hosts.toString() }, + function(data,status){ + jQuery(document).trigger('close.facebox'); + $("#hosts_grid").flexReload() + if (data.alert) { + alert(data.alert); + } + }, 'json'); + } +} +function add_storage(url) +{ + storage= get_selected_checkboxes(document.addstorage_grid_form) + if (validate_selected(storage, "storage pool")) { + $.post(url, + { resource_ids: storage.toString() }, + function(data,status){ + jQuery(document).trigger('close.facebox'); + $("#storage_grid").flexReload() + if (data.alert) { + alert(data.alert); + } + }, 'json'); + } +} +// deal with ajax form response, filling in validation messages where required. +function ajax_validation(response, status) +{ + if (response.object) { + $(".fieldWithErrors").removeClass("fieldWithErrors"); + $("div.errorExplanation").remove(); + if (!response.success) { + for(i=0; i<response.errors.length; i++) { + var element = $("div.form_field:has(#"+response.object + "_" + response.errors[i][0]+")"); + if (element) { + element.addClass("fieldWithErrors"); + for(j=0; j<response.errors[i][1].length; j++) { + element.append('<div class="errorExplanation">'+response.errors[i][1][j]+'</div>'); + } + } + } + } + if (response.alert) { + alert(response.alert) + } + } +} + +// callback actions for dialog submissions +function afterHwPool(response, status){ + ajax_validation(response, status) + if (response.success) { + jQuery(document).trigger('close.facebox'); + // FIXME do we need to reload the tree here + + // this is for reloading the host/storage grid when + // adding hosts/storage to a new HW pool + if (response.resource_type) { + $('#' + response.resource_type + '_grid').flexReload() + } + // do we have HW pools grid? + //$("#vmpools_grid").flexReload() + } +} +function afterVmPool(response, status){ + ajax_validation(response, status) + if (response.success) { + jQuery(document).trigger('close.facebox'); + $("#vmpools_grid").flexReload() + } +} +function afterStoragePool(response, status){ + ajax_validation(response, status) + if (response.success) { + jQuery(document).trigger('close.facebox'); + $("#storage_grid").flexReload() + } +} +function afterPermission(response, status){ + ajax_validation(response, status) + if (response.success) { + jQuery(document).trigger('close.facebox'); + $("#users_grid").flexReload() + } +} +function afterVm(response, status){ + ajax_validation(response, status) + if (response.success) { + jQuery(document).trigger('close.facebox'); + $("#vms_grid").flexReload() + } +} diff --git a/wui/src/vendor/plugins/betternestedset/lib/#better_nested_set.rb# b/wui/src/vendor/plugins/betternestedset/lib/#better_nested_set.rb# deleted file mode 100644 index d430419..0000000 --- a/wui/src/vendor/plugins/betternestedset/lib/#better_nested_set.rb# +++ /dev/null @@ -1,583 +0,0 @@ -module SymetrieCom - module Acts #:nodoc: - module NestedSet #:nodoc: - def self.included(base) - base.extend(ClassMethods) - end - # This module provides an enhanced acts_as_nested_set mixin for ActiveRecord. - # Please see the README for background information, examples, and tips on usage. - module ClassMethods - # Configuration options are: - # * +parent_column+ - Column name for the parent/child foreign key (default: +parent_id+). - # * +left_column+ - Column name for the left index (default: +lft+). - # * +right_column+ - Column name for the right index (default: +rgt+). NOTE: - # Don't use +left+ and +right+, since these are reserved database words. - # * +scope+ - Restricts what is to be considered a tree. Given a symbol, it'll attach "_id" - # (if it isn't there already) and use that as the foreign key restriction. It's also possible - # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key. - # Example: <tt>acts_as_nested_set :scope => 'tree_id = #{tree_id} AND completed = 0'</tt> - # * +text_column+ - Column name for the title field (optional). Used as default in the - # {your-class}_options_for_select helper method. If empty, will use the first string field - # of your model class. - def acts_as_nested_set(options = {}) - - options[:scope] = "#{options[:scope]}_id".intern if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/ - - write_inheritable_attribute(:acts_as_nested_set_options, - { :parent_column => (options[:parent_column] || 'parent_id'), - :left_column => (options[:left_column] || 'lft'), - :right_column => (options[:right_column] || 'rgt'), - :scope => (options[:scope] || '1 = 1'), - :text_column => (options[:text_column] || columns.collect{|c| (c.type == :string) ? c.name : nil }.compact.first), - :class => self # for single-table inheritance - } ) - - class_inheritable_reader :acts_as_nested_set_options - - if acts_as_nested_set_options[:scope].is_a?(Symbol) - scope_condition_method = %( - def scope_condition - if #{acts_as_nested_set_options[:scope].to_s}.nil? - "#{acts_as_nested_set_options[:scope].to_s} IS NULL" - else - "#{acts_as_nested_set_options[:scope].to_s} = \#{#{acts_as_nested_set_options[:scope].to_s}}" - end - end - ) - else - scope_condition_method = "def scope_condition() \"#{acts_as_nested_set_options[:scope]}\" end" - end - - # no bulk assignment - attr_protected acts_as_nested_set_options[:left_column].intern, - acts_as_nested_set_options[:right_column].intern, - acts_as_nested_set_options[:parent_column].intern - # no assignment to structure fields - module_eval <<-"end_eval", __FILE__, __LINE__ - def #{acts_as_nested_set_options[:left_column]}=(x) - raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{acts_as_nested_set_options[:left_column]}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead." - end - def #{acts_as_nested_set_options[:right_column]}=(x) - raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{acts_as_nested_set_options[:right_column]}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead." - end - def #{acts_as_nested_set_options[:parent_column]}=(x) - raise ActiveRecord::ActiveRecordError, "Unauthorized assignment to #{acts_as_nested_set_options[:parent_column]}: it's an internal field handled by acts_as_nested_set code, use move_to_* methods instead." - end - #{scope_condition_method} - end_eval - - - include SymetrieCom::Acts::NestedSet::InstanceMethods - extend SymetrieCom::Acts::NestedSet::ClassMethods - - # adds the helper for the class -# ActionView::Base.send(:define_method, "#{Inflector.underscore(self.class)}_options_for_select") { special=nil -# "#{acts_as_nested_set_options[:text_column]} || "#{self.class} id #{id}" -# } - - end - - - # Returns the single root for the class (or just the first root, if there are several). - # Deprecation note: the original acts_as_nested_set allowed roots to have parent_id = 0, - # so we currently do the same. This silliness will not be tolerated in future versions, however. - def root - acts_as_nested_set_options[:class].find(:first, :conditions => "(#{acts_as_nested_set_options[:parent_column]} IS NULL OR #{acts_as_nested_set_options[:parent_column]} = 0)") - end - - # Returns the roots and/or virtual roots of all trees. See the explanation of virtual roots in the README. - def roots - acts_as_nested_set_options[:class].find(:all, :conditions => "(#{acts_as_nested_set_options[:parent_column]} IS NULL OR #{acts_as_nested_set_options[:parent_column]} = 0)", :order => "#{acts_as_nested_set_options[:left_column]}") - end - - # Checks the left/right indexes of all records, - # returning the number of records checked. Throws ActiveRecord::ActiveRecordError if it finds a problem. - def check_all - total = 0 - transaction do - # if there are virtual roots, only call check_full_tree on the first, because it will check other virtual roots in that tree. - total = roots.inject(0) {|sum, r| sum + (r[r.left_col_name] == 1 ? r.check_full_tree : 0 )} - raise ActiveRecord::ActiveRecordError, "Scope problems or nodes without a valid root" unless acts_as_nested_set_options[:class].count == total - end - return total - end - - # Re-calculate the left/right values of all nodes. Can be used to convert ordinary trees into nested sets. - def renumber_all - scopes = [] - # only call it once for each scope_condition (if the scope conditions are messed up, this will obviously cause problems) - roots.each do |r| - r.renumber_full_tree unless scopes.include?(r.scope_condition) - scopes << r.scope_condition - end - end - - # Returns an SQL fragment that matches _items_ *and* all of their descendants, for use in a WHERE clause. - # You can pass it a single object, a single ID, or an array of objects and/or IDs. - # # if a.lft = 2, a.rgt = 7, b.lft = 12 and b.rgt = 13 - # Set.sql_for([a,b]) # returns "((lft BETWEEN 2 AND 7) OR (lft BETWEEN 12 AND 13))" - # Returns "1 != 1" if passed no items. If you need to exclude items, just use "NOT (#{sql_for(items)})". - # Note that if you have multiple trees, it is up to you to apply your scope condition. - def sql_for(items) - items = [items] unless items.is_a?(Array) - # get objects for IDs - items.collect! {|s| s.is_a?(acts_as_nested_set_options[:class]) ? s : acts_as_nested_set_options[:class].find(s)}.uniq - items.reject! {|e| e.new_record?} # exclude unsaved items, since they don't have left/right values yet - - return "1 != 1" if items.empty? # PostgreSQL didn't like '0', and SQLite3 didn't like 'FALSE' - items.map! {|e| "(#{acts_as_nested_set_options[:left_column]} BETWEEN #{e[acts_as_nested_set_options[:left_column]]} AND #{e[acts_as_nested_set_options[:right_column]]})" } - "(#{items.join(' OR ')})" - end - - end - - # This module provides instance methods for an enhanced acts_as_nested_set mixin. Please see the README for background information, examples, and tips on usage. - module InstanceMethods - # convenience methods to make the code more readable - def left_col_name()#:nodoc: - acts_as_nested_set_options[:left_column] - end - def right_col_name()#:nodoc: - acts_as_nested_set_options[:right_column] - end - def parent_col_name()#:nodoc: - acts_as_nested_set_options[:parent_column] - end - alias parent_column parent_col_name#:nodoc: Deprecated - def base_set_class()#:nodoc: - acts_as_nested_set_options[:class] # for single-table inheritance - end - - # On creation, automatically add the new node to the right of all existing nodes in this tree. - def before_create # already protected by a transaction - maxright = base_set_class.maximum(right_col_name, :conditions => scope_condition) || 0 - self[left_col_name] = maxright+1 - self[right_col_name] = maxright+2 - end - - # On destruction, delete all children and shift the lft/rgt values back to the left so the counts still work. - def before_destroy # already protected by a transaction - return if self[right_col_name].nil? || self[left_col_name].nil? - self.reload # in case a concurrent move has altered the indexes - dif = self[right_col_name] - self[left_col_name] + 1 - base_set_class.delete_all( "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})" ) - base_set_class.update_all("#{left_col_name} = CASE \ - WHEN #{left_col_name} > #{self[right_col_name]} THEN (#{left_col_name} - #{dif}) \ - ELSE #{left_col_name} END, \ - #{right_col_name} = CASE \ - WHEN #{right_col_name} > #{self[right_col_name]} THEN (#{right_col_name} - #{dif} ) \ - ELSE #{right_col_name} END", - scope_condition) - end - - # By default, records are compared and sorted using the left column. - def <=>(x) - self[left_col_name] <=> x[left_col_name] - end - - # Deprecated. Returns true if this is a root node. - def root? - parent_id = self[parent_col_name] - (parent_id == 0 || parent_id.nil?) && self[right_col_name] && self[left_col_name] && (self[right_col_name] > self[left_col_name]) - end - - # Deprecated. Returns true if this is a child node - def child? - parent_id = self[parent_col_name] - !(parent_id == 0 || parent_id.nil?) && (self[left_col_name] > 1) && (self[right_col_name] > self[left_col_name]) - end - - # Deprecated. Returns true if we have no idea what this is - def unknown? - !root? && !child? - end - - # Returns this record's root ancestor. - def root - # the BETWEEN clause is needed to ensure we get the right virtual root, if using those - base_set_class.find(:first, :conditions => "#{scope_condition} \ - AND (#{parent_col_name} IS NULL OR #{parent_col_name} = 0) AND (#{self[left_col_name]} BETWEEN #{left_col_name} AND #{right_col_name})") - end - - # Returns the root or virtual roots of this record's tree (a tree cannot have more than one real root). See the explanation of virtual roots in the README. - def roots - base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{parent_col_name} IS NULL OR #{parent_col_name} = 0)", :order => "#{left_col_name}") - end - - # Returns this record's parent. - def parent - base_set_class.find(self[parent_col_name]) if self[parent_col_name] - end - - # Returns an array of all parents, starting with the root. - def ancestors - self_and_ancestors - [self] - end - - # Returns an array of all parents plus self, starting with the root. - def self_and_ancestors - base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{self[left_col_name]} BETWEEN #{left_col_name} AND #{right_col_name})", :order => left_col_name ) - end - - # Returns all the children of this node's parent, except self. - def siblings - self_and_siblings - [self] - end - - # Returns all the children of this node's parent, including self. - def self_and_siblings - if self[parent_col_name].nil? || self[parent_col_name].zero? - [self] - else - base_set_class.find(:all, :conditions => "#{scope_condition} AND #{parent_col_name} = #{self[parent_col_name]}", :order => left_col_name) - end - end - - # Returns the level of this object in the tree, root level being 0. - def level - return 0 if self[parent_col_name].nil? - base_set_class.count(:conditions => "#{scope_condition} AND (#{self[left_col_name]} BETWEEN #{left_col_name} AND #{right_col_name})") - 1 - end - - # Returns the number of nested children of this object. - def all_children_count - return (self[right_col_name] - self[left_col_name] - 1)/2 - end - - # Returns itself and all nested children. - # Pass :exclude => item, or id, or [items or id] to exclude one or more items *and* all of their descendants. - def full_set(special=nil) - if special && special[:exclude] - exclude_str = " AND NOT (#{base_set_class.sql_for(special[:exclude])}) " - elsif new_record? || self[right_col_name] - self[left_col_name] == 1 - return [self] - endas - base_set_class.find(:all, :conditions => "#{scope_condition} #{exclude_str} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})", :order => left_col_name) - end - - # Returns all children and nested children. - # Pass :exclude => item, or id, or [items or id] to exclude one or more items *and* all of their descendants. - def all_children(special=nil) - full_set(special) - [self] - end - - # Returns this record's immediate children. - def children - base_set_class.find(:all, :conditions => "#{scope_condition} AND #{parent_col_name} = #{self.id}", :order => left_col_name) - end - - # Deprecated - alias direct_children children - - # Returns this record's terminal children (nodes without children). - def leaves - base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{left_col_name} + 1 = #{right_col_name}", :order => left_col_name) - end - - # Returns the count of this record's terminal children (nodes without children). - def leaves_count - base_set_class.count(:conditions => "#{scope_condition} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{left_col_name} + 1 = #{right_col_name}") - end - - # Checks the left/right indexes of one node and all descendants. - # Throws ActiveRecord::ActiveRecordError if it finds a problem. - def check_subtree - transaction do - self.reload - check # this method is implemented via #check, so that we don't generate lots of unnecessary nested transactions - end - end - - # Checks the left/right indexes of the entire tree that this node belongs to, - # returning the number of records checked. Throws ActiveRecord::ActiveRecordError if it finds a problem. - # This method is needed because check_subtree alone cannot find gaps between virtual roots, orphaned nodes or endless loops. - def check_full_tree - total_nodes = 0 - transaction do - # virtual roots make this method more complex than it otherwise would be - n = 1 - roots.each do |r| - raise ActiveRecord::ActiveRecordError, "Gaps between roots in the tree containing record ##{r.id}" if r[left_col_name] != n - r.check_subtree - n = r[right_col_name] + 1 - end - total_nodes = roots.inject(0) {|sum, r| sum + r.all_children_count + 1 } - unless base_set_class.count(:conditions => "#{scope_condition}") == total_nodes - raise ActiveRecord::ActiveRecordError, "Orphaned nodes or endless loops in the tree containing record ##{self.id}" - end - end - return total_nodes - end - - # Re-calculate the left/right values of all nodes in this record's tree. Can be used to convert an ordinary tree into a nested set. - def renumber_full_tree - indexes = [] - n = 1 - transaction do - for r in roots # because we may have virtual roots - n = r.calc_numbers(n, indexes) - end - for i in indexes - base_set_class.update_all("#{left_col_name} = #{i[:lft]}, #{right_col_name} = #{i[:rgt]}", "#{self.class.primary_key} = #{i[:id]}") - end - end - ## reload? - end - - # Deprecated. Adds a child to this object in the tree. If this object hasn't been initialized, - # it gets set up as a root node. - # - # This method exists only for compatibility and will be removed in future versions. - def add_child(child) - transaction do - self.reload; child.reload # for compatibility with old version - # the old version allows records with nil values for lft and rgt - unless self[left_col_name] && self[right_col_name] - if child[left_col_name] || child[right_col_name] - raise ActiveRecord::ActiveRecordError, "If parent lft or rgt are nil, you can't add a child with non-nil lft or rgt" - end - base_set_class.update_all("#{left_col_name} = CASE \ - WHEN id = #{self.id} \ - THEN 1 \ - WHEN id = #{child.id} \ - THEN 3 \ - ELSE #{left_col_name} END, \ - #{right_col_name} = CASE \ - WHEN id = #{self.id} \ - THEN 2 \ - WHEN id = #{child.id} \ - THEN 4 \ - ELSE #{right_col_name} END", - scope_condition) - self.reload; child.reload - end - unless child[left_col_name] && child[right_col_name] - maxright = base_set_class.maximum(right_col_name, :conditions => scope_condition) || 0 - base_set_class.update_all("#{left_col_name} = CASE \ - WHEN id = #{child.id} \ - THEN #{maxright + 1} \ - ELSE #{left_col_name} END, \ - #{right_col_name} = CASE \ - WHEN id = #{child.id} \ - THEN #{maxright + 2} \ - ELSE #{right_col_name} END", - scope_condition) - child.reload - end - - child.move_to_child_of(self) - # self.reload ## even though move_to calls target.reload, at least one object in the tests was not reloading (near the end of test_common_usage) - end - # self.reload - # child.reload - # - # if child.root? - # raise ActiveRecord::ActiveRecordError, "Adding sub-tree isn\'t currently supported" - # else - # if ( (self[left_col_name] == nil) || (self[right_col_name] == nil) ) - # # Looks like we're now the root node! Woo - # self[left_col_name] = 1 - # self[right_col_name] = 4 - # - # # What do to do about validation? - # return nil unless self.save - # - # child[parent_col_name] = self.id - # child[left_col_name] = 2 - # child[right_col_name]= 3 - # return child.save - # else - # # OK, we need to add and shift everything else to the right - # child[parent_col_name] = self.id - # right_bound = self[right_col_name] - # child[left_col_name] = right_bound - # child[right_col_name] = right_bound + 1 - # self[right_col_name] += 2 - # self.class.transaction { - # self.class.update_all( "#{left_col_name} = (#{left_col_name} + 2)", "#{scope_condition} AND #{left_col_name} >= #{right_bound}" ) - # self.class.update_all( "#{right_col_name} = (#{right_col_name} + 2)", "#{scope_condition} AND #{right_col_name} >= #{right_bound}" ) - # self.save - # child.save - # } - # end - # end - end - - # Move this node to the left of _target_ (you can pass an object or just an id). - # Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem. - def move_to_left_of(target) - self.move_to target, :left - end - - # Move this node to the right of _target_ (you can pass an object or just an id). - # Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem. - def move_to_right_of(target) - self.move_to target, :right - end - - # Make this node a child of _target_ (you can pass an object or just an id). - # Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem. - def move_to_child_of(target) - self.move_to target, :child - end - - protected - def move_to(target, position) #:nodoc: - raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if new_record? - raise ActiveRecord::ActiveRecordError, "You cannot move a node if left or right is nil" unless self[left_col_name] && self[right_col_name] - - transaction do - self.reload # the lft/rgt values could be stale (target is reloaded below) - if target.is_a?(base_set_class) - target.reload # could be stale - else - target = base_set_class.find(target) # load object if we were given an ID - end - - if (target[left_col_name] >= self[left_col_name]) && (target[right_col_name] <= self[right_col_name]) - raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree." - end - - # prevent moves between different trees - if target.scope_condition != scope_condition - raise ActiveRecord::ActiveRecordError, "Scope conditions do not match. Is the target in the same tree?" - end - - # the move: we just need to define two adjoining segments of the left/right index and swap their positions - bound = case position - when :child then target[right_col_name] - when :left then target[left_col_name] - when :right then target[right_col_name] + 1 - else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left or :right ('#{position}' received)." - end - - if bound > self[right_col_name] - bound = bound - 1 - other_bound = self[right_col_name] + 1 - else - other_bound = self[left_col_name] - 1 - end - - return if bound == self[right_col_name] || bound == self[left_col_name] # there would be no change, and other_bound is now wrong anyway - - # we have defined the boundaries of two non-overlapping intervals, - # so sorting puts both the intervals and their boundaries in order - a, b, c, d = [self[left_col_name], self[right_col_name], bound, other_bound].sort - - # change nil to NULL for new parent - if position == :child - new_parent = target.id - else - new_parent = target[parent_col_name].nil? ? 'NULL' : target[parent_col_name] - end - - base_set_class.update_all("\ - #{left_col_name} = CASE \ - WHEN #{left_col_name} BETWEEN #{a} AND #{b} THEN #{left_col_name} + #{d - b} \ - WHEN #{left_col_name} BETWEEN #{c} AND #{d} THEN #{left_col_name} + #{a - c} \ - ELSE #{left_col_name} END, \ - #{right_col_name} = CASE \ - WHEN #{right_col_name} BETWEEN #{a} AND #{b} THEN #{right_col_name} + #{d - b} \ - WHEN #{right_col_name} BETWEEN #{c} AND #{d} THEN #{right_col_name} + #{a - c} \ - ELSE #{right_col_name} END, \ - #{parent_col_name} = CASE \ - WHEN #{self.class.primary_key} = #{self.id} THEN #{new_parent} \ - ELSE #{parent_col_name} END", - scope_condition) - self.reload - target.reload - end - end - - def check #:nodoc: - # performance improvements (3X or more for tables with lots of columns) by using :select to load just id, lft and rgt - ## i don't use the scope condition here, because it shouldn't be needed - my_children = base_set_class.find(:all, :conditions => "#{parent_col_name} = #{self.id}", - :order => left_col_name, :select => "#{self.class.primary_key}, #{left_col_name}, #{right_col_name}") - - if my_children.empty? - unless self[left_col_name] && self[right_col_name] - raise ActiveRecord::ActiveRecordError, "#{self.class.name}##{self.id}.#{right_col_name} or #{left_col_name} is blank" - end - unless self[right_col_name] - self[left_col_name] == 1 - raise ActiveRecord::ActiveRecordError, "#{self.class.name}##{self.id}.#{right_col_name} should be 1 greater than #{left_col_name}" - end - else - n = self[left_col_name] - for c in (my_children) # the children come back ordered by lft - unless c[left_col_name] && c[right_col_name] - raise ActiveRecord::ActiveRecordError, "#{self.class.name}##{c.id}.#{right_col_name} or #{left_col_name} is blank" - end - unless c[left_col_name] == n + 1 - raise ActiveRecord::ActiveRecordError, "#{self.class.name}##{c.id}.#{left_col_name} should be 1 greater than #{n}" - end - c.check - n = c[right_col_name] - end - unless self[right_col_name] == n + 1 - raise ActiveRecord::ActiveRecordError, "#{self.class.name}##{self.id}.#{right_col_name} should be 1 greater than #{n}" - end - end - end - - # used by the renumbering methods - def calc_numbers(n, indexes) #:nodoc: - my_lft = n - # performance improvements (3X or more for tables with lots of columns) by using :select to load just id, lft and rgt - ## i don't use the scope condition here, because it shouldn't be needed - my_children = base_set_class.find(:all, :conditions => "#{parent_col_name} = #{self.id}", - :order => left_col_name, :select => "#{self.class.primary_key}, #{left_col_name}, #{right_col_name}") - if my_children.empty? - my_rgt = (n += 1) - else - for c in (my_children) - n = c.calc_numbers(n + 1, indexes) - end - my_rgt = (n += 1) - end - indexes << {:id => self.id, :lft => my_lft, :rgt => my_rgt} unless self[left_col_name] == my_lft && self[right_col_name] == my_rgt - return n - end - - - - # The following code is my crude method of making things concurrency-safe. - # Basically, we need to ensure that whenever a record is saved, the lft/rgt - # values are _not_ written to the database, because if any changes to the tree - # structure occurrred since the object was loaded, the lft/rgt values could - # be out of date and corrupt the indexes. - # I hope that someone with a little more ruby-foo can look at this and come - # up with a more elegant solution. - private - # override ActiveRecord to prevent lft/rgt values from being saved (can corrupt indexes under concurrent usage) - def update #:nodoc: - connection.update( - "UPDATE #{self.class.table_name} " + - "SET #{quoted_comma_pair_list(connection, special_attributes_with_quotes(false))} " + - "WHERE #{self.class.primary_key} = #{quote_value(id)}", - "#{self.class.name} Update" - ) - end - - # exclude the lft/rgt columns from update statements - def special_attributes_with_quotes(include_primary_key = true) #:nodoc: - attributes.inject({}) do |quoted, (name, value)| - if column = column_for_attribute(name) - quoted[name] = quote_value(value, column) unless (!include_primary_key && column.primary) || [acts_as_nested_set_options[:left_column], acts_as_nested_set_options[:right_column]].include?(column.name) - end - quoted - end - end - - # i couldn't figure out how to call attributes_with_quotes without cutting and pasting this private method in. :( - # Quote strings appropriately for SQL statements. - def quote_value(value, column = nil) #:nodoc: - self.class.connection.quote(value, column) - end - - end - end - end -end - - diff --git a/wui/src/vendor/plugins/betternestedset/lib/better_nested_set.rb b/wui/src/vendor/plugins/betternestedset/lib/better_nested_set.rb index 43b46ca..ed5d3e3 100644 --- a/wui/src/vendor/plugins/betternestedset/lib/better_nested_set.rb +++ b/wui/src/vendor/plugins/betternestedset/lib/better_nested_set.rb @@ -246,24 +246,33 @@ module SymetrieCom # Returns itself and all nested children. # Pass :exclude => item, or id, or [items or id] to exclude one or more items *and* all of their descendants. - def full_set(special=nil) - if special && special[:exclude] - exclude_str = " AND NOT (#{base_set_class.sql_for(special[:exclude])}) " + # in addition to the standard find opts + def full_set(find_opts={}) + exclude = find_opts.delete(:exclude) + if exclude + exclude_str = " AND NOT (#{base_set_class.sql_for(exclude)}) " elsif new_record? || self[right_col_name] - self[left_col_name] == 1 return [self] end - base_set_class.find(:all, :conditions => "#{scope_condition} #{exclude_str} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})", :order => left_col_name) + opts = merge_incoming_opts({:conditions => "#{scope_condition} #{exclude_str} AND (#{left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})", + :order => left_col_name}, + find_opts) + base_set_class.find(:all, opts) end # Returns all children and nested children. # Pass :exclude => item, or id, or [items or id] to exclude one or more items *and* all of their descendants. - def all_children(special=nil) - full_set(special) - [self] + # in addition to the standard find opts + def all_children(find_opts=nil) + full_set(find_opts) - [self] end # Returns this record's immediate children. - def children - base_set_class.find(:all, :conditions => "#{scope_condition} AND #{parent_col_name} = #{self.id}", :order => left_col_name) + def children(find_opts={}) + opts = merge_incoming_opts({:conditions => "#{scope_condition} AND #{parent_col_name} = #{self.id}", + :order => left_col_name}, + find_opts) + base_set_class.find(:all, opts) end # Deprecated @@ -575,6 +584,14 @@ module SymetrieCom self.class.connection.quote(value, column) end + # accept incoming opts to allow filtering of results. + # So far only tested in limited use cases encountered in oVirt devel. + def merge_incoming_opts(set_opts, incoming_opts) + new_conditions = incoming_opts.delete(:conditions) + set_opts[:conditions] = "(#{set_opts[:conditions]}) AND (#{new_conditions})" if new_conditions + set_opts.merge(incoming_opts) + end + end end end -- 1.5.4.1 --------------040805000303080804070604--