---
src/app/controllers/instance_controller.rb | 4 +
src/app/controllers/pool_controller.rb | 95 ++++++++++++++++++++++++++
src/app/models/image.rb | 23 ++++++
src/app/models/instance.rb | 19 +++++
src/app/views/instance/new.html.erb | 53 ++++++++++++++-
src/app/views/pool/images.html.erb | 102 ++++++++++++++++++++++------
src/app/views/pool/show.html.erb | 81 ++++++++++++----------
7 files changed, 319 insertions(+), 58 deletions(-)
diff --git a/src/app/controllers/instance_controller.rb
b/src/app/controllers/instance_controller.rb
index 0008c06..e47432c 100644
--- a/src/app/controllers/instance_controller.rb
+++ b/src/app/controllers/instance_controller.rb
@@ -40,6 +40,10 @@ class InstanceController < ApplicationController
def new
@instance = Instance.new({:pool_id => params[:id]})
@pool = Pool.find(params[:id])
+
+ image_id = (params[:image_id] || []).first.to_s
+ @image = image_id.empty? ? nil : Image.find(image_id)
+
require_privilege(Privilege::INSTANCE_MODIFY,@pool)
end
diff --git a/src/app/controllers/pool_controller.rb
b/src/app/controllers/pool_controller.rb
index 2ae0988..c438773 100644
--- a/src/app/controllers/pool_controller.rb
+++ b/src/app/controllers/pool_controller.rb
@@ -83,4 +83,99 @@ class PoolController < ApplicationController
require_privilege(Privilege::POOL_VIEW, @pool)
end
+ def images_paginate
+ # datatables sends pagination in format:
+ # iDisplayStart - start index
+ # iDisplayLength - num of recs
+ # => we need to count page num
+ page = params[:iDisplayStart].to_i / Image::RECS_PER_PAGE
+
+ if params[:mode].to_s == 'simple'
+ simple_mode = true
+ cols = Image::COLUMNS_SIMPLE
+ default_order_col = 1
+ else
+ cols = Image::COLUMNS
+ simple_mode = false
+ default_order_col = 2
+ end
+
+ # FIXME: check sortable on selected column
+ order_col = (cols[params[:iSortCol_0].to_i] ||
cols[default_order_col])[:id]
+ order = order_col + " " + (params[:sSortDir_0] == 'desc' ? 'desc' : 'asc')
+
+ if params[:sSearch].to_s.empty?
+ conditions = {:pool_id => params[:id]}
+ else
+ conditions = ["pool_id = ? and name like ?", params[:id],
"%#{params[:sSearch]}%"]
+ end
+
+ @images = Image.paginate(
+ :page => page + 1,
+ :include => :instances,
+ :order => order,
+ :conditions => conditions
+ )
+
+ expand_button_html = "<img src='/images/dir_closed.png'>"
+
+ data = @images.map do |i|
+ if simple_mode
+ [i.id, i.name, i.architecture, i.instances.size]
+ else
+ [i.id, expand_button_html, i.name, i.architecture, i.instances.size,
"TODO: some description here?"]
+ end
+ end
+
+ render :json => {
+ :sEcho => params[:sEcho],
+ :iTotalRecords => @images.total_entries,
+ :iTotalDisplayRecords => @images.total_entries,
+ :aaData => data
+ }
+ end
+
+ def instances_paginate
+ # datatables sends pagination in format:
+ # iDisplayStart - start index
+ # iDisplayLength - num of recs
+ # => we need to count page num
+ page = params[:iDisplayStart].to_i / 15
+
+ @instances = Instance.paginate(:page => page + 1, :order => "name asc",
:conditions => {:pool_id => params[:id]})
+
+ # FIXME: default stylesheet redefines link color
+ details_html = "<a href='#' class='details_link'>Details</a>"
+
+ render :json => {
+ :sEcho => params[:sEcho],
+ :iTotalRecords => @instances.total_entries,
+ :iTotalDisplayRecords => @instances.total_entries,
+ :aaData => @instances.map {|i| [i.id, "", i.name, details_html, i.state,
i.hardware_profile.name, i.image.name]}
+ }
+ end
+
+
+ def accounts_for_pool
+ @pool = Pool.find(params[:pool_id])
+ require_privilege(Privilege::ACCOUNT_VIEW,@pool)
+ @cloud_accounts = []
+ all_accounts = CloudAccount.list_for_user(@current_user,
Privilege::ACCOUNT_ADD)
+ all_accounts.each {|account|
+ @cloud_accounts << account unless @pool.cloud_accounts.map{|x|
x.id}.include?(account.id)
+ }
+ end
+
+ def add_account
+ @pool = Pool.find(params[:pool])
+ @cloud_account = CloudAccount.find(params[:cloud_account])
+ require_privilege(Privilege::ACCOUNT_ADD,@pool)
+ require_privilege(Privilege::ACCOUNT_ADD,@cloud_account)
+ Pool.transaction do
+ @pool.cloud_accounts << @cloud_account unless
@pool.cloud_accounts.map{|x| x.id}.include?(@cloud_account.id)
+ @pool.save!
+ @pool.populate_realms_and_images([...@cloud_account])
+ end
+ redirect_to :action => 'show', :id => @pool.id
+ end
end
diff --git a/src/app/models/image.rb b/src/app/models/image.rb
index 86806a4..7b36c96 100644
--- a/src/app/models/image.rb
+++ b/src/app/models/image.rb
@@ -20,6 +20,11 @@
# Likewise, all the methods added will be available for all controllers.
class Image < ActiveRecord::Base
+ RECS_PER_PAGE = 15
+
+ cattr_reader :per_page
+ @@per_page = RECS_PER_PAGE
+
has_many :instances
belongs_to :provider
@@ -43,6 +48,24 @@ class Image < ActiveRecord::Base
validates_presence_of :architecture, :if => :provider
+ # used to get sorting column in controller and in view to generate datatable
definition and
+ # html table structure
+ COLUMNS = [
+ {:id => 'id', :header => '<input type="checkbox" id="image_id_all"
onclick="checkAll(event)">', :opts => {:checkbox_id => 'image_id', :searchable
=> false, :sortable => false, :width => '1px', :class => 'center'}},
+ {:id => 'expand_button', :header => '', :header => '', :opts =>
{:searchable => false, :sortable => false, :width => '1px'}},
+ {:id => 'name', :header => 'Name', :opts => {:width => "30%"}},
+ {:id => 'architecture', :header => 'Architecture', :opts => {:width =>
"10%"}},
+ {:id => 'instances', :header => 'Instances', :opts => {:sortable => false,
:width => "10%"}},
+ {:id => 'details', :header => '', :opts => {:sortable => false, :visible
=> false, :searchable => false}},
+ ]
+
+ COLUMNS_SIMPLE = [
+ {:id => 'id', :header => '', :opts => {:visible => false, :searchable =>
false, :sortable => false, :width => '1px', :class => 'center'}},
+ {:id => 'name', :header => 'Name', :opts => {:width => "50%"}},
+ {:id => 'architecture', :header => 'Architecture', :opts => {:width =>
"30%"}},
+ {:id => 'instances', :header => 'Instances', :opts => {:sortable => false,
:width => "20%"}},
+ ]
+
def provider_image?
!provider.nil?
diff --git a/src/app/models/instance.rb b/src/app/models/instance.rb
index fa89844..8c2da69 100644
--- a/src/app/models/instance.rb
+++ b/src/app/models/instance.rb
@@ -20,6 +20,11 @@
# Likewise, all the methods added will be available for all controllers.
class Instance < ActiveRecord::Base
+ RECS_PER_PAGE = 15
+
+ cattr_reader :per_page
+ @@per_page = RECS_PER_PAGE
+
belongs_to :pool
belongs_to :cloud_account
@@ -56,6 +61,20 @@ class Instance < ActiveRecord::Base
:in => [STATE_NEW, STATE_PENDING, STATE_RUNNING,
STATE_SHUTTING_DOWN, STATE_STOPPED, STATE_CREATE_FAILED]
+ # used to get sorting column in controller and in view to generate datatable
definition and
+ # html table structure
+ COLUMNS = [
+ {:id => 'id', :header => '', :opts => {:checkbox_id => 'image_id',
:searchable => false, :sortable => false, :width => '1px', :class => 'center'}},
+ {:id => 'actions', :header => 'Actions', :opts => {:width => "15%",
:sortable => false}},
+ {:id => 'name', :header => 'Name', :opts => {:width => "25%"}},
+ {:id => 'details', :header => '', :opts => {:width => "10%", :sortable =>
false, :searchable => false}},
+ {:id => 'state', :header => 'State', :opts => {:width => "15%"}},
+ {:id => 'hwprofile', :header => 'HW profile', :opts => {:width => "15%"}},
+ {:id => 'image', :header => 'Image', :opts => {:sortable => false}},
+ ]
+
+ RECS_PER_PAGE = 15
+
def get_action_list(user=nil)
# return empty list rather than nil
# FIXME: not handling pending state now -- only current state
diff --git a/src/app/views/instance/new.html.erb
b/src/app/views/instance/new.html.erb
index 986959a..0acc4e8 100644
--- a/src/app/views/instance/new.html.erb
+++ b/src/app/views/instance/new.html.erb
@@ -14,8 +14,9 @@
<% end %>
<% if @pool.images.size > 0 %>
<li><label>Image<span>Choose a bundled image to use</span></label>
- <%= select("instance", "image_id", @pool.images.collect {|i| [
truncate(i.name, 29, '...'), i.id ] }, { :include_blank => true }) %>
+ <input type="button" style="cursor:pointer" onclick="showImages(event)"
name="image_selector" value="<%= @image ? @image.name : "click to select
image"%>"/>
</li>
+ <input type=hidden name="instance[image_id]" value="<%= @image ?
@image.id : '' %>">
<% end %>
<% if @pool.realms.size > 0 %>
<li><label>Realm<span>Choose a realm</span></label>
@@ -26,4 +27,54 @@
<%= hidden_field :instance, :pool_id %>
<%= submit_tag "Save", :class => "submit" %>
<% end %>
+ </div>
+
+
+
+<script type="text/javascript">
+ function showImages(ev) {
+ $("#images_table_wrapper").css('display', 'block');
+ images_dialog = $("#images_table_wrapper").dialog({
+ title: "Select image for new instance",
+ dialogClass: 'flora',
+ width: 600,
+ height: 500,
+ modal: true,
+ overlay: {opacity: 0.2, background: "black"}
+ });
+ }
+ function selectImage(ev) {
+ var image_id = dataTable_images_table.fnGetData(ev.target.parentNode)[0];
+ var image_name = dataTable_images_table.fnGetData(ev.target.parentNode)[1];
+ $("#images_table_wrapper").css('display', 'none');
+ $("#images_table_wrapper").dialog('close');
+ $('input[name="image_selector"]').attr('value', image_name);
+ $('input[name="instance[image_id]"]').attr('value', image_id);
+ }
+</script>
+
+<%= datatable(
+ Image::COLUMNS_SIMPLE.map {|c| c[:opts]},
+ {
+ :table_dom_id => 'images_table',
+ :per_page => Image::RECS_PER_PAGE,
+ :sort_by => "[2, 'asc']",
+ :serverside => true,
+ :ajax_source => url_for(:controller => 'pool', :action =>
'images_paginate') + "/#[email protected]}?mode=simple",
+ :append => ".fnSetFilteringDelay()",
+ :click_callback => "function(event) {selectImage(event)}",
+ }
+) %>
+<div style="display:none" id="images_table_wrapper">
+ <table class="datatable display" id="images_table">
+ <thead>
+ <tr>
+ <% Image::COLUMNS_SIMPLE.each do |c| %>
+ <%= "<th>#{c[:header]}</th>" %>
+ <% end %>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+</table>
</div>
diff --git a/src/app/views/pool/images.html.erb
b/src/app/views/pool/images.html.erb
index e0187a8..3fe0637 100644
--- a/src/app/views/pool/images.html.erb
+++ b/src/app/views/pool/images.html.erb
@@ -1,20 +1,82 @@
-<% if @pool.images.size == 0 %>
-<h1>There are no images to display</h1>
-<% else %>
- <table>
- <thead>
- <tr>
- <th scope="col">Name</th>
- <th scope="col">Arch</th>
- </tr>
- </thead>
- <tbody>
- <%[email protected] {|image| %>
- <tr>
- <td><%= image.name %></td>
- <td><%= image.architecture %></td>
- </tr>
- <% } %>
- </tbody>
- </table>
-<% end %>
\ No newline at end of file
+<script type="text/javascript">
+
+ function fnOpenClose() {
+ $('td img', dataTable_images_table.fnGetNodes() ).each( function () {
+ $(this).click( function () {
+ var nTr = this.parentNode.parentNode;
+ if ( this.src.match('dir_open') )
+ {
+ /* This row is already open - close it */
+ this.src = "/images/dir_closed.png";
+ /* fnClose doesn't do anything for server-side processing - do it
ourselves :-) */
+ var nRemove = $(nTr).next()[0];
+ nRemove.parentNode.removeChild( nRemove );
+ }
+ else
+ {
+ /* Open this row */
+ this.src = "/images/dir_open.png";
+ dataTable_images_table.fnOpen( nTr,
dataTable_images_table.fnGetData(nTr)[5], 'details' );
+ }
+ });
+ });
+ }
+
+ function getCheckedRows() {
+ return $('input[name="image_id[]"]:checked',
dataTable_images_table).length;
+ }
+
+ function showButtons() {
+ getCheckedRows();
+ $('input[name="create_image"]').attr('disabled', getCheckedRows() != 1);
+ }
+
+ function checkRow(ev) {
+ var box = $('input[name="image_id[]"]', ev.target.parentNode);
+ if ($(ev.target).attr('name') != "image_id[]")
+ box.attr('checked', !box.attr('checked'));
+ showButtons();
+ }
+
+ function checkAll(ev) {
+ $('input[name="image_id[]"]', dataTable_images_table).attr('checked',
$(ev.target).attr('checked'))
+ }
+
+ function drawCallback() {
+ $('#image_id_all').attr('checked', false);
+ fnOpenClose();
+ }
+</script>
+
+<%= datatable(
+ Image::COLUMNS.map {|c| c[:opts]},
+ {
+ :table_dom_id => 'images_table',
+ :per_page => Image::RECS_PER_PAGE,
+ :sort_by => "[2, 'asc']",
+ :serverside => true,
+ :ajax_source => url_for(:controller => 'pool', :action =>
'images_paginate') + "/#[email protected]}",
+ :append => ".fnSetFilteringDelay()",
+ :persist_state => false,
+ :click_callback => "function(ev) {checkRow(ev);}",
+ :draw_callback => "drawCallback",
+ }
+) %>
+ <form method=POST action="/instance/new/<%= @pool.id %>"
style="display:block">
+ <div style="padding:20px">
+ <input type="submit" value="Create instance" name="create_image"
disabled=true>
+ </div>
+ <table class="datatable display" id="images_table">
+ <thead>
+ <tr>
+ <% Image::COLUMNS.each do |c| %>
+ <%= "<th>#{c[:header]}</th>" %>
+ <% end %>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+</table>
+</form>
+
+
diff --git a/src/app/views/pool/show.html.erb b/src/app/views/pool/show.html.erb
index ef637f1..43fcec8 100644
--- a/src/app/views/pool/show.html.erb
+++ b/src/app/views/pool/show.html.erb
@@ -1,40 +1,47 @@
-<% if @instances.size == 0 %>
-<h1>There are no instances to display</h1>
-<% else %>
- <table>
- <thead>
- <tr>
- <th scope="col">Actions</th>
- <th scope="col">Name</th>
- <th scope="col">State</th>
- <th scope="col">Hardware Profile</th>
- <th scope="col">Image</th>
- </tr>
- </thead>
- <tbody>
- <%[email protected] {|instance| %>
- <tr>
- <td>
- <ul class="instance_action_list">
- <%instance.get_action_list.each {|action|%>
- <li>
- <%= link_to action, :controller => "instance",
- :action => "instance_action",
- :id => instance,
- :instance_action => action %>
- </li>
- <% } %>
- </ul>
- </td>
- <td><%= instance.name %></td>
- <td><%= instance.state %></td>
- <td><%= instance.hardware_profile.name %></td>
- <td><%= instance.image.name %></td>
- </tr>
- <% } %>
- </tbody>
- </table>
-<% end %>
+<%= datatable(
+ Instance::COLUMNS.map {|c| c[:opts]},
+ {
+ :table_dom_id => 'instances_table',
+ :per_page => Instance::RECS_PER_PAGE,
+ :sort_by => "[3, 'asc']",
+ :serverside => true,
+ :ajax_source => url_for(:controller => 'pool', :action =>
'instances_paginate') + "/#[email protected]}",
+ :append => ".fnSetFilteringDelay()",
+ :persist_state => false,
+ :click_callback => "function(ev) {clickRow(ev);}",
+ }
+) %>
+<table class="datatable display" id="instances_table">
+ <thead>
+ <tr>
+ <% Instance::COLUMNS.each do |c| %>
+ <%= "<th>#{c[:header]}</th>" %>
+ <% end %>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+</table>
+<script type="text/javascript">
+ function showInstanceDetailsDialog() {
+ images_dialog = $("<div>TODO: show dialog</div>").dialog({
+ title: "Instance details",
+ dialogClass: 'flora',
+ width: 600,
+ height: 300,
+ modal: true,
+ overlay: {opacity: 0.2, background: "black"}
+ });
+ }
+
+ function clickRow(ev) {
+ var box = $('input[name="image_id[]"]', ev.target.parentNode);
+ if ($(ev.target).hasClass('details_link')) showInstanceDetailsDialog();
+ if ($(ev.target).attr('name') != "image_id[]")
+ box.attr('checked', !box.attr('checked'));
+ }
+</script>
+
<%= link_to "Add a new instance", {:controller => "instance", :action =>
"new", :id => @pool}, :class=>"actionlink"%>
<%= link_to "User access", {:controller => "permissions", :action => "list",
:pool_id => @pool.id}, :class=>"actionlink" if has_view_perms? %>
<%= link_to "Hardware Profiles", {:action => "hardware_profiles", :id =>
@pool.id}, :class=>"actionlink"%>
--
1.6.2.5
_______________________________________________
deltacloud-devel mailing list
[email protected]
https://fedorahosted.org/mailman/listinfo/deltacloud-devel