staypuft 0.1.22 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +6 -0
  3. data/app/assets/javascripts/staypuft/staypuft.js +60 -0
  4. data/app/assets/stylesheets/staypuft/staypuft.css.scss +23 -0
  5. data/app/controllers/staypuft/deployments_controller.rb +1 -1
  6. data/app/helpers/staypuft/application_helper.rb +11 -0
  7. data/app/lib/staypuft/seeder.rb +22 -18
  8. data/app/models/staypuft/concerns/host_details_helper.rb +39 -0
  9. data/app/models/staypuft/deployment.rb +9 -4
  10. data/app/models/staypuft/deployment/cinder_service.rb +104 -17
  11. data/app/models/staypuft/deployment/glance_service.rb +30 -6
  12. data/app/models/staypuft/deployment/neutron_service.rb +1 -1
  13. data/app/models/staypuft/deployment/vlan_range_values_validator.rb +1 -1
  14. data/app/views/staypuft/deployments/{summary.html.erb → _advanced_configuration.html.erb} +20 -18
  15. data/app/views/staypuft/deployments/_assigned_hosts.html.erb +47 -0
  16. data/app/views/staypuft/deployments/_deployed_hosts.html.erb +47 -0
  17. data/app/views/staypuft/deployments/_deployment_hosts.html.erb +21 -0
  18. data/app/views/staypuft/deployments/_deployment_networking.html.erb +1 -0
  19. data/app/views/staypuft/deployments/_deployment_overview.html.erb +95 -0
  20. data/app/views/staypuft/deployments/_deployment_show_header.html.erb +44 -0
  21. data/app/views/staypuft/deployments/_free_hosts.html.erb +48 -0
  22. data/app/views/staypuft/deployments/_free_hosts_table.html.erb +2 -2
  23. data/app/views/staypuft/deployments/edit.html.erb +2 -9
  24. data/app/views/staypuft/deployments/show.html.erb +17 -134
  25. data/app/views/staypuft/steps/_cinder.html.erb +13 -0
  26. data/lib/staypuft/engine.rb +1 -0
  27. data/lib/staypuft/version.rb +1 -1
  28. metadata +94 -96
@@ -5,14 +5,18 @@ module Staypuft
5
5
  end
6
6
 
7
7
  NFS_HELP = N_('(<server>:<local path>)')
8
+ BACKEND_FILE = 'file'
9
+ BACKEND_RBD = 'rbd'
8
10
 
9
11
  param_attr :driver_backend, :nfs_network_path
10
12
 
11
13
  module DriverBackend
12
14
  LOCAL = 'local'
13
15
  NFS = 'nfs'
16
+ CEPH = 'ceph'
14
17
  LABELS = { LOCAL => N_('Local File'),
15
- NFS => N_('NFS') }
18
+ NFS => N_('NFS'),
19
+ CEPH => N_('Ceph') }
16
20
  TYPES = LABELS.keys
17
21
  HUMAN = N_('Choose Driver Backend')
18
22
  end
@@ -30,17 +34,17 @@ module Staypuft
30
34
  # TODO: network_path validation
31
35
 
32
36
  class Jail < Safemode::Jail
33
- allow :driver_backend, :pcmk_fs_device, :pcmk_fs_options
37
+ allow :driver_backend, :pcmk_fs_type, :pcmk_fs_device, :pcmk_fs_options, :backend,
38
+ :pcmk_fs_manage
34
39
  end
35
40
 
36
41
  def set_defaults
37
42
  self.driver_backend = DriverBackend::LOCAL
38
43
  end
39
44
 
40
- # gluster config only shows up for HA
41
- # glance UI is HA only until we add ceph (since there's only one option for non-HA)
45
+ # glance config always shows up
42
46
  def active?
43
- deployment.ha?
47
+ true
44
48
  end
45
49
 
46
50
  def local_backend?
@@ -51,6 +55,20 @@ module Staypuft
51
55
  self.driver_backend == DriverBackend::NFS
52
56
  end
53
57
 
58
+ def ceph_backend?
59
+ self.driver_backend == DriverBackend::CEPH
60
+ end
61
+
62
+ def backend
63
+ ceph_backend? ? BACKEND_RBD : BACKEND_FILE
64
+ end
65
+
66
+ def pcmk_fs_type
67
+ if self.nfs_backend?
68
+ self.driver_backend
69
+ end
70
+ end
71
+
54
72
  def pcmk_fs_device
55
73
  if self.nfs_backend?
56
74
  self.nfs_network_path
@@ -59,22 +77,28 @@ module Staypuft
59
77
 
60
78
  def pcmk_fs_options
61
79
  if self.nfs_backend?
62
- 'context=\"system_u:object_r:glance_var_lib_t:s0\"'
80
+ 'nosharecache,context=\"system_u:object_r:glance_var_lib_t:s0\"'
63
81
  else
64
82
  ''
65
83
  end
66
84
  end
67
85
 
86
+ def pcmk_fs_manage
87
+ backend == BACKEND_FILE
88
+ end
89
+
68
90
  # view should use this rather than DriverBackend::LABELS to hide LOCAL for HA.
69
91
  def backend_labels_for_layout
70
92
  ret_list = DriverBackend::LABELS.clone
71
93
  ret_list.delete(DriverBackend::LOCAL) if self.deployment.ha?
94
+ ret_list.delete(DriverBackend::NFS) if self.deployment.non_ha?
72
95
  ret_list
73
96
  end
74
97
 
75
98
  def backend_types_for_layout
76
99
  ret_list = DriverBackend::TYPES.clone
77
100
  ret_list.delete(DriverBackend::LOCAL) if self.deployment.ha?
101
+ ret_list.delete(DriverBackend::NFS) if self.deployment.non_ha?
78
102
  ret_list
79
103
  end
80
104
 
@@ -6,7 +6,7 @@ module Staypuft
6
6
 
7
7
  SEGMENTATION_LIST = ['vxlan', 'vlan', 'gre', 'flat']
8
8
  INTERFACE_HELP = N_('(e.g. eth0 or em1)')
9
- VLAN_HELP = N_('[1-4094] (e.g. 10:100)')
9
+ VLAN_HELP = N_('[1-4094] (e.g. 10:15)')
10
10
 
11
11
 
12
12
  param_attr :network_segmentation, :tenant_vlan_ranges, :networker_tenant_interface,
@@ -1,6 +1,6 @@
1
1
  module Staypuft
2
2
  module Deployment::VlanRangeValuesValidator
3
- GENERAL_MSG = N_("Start and end vlan IDs required, in format '10:100'")
3
+ GENERAL_MSG = N_("Start and end vlan IDs required, in format '10:15'")
4
4
  INT_VAL_MSG = N_("is not a valid integer between 1 and 4094")
5
5
  FIRST_VALID = 1
6
6
  LAST_VALID = 4024
@@ -1,25 +1,27 @@
1
- <% title _("%s Configuration Parameters") % @deployment.name %>
2
- <% content_for(:top_actions) do %>
3
- <%= link_to _("Back to Deployment"), deployment_path(@deployment.id), :class => "btn btn-link" %>
4
- <%= link_to(icon_text("cloud-upload", _("Import")),
5
- {},
6
- { :class => "btn btn-default",
7
- :remote => true,
8
- :rel => 'popover',
9
- :data => { 'content' => "#{render partial: 'import_form'}",
10
- 'original-title' => _('Import Config'),
11
- 'placement' => 'left' } }) %>
12
- <%= link_to(icon_text("cloud-download", _("Export")),
13
- export_config_deployment_path(@deployment.id), :class => "btn btn-default") %>
14
- <%= link_to _("Edit"), edit_deployment_path(@deployment.id),
15
- :class => @deployment.in_progress? ? %w(btn btn-default disabled) : %w(btn btn-default) %>
16
- <% end %>
17
1
 
2
+ <div class="col-md-12">
3
+ <div class="pull-right">
4
+ <%= link_to(icon_text("cloud-upload", _("Import")),
5
+ {},
6
+ { :class => "btn btn-default",
7
+ :remote => true,
8
+ :rel => 'popover',
9
+ :data => { 'content' => "#{render partial: 'import_form'}",
10
+ 'original-title' => _('Import Config'),
11
+ 'placement' => 'left' } }) %>
12
+ <%= link_to(icon_text("cloud-download", _("Export")),
13
+ export_config_deployment_path(@deployment.id), :class => "btn btn-default") %>
14
+ <%= link_to _("Edit"), edit_deployment_path(@deployment.id),
15
+ :class => @deployment.in_progress? ? %w(btn btn-default disabled) : %w(btn btn-default) %>
16
+ </div>
17
+ </div>
18
+ <br />
19
+ <br />
18
20
  <div class="col-md-12">
19
21
  <div class="form-group tabbed_side_nav_form row">
20
22
  <ul class="nav nav-pills nav-stacked col-md-3" data-tabs="pills">
21
23
  <h3><%= _("Services") %></h3>
22
- <% @service_hostgroup_map.each_with_index do |(service, hostgroup), i| %>
24
+ <% @deployment.services_hostgroup_map.each_with_index do |(service, hostgroup), i| %>
23
25
  <li class="<%= 'active' if i == 0 %>">
24
26
  <a href="#<%= service.name.parameterize.underscore %>" data-toggle="tab">
25
27
  <%= service.name %>
@@ -29,7 +31,7 @@
29
31
  </ul>
30
32
 
31
33
  <div class="tab-content col-md-9">
32
- <% @service_hostgroup_map.each_with_index do |(service, hostgroup), i| %>
34
+ <% @deployment.services_hostgroup_map.each_with_index do |(service, hostgroup), i| %>
33
35
  <div class="tab-pane <%= 'active' if i == 0 %>" id="<%= service.name.parameterize.underscore %>">
34
36
  <h3><%= "#{service.name} " + _("Service Configuration") %></h3>
35
37
  <% if (params_hash = service.ui_params_for_form(hostgroup)).size > 0 %>
@@ -0,0 +1,47 @@
1
+ <% if hosts.any? %>
2
+ <div class="" id="table_assigned_hosts">
3
+ <%= form_tag(associate_host_deployment_path(id: @deployment), class: 'form-horizontal association') do |f| %>
4
+ <div class="pull-right top_actions">
5
+ <%= submit_tag _("Configure Networks"), :class => "btn btn-default btn-sm", :style => "visibility: hidden" %>
6
+ <%= submit_tag _("Unassign Hosts"), :class => "btn btn-primary btn-sm", :style => "visibility: hidden" %>
7
+ </div>
8
+ <h4 class="pull-left"><%= _("Assigned Hosts") %></h4>
9
+ <p class="clearfix"></p>
10
+ <table class="table table-bordered table-striped table-condensed">
11
+ <thead>
12
+ <tr>
13
+ <th class="ca">
14
+ <%= tag :input, type: 'checkbox', class: 'check_all' %>
15
+ </th>
16
+ <th><%= sort :name, :as => _('Name') %></th>
17
+ <th class="hidden-s hidden-xs"><%= sort :ip, :as => _('IP Address') %></th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ <% hosts.each do |host| %>
22
+ <tr class="checkbox_highlight">
23
+ <td class="ca">
24
+ <%= check_box_tag 'host_ids[]',
25
+ host.id,
26
+ false,
27
+ id: "host_ids_#{host.id}" %>
28
+ </td>
29
+ <td class="ellipsis">
30
+ <%= host_label(host) %>
31
+ </td>
32
+ <td class="hidden-s hidden-xs"><%= host.ip %></td>
33
+ </tr>
34
+ <% end %>
35
+ </tbody>
36
+ </table>
37
+ <% end %>
38
+ </div>
39
+ <% else %>
40
+ <div class="association">
41
+ <h4><%= _("Assigned Hosts") %></h4>
42
+ <div class="alert alert-warning">
43
+ <span class="glyphicon glyphicon-warning-sign">&nbsp;</span>
44
+ <%= _("There are no currently assigned hosts.") %>
45
+ </div>
46
+ </div>
47
+ <% end %>
@@ -0,0 +1,47 @@
1
+ <% if hosts.any? %>
2
+ <div class="" id="table_deployed_hosts">
3
+ <%= form_tag(associate_host_deployment_path(id: @deployment), class: 'form-horizontal association') do |f| %>
4
+ <div class="pull-right top_actions">
5
+ <%= submit_tag _("Undeploy Hosts"), :class => "btn btn-primary btn-smt",
6
+ :style => "visibility: hidden" %>
7
+ </div>
8
+ <h4 class="pull-left"><%= _("Deployed Hosts") %></h4>
9
+ <p class="clearfix"></p>
10
+ <table class="table table-bordered table-striped table-condensed">
11
+ <thead>
12
+ <tr>
13
+ <th class="ca">
14
+ <%= tag :input, type: 'checkbox', class: 'check_all' %>
15
+ </th>
16
+ <th><%= sort :name, :as => _('Name') %></th>
17
+ <th class="hidden-s hidden-xs"><%= sort :ip, :as => _('IP Address') %></th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ <% hosts.each do |host| %>
22
+ <tr class="checkbox_highlight">
23
+ <td class="ca">
24
+ <%= check_box_tag 'host_ids[]',
25
+ host.id,
26
+ false,
27
+ id: "host_ids_#{host.id}" %>
28
+ </td>
29
+ <td class="ellipsis">
30
+ <%= host_label(host) %>
31
+ </td>
32
+ <td class="hidden-s hidden-xs"><%= host.ip %></td>
33
+ </tr>
34
+ <% end %>
35
+ </tbody>
36
+ </table>
37
+ <% end %>
38
+ </div>
39
+ <% else %>
40
+ <div class="association">
41
+ <h4><%= _("Deployed Hosts") %></h4>
42
+ <div class="alert alert-warning">
43
+ <span class="glyphicon glyphicon-warning-sign">&nbsp;</span>
44
+ <%= _("There are no currently deployed hosts.") %>
45
+ </div>
46
+ </div>
47
+ <% end %>
@@ -0,0 +1,21 @@
1
+ <% deployed_hosts = @deployment.hosts.select { |h| h.open_stack_deployed? } %>
2
+ <% assigned_hosts = @deployment.hosts.select { |h| !h.open_stack_deployed? } %>
3
+ <% free_hosts = ::Host.where("hostgroup_id IS NULL") %>
4
+
5
+ <% sub_nav = [ {:title => "#{_('Deployed')} (#{deployed_hosts.count})", :target_id => '#deployed-hosts', :class => "inner-nav", :active => true},
6
+ {:title => "#{_('Assigned')} (#{assigned_hosts.count})", :target_id => '#assigned-hosts', :class => "inner-nav"},
7
+ {:title => "#{_('Free')} (#{free_hosts.count})", :target_id => '#free-hosts', :class => "inner-nav"} ] %>
8
+
9
+ <%= tabbed_nav_menu(sub_nav, 'hosts-navigation', 'nav nav-tabs inner-nav', 'sub-tab') %>
10
+
11
+ <div id="hosts-navigation-tabs" class="tab-content">
12
+ <div class="tab-pane active in" id="deployed-hosts">
13
+ <%= render :partial => "deployed_hosts", :locals => { :hosts => deployed_hosts } %>
14
+ </div>
15
+ <div class="tab-pane" id="assigned-hosts">
16
+ <%= render :partial => "assigned_hosts", :locals => { :hosts => assigned_hosts } %>
17
+ </div>
18
+ <div class="tab-pane" id="free-hosts">
19
+ <%= render :partial => "free_hosts", :locals => { :hosts => free_hosts } %>
20
+ </div>
21
+ </div>
@@ -0,0 +1 @@
1
+ <h1>TODO: Deployment Networking Tab</h1>
@@ -0,0 +1,95 @@
1
+ <div style="width: 100%; display: inline-block;">
2
+ <%= edit_textfield(@deployment, :description, options={ :update_url => deployment_path(@deployment) }) %>
3
+ </div>
4
+
5
+ <div class="row tabbed_side_nav_table">
6
+ <ul class="nav nav-pills nav-stacked col-md-4" data-tabs="pills">
7
+ <h3><%= _("Deployment Roles") %></h3>
8
+ <% @deployment.child_hostgroups.deploy_order.each_with_index do |child_hostgroup, i| %>
9
+ <li>
10
+ <div class="col-xs-2">
11
+ <% if child_hostgroup.hosts.select { |h| !ForemanTasks::Lock.locked?(@deployment, nil) && h.open_stack_deployed? }.count > 0 %>
12
+ <a href="#<%= child_hostgroup.name.parameterize.underscore %>_deployed_hosts" data-toggle="tab" class="roles_list">
13
+ <span><%= child_hostgroup.hosts.select { |h| !ForemanTasks::Lock.locked?(@deployment, nil) && h.open_stack_deployed? }.count %></span>
14
+ </a>
15
+ <% else %>
16
+ <span><%= child_hostgroup.hosts.select { |h| !ForemanTasks::Lock.locked?(@deployment, nil) && h.open_stack_deployed? }.count %></span>
17
+ <% end %>
18
+ </div>
19
+ <div class="col-xs-6">
20
+ <div class="group_name">
21
+ <%= child_hostgroup.name %>
22
+ </div>
23
+ </div>
24
+ <div class="col-xs-2">
25
+ <% if child_hostgroup.hosts.select { |h| !(!ForemanTasks::Lock.locked?(@deployment, nil) && h.open_stack_deployed?) }.count > 0 %>
26
+ <a href="#<%= child_hostgroup.name.parameterize.underscore %>_assigned_hosts" data-toggle="tab" class="roles_list">
27
+ <% unless @deployment.in_progress? %>
28
+ <i class="glyphicon glyphicon-time"></i>
29
+ <% else %>
30
+ <%= image_tag '/assets/spinner.gif' %>
31
+ <% end %>
32
+ <span><%= child_hostgroup.hosts.select { |h| !(!ForemanTasks::Lock.locked?(@deployment, nil) && h.open_stack_deployed?) }.count %></span>
33
+ </a>
34
+ <% end %>
35
+ </div>
36
+ <div class="col-xs-2">
37
+ <a href="#<%= child_hostgroup.name.parameterize.underscore %>_free_hosts" data-toggle="tab" class="roles_list">
38
+ <i class="glyphicon glyphicon-plus"></i>
39
+ </a>
40
+ </div>
41
+ </li>
42
+ <% end %>
43
+ </ul>
44
+
45
+ <div class="tab-content col-md-8">
46
+ <% @deployment.child_hostgroups.deploy_order.each_with_index do |child_hostgroup, i| %>
47
+ <%= render :partial => "free_hosts_table", :locals => { :deployment => @deployment,
48
+ :hostgroup => @hostgroup,
49
+ :child_hostgroup => child_hostgroup } %>
50
+ <%= render :partial => "assigned_hosts_table", :locals => { :deployment => @deployment,
51
+ :hostgroup => @hostgroup,
52
+ :child_hostgroup => child_hostgroup } %>
53
+ <%= render :partial => "deployed_hosts_table", :locals => { :deployment => @deployment,
54
+ :hostgroup => @hostgroup,
55
+ :child_hostgroup => child_hostgroup } %>
56
+ <% end %>
57
+ <%= render :partial => "deployment_summary", :locals => { :deployment => @deployment } %>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="modal fade" id="deploy_modal" tabindex="-1" role="dialog" aria-labelledby="Deploy" aria-hidden="true">
62
+ <div class="modal-dialog">
63
+ <div class="modal-content">
64
+ <div class="modal-header">
65
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
66
+ <h2 class="modal-title" id="deploy_modal_label">
67
+ <span class="glyphicon glyphicon-cloud-upload">
68
+ </span> <%= _("Deploy") %>
69
+ </h2>
70
+ </div>
71
+ <div class="modal-body">
72
+ <%= (_("This action will initiate the deployment of %s ") % "<strong>#{@deployment.name}</strong>").html_safe %>
73
+ <div class="deploy-changes">
74
+ <strong><%= _("Resulting OpenStack Deployment") %>:</strong>
75
+ <% @deployment.child_hostgroups.deploy_order.each do |child_hostgroup| %>
76
+ <div <%= "class=text-muted".html_safe if child_hostgroup.hosts.count == 0 %>>
77
+ <strong><%= "#{child_hostgroup.hosts.count} x" %></strong>
78
+ <%= "#{child_hostgroup.name}" %>
79
+ </div>
80
+ <% end %>
81
+ </div>
82
+ </div>
83
+ <div class="modal-footer">
84
+ <button type="button" class="btn btn-default" data-dismiss="modal"><%= _("Cancel") %></button>
85
+ <%= display_link_if_authorized(_("Deploy"),
86
+ hash_for_deploy_deployment_path,
87
+ :class => 'btn btn-primary',
88
+ :method => :post,
89
+ :data => { :disable_with => _("Deploying...") }) %>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+
95
+ <%= render :partial => "deployment_access_all_details_dialogue", :locals => { :deployment => @deployment } %>
@@ -0,0 +1,44 @@
1
+ <% title(@deployment.name,
2
+ edit_textfield(@deployment,
3
+ :name,
4
+ options={ :update_url => deployment_path(@deployment) }) +
5
+ content_tag(:small,
6
+ @deployment.ha? ? _("with High Availability") : _("without High Availability"),
7
+ :class => "text-muted")) %>
8
+
9
+ <% content_for(:top_actions) do %>
10
+ <%= link_to(_("Deploy"),
11
+ "",
12
+ :class => @deployment.in_progress? ? %w(btn btn-primary disabled) : %w(btn btn-primary),
13
+ :'data-toggle' => "modal",
14
+ :'data-target' => "#deploy_modal",
15
+ :id => "deploy_button") %>
16
+ <%= link_to(_("Revisit Setup Wizard"),
17
+ if @deployment.deployed?
18
+ deployment_step_path(deployment_id: @deployment, id: 'services_configuration')
19
+ else
20
+ deployment_steps_path(deployment_id: @deployment)
21
+ end,
22
+ :class => @deployment.in_progress? ? %w(btn btn-default disabled) : %w(btn btn-default)) %>
23
+
24
+ <% if Rails.env.development? %>
25
+ <div class="btn-group">
26
+ <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
27
+ <%= _('Populate with') %>
28
+ <span class="caret"></span>
29
+ </button>
30
+ <ul class="dropdown-menu" role="menu">
31
+ <li>
32
+ <%= display_link_if_authorized(_("Real assigned Hosts"), hash_for_populate_deployment_path(assign: true)) %>
33
+ </li>
34
+ <li>
35
+ <%= display_link_if_authorized(_("Real unassigned Hosts"), hash_for_populate_deployment_path) %>
36
+ </li>
37
+ <li>
38
+ <%= display_link_if_authorized(_("Fake unassigned Hosts"), hash_for_populate_deployment_path(:fake => true)) %>
39
+ </li>
40
+ </ul>
41
+ </div>
42
+ <%= help_path %>
43
+ <% end %>
44
+ <% end %>
@@ -0,0 +1,48 @@
1
+ <% if hosts.exists? %>
2
+ <div class="" id="table_free_hosts">
3
+ <%= form_tag(associate_host_deployment_path(id: @deployment), class: 'form-horizontal association') do |f| %>
4
+ <div class="pull-right top_actions">
5
+ <%= submit_tag _("Configure Networks"), :class => "btn btn-default btn-sm", :style => "visibility: hidden" %>
6
+ <%= submit_tag _("Assign Hosts"), :class => "btn btn-primary btn-sm",
7
+ :style => "visibility: hidden" %>
8
+ </div>
9
+ <h4 class="pull-left"><%= _("Free Hosts") %></h4>
10
+ <p class="clearfix"></p>
11
+ <table class="table table-bordered table-striped table-condensed">
12
+ <thead>
13
+ <tr>
14
+ <th class="ca">
15
+ <%= tag :input, type: 'checkbox', class: 'check_all' %>
16
+ </th>
17
+ <th><%= sort :name, :as => _('Name') %></th>
18
+ <th class="hidden-s hidden-xs"><%= sort :ip, :as => _('IP Address') %></th>
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ <% hosts.each do |host| %>
23
+ <tr class="checkbox_highlight">
24
+ <td class="ca">
25
+ <%= check_box_tag 'host_ids[]',
26
+ host.id,
27
+ false,
28
+ id: "host_ids_#{host.id}" %>
29
+ </td>
30
+ <td class="ellipsis">
31
+ <%= host_label(host) %>
32
+ </td>
33
+ <td class="hidden-s hidden-xs"><%= host.ip %></td>
34
+ </tr>
35
+ <% end %>
36
+ </tbody>
37
+ </table>
38
+ <% end %>
39
+ </div>
40
+ <% else %>
41
+ <div class="association">
42
+ <h4><%= _("Free Hosts") %></h4>
43
+ <div class="alert alert-warning">
44
+ <span class="glyphicon glyphicon-warning-sign">&nbsp;</span>
45
+ <%= _("All available hosts have been already assigned.") %>
46
+ </div>
47
+ </div>
48
+ <% end %>