foreman_datacenter 1.23.0 → 1.24.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/foreman_datacenter/rack_show.css +56 -0
  3. data/app/controllers/foreman_datacenter/application_controller.rb +2 -0
  4. data/app/controllers/foreman_datacenter/rack_groups_controller.rb +1 -0
  5. data/app/controllers/foreman_datacenter/racks_controller.rb +9 -1
  6. data/app/helpers/foreman_datacenter/racks_helper.rb +96 -0
  7. data/app/models/foreman_datacenter/device.rb +38 -0
  8. data/app/models/foreman_datacenter/rack.rb +103 -1
  9. data/app/views/foreman_datacenter/devices/show.html.erb +1 -1
  10. data/app/views/foreman_datacenter/racks/_device_position.html.erb +10 -1
  11. data/app/views/foreman_datacenter/racks/_device_position_new.html.erb +91 -0
  12. data/app/views/foreman_datacenter/racks/show.html.erb +6 -4
  13. data/config/routes.rb +21 -1
  14. data/lib/foreman_datacenter/version.rb +1 -1
  15. data/test/controllers/foreman_datacenter/device_roles_controller_test.rb +133 -0
  16. data/test/controllers/foreman_datacenter/device_types_contoller_test.rb +108 -47
  17. data/test/controllers/foreman_datacenter/devices_controller_test.rb +132 -0
  18. data/test/controllers/foreman_datacenter/manufacturers_controller_test.rb +133 -0
  19. data/test/controllers/foreman_datacenter/platforms_contoller_test.rb +91 -48
  20. data/test/controllers/foreman_datacenter/rack_groups_contoller_test.rb +90 -43
  21. data/test/controllers/foreman_datacenter/racks_contoller_test.rb +111 -42
  22. data/test/controllers/foreman_datacenter/sites_contoller_test.rb +108 -81
  23. data/test/datacenter_helper.rb +8 -1
  24. data/test/factories/device.rb +5 -0
  25. data/test/factories/device_role.rb +6 -0
  26. data/test/factories/device_type.rb +30 -0
  27. data/test/factories/rack_group.rb +13 -14
  28. data/test/fixtures/foreman_datacenter/device_roles.yml +0 -1
  29. data/test/integration/foreman_datacenter/device_role_test.rb +35 -0
  30. data/test/integration/foreman_datacenter/device_type_test.rb +33 -0
  31. data/test/integration/foreman_datacenter/manufacturer_test.rb +33 -0
  32. data/test/integration/foreman_datacenter/platform_test.rb +35 -0
  33. data/test/integration/foreman_datacenter/rack_group_test.rb +35 -25
  34. data/test/integration/foreman_datacenter/rack_test.rb +37 -25
  35. data/test/integration/foreman_datacenter/site_test.rb +39 -29
  36. data/test/unit/foreman_datacenter_test.rb +1 -1
  37. metadata +60 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2526b45caa397c2e070eea32e723b24e2d8e7cfccccc34dc325d9773ff612d8f
4
- data.tar.gz: 7c5125d05ac95756b6ecbd6940a5945b6270e6896f9b61827d1e6198c75d5a47
3
+ metadata.gz: 0b77182c1385f10fbeb5506db376a8fa29a079b4cebae2945ce99d07a3350aa7
4
+ data.tar.gz: 36f47250116e01a60c30a1be2503ff7c636e18bf7408fc75b00fd130b9eabc99
5
5
  SHA512:
6
- metadata.gz: 69fde6a95432be24db60c9d8d4445001e843d35482c5bd6e206d05c955f3f3bedad0dbc337ccbcd111ba388a1635f3d71e81c43fb2afc6a989d4f85c22fb960a
7
- data.tar.gz: d27d1b23ab5573c0d5920368b0528fb8818aaff7583311c1b0873309bc09c676c4f7a8084054b4d73a0dcfa81c1b60c9b5d1e0b34dc83ace4f40836b3f66e6c8
6
+ metadata.gz: 73e0482bff2ae27ec2b2f59ad59946c89ec6bc712362952e1f2fb77095b1953f0cce0e943506fae0814b94ccb4188172a50106e23b4b2789358886a1c9ffef04
7
+ data.tar.gz: a3ff1c963f5f22c2902a25e503b3218aa408b27edf7da7417545010b4faf3eaaeb81ae3c460d73fd6e08acd6496beceb9f0b1893f42589f3fe6e188de86b21a3
@@ -0,0 +1,56 @@
1
+ .rack-grid {
2
+ width: 100%;
3
+ border-bottom: 1px solid #d1d1d1;
4
+ border-left: 1px solid #d1d1d1;
5
+ display: inline-grid;
6
+ }
7
+
8
+ .item {
9
+ justify-content: center;
10
+ padding: 2px 10px 3px;
11
+ border-top: 1px solid #d1d1d1;
12
+ border-right: 1px solid #d1d1d1;
13
+ }
14
+
15
+ .item-dev {
16
+ position: relative;
17
+ text-align: center;
18
+ }
19
+
20
+ .dev-cell {
21
+ overflow: hidden;
22
+ text-overflow: ellipsis;
23
+ white-space: nowrap;
24
+ position: absolute;
25
+ height: inherit;
26
+ width: 90%;
27
+ text-align: center;
28
+ }
29
+
30
+ .a:hover {
31
+ overflow: visible;
32
+ }
33
+
34
+ .no-border {
35
+ border: none !important;
36
+ }
37
+
38
+ .panel-head-border {
39
+ border-top: 1px solid #ddd !important;
40
+ border-left: 1px solid #ddd !important;
41
+ border-right: 1px solid #ddd !important;
42
+ border-bottom: none !important;
43
+ }
44
+
45
+ .item-row {
46
+ white-space: nowrap;
47
+ text-overflow: ellipsis;
48
+ overflow: hidden;
49
+ }
50
+
51
+ .item-row:hover {
52
+ overflow: visible;
53
+ }
54
+
55
+ .item-empty-cell {
56
+ }
@@ -92,6 +92,8 @@ module ForemanDatacenter
92
92
  'move'
93
93
  when 'racks'
94
94
  'racks'
95
+ when 'export_to_csv'
96
+ 'export_to_csv'
95
97
  else
96
98
  super
97
99
  end
@@ -61,6 +61,7 @@ module ForemanDatacenter
61
61
  def racks
62
62
  @racks = @rack_group.racks.includes(devices: [:device_role])
63
63
  process_error redirect: rack_groups_path(@rack_group), error_msg: 'Current Rack Group haven\'t any Racks.' if @racks.empty?
64
+ @order = params[:order] || "desc"
64
65
  end
65
66
 
66
67
  def update_associated_objects
@@ -3,7 +3,7 @@ module ForemanDatacenter
3
3
  include Foreman::Controller::AutoCompleteSearch
4
4
  include ForemanDatacenter::Controller::Parameters::Rack
5
5
 
6
- before_action :find_resource, only: [:show, :edit, :update, :destroy, :devices, :move]
6
+ before_action :find_resource, only: [:show, :edit, :update, :destroy, :devices, :move, :export_to_csv]
7
7
 
8
8
  def index
9
9
  @racks = resource_base_search_and_page.includes(:site, :rack_group)
@@ -17,6 +17,7 @@ module ForemanDatacenter
17
17
 
18
18
  def show
19
19
  @rack = resource_base.includes(devices: [:device_role]).find(params[:id])
20
+ @order = params[:order] || "desc"
20
21
  end
21
22
 
22
23
  def new
@@ -64,6 +65,13 @@ module ForemanDatacenter
64
65
  process_error object: @rack, error_msg: 'Current Rack haven\'t any Devices.' if @devices.empty?
65
66
  end
66
67
 
68
+ def export_to_csv
69
+ data = @rack.format_for_csv
70
+ file = File.open("#{@rack.name}.csv", "r")
71
+ send_file(file)
72
+ file.close
73
+ end
74
+
67
75
  def update_associated_objects
68
76
  begin
69
77
  @rack = ForemanDatacenter::Rack.find(request.env['HTTP_REFERER'].split('/')[-2])
@@ -4,5 +4,101 @@ module ForemanDatacenter
4
4
  collection = rack.site ? rack.site.rack_groups : []
5
5
  options_from_collection_for_select(collection, 'id', 'name', rack.rack_group_id)
6
6
  end
7
+
8
+ def grid_u_desc(rack)
9
+ i = rack.height + 1
10
+ limit = 0
11
+ result = ""
12
+
13
+ while limit <= i do
14
+ if i == 0 && !rack.unpositioned_devices.empty?
15
+ concat content_tag(:div, "Unpositioned Devices")
16
+ else
17
+ if i != 0
18
+ if rack.child_devices.any? { |d| d.position == i }
19
+ concat content_tag :div, &-> do
20
+ concat "#{i}U"
21
+ rack.child_devices.each do |d|
22
+ if d.position == i
23
+ concat content_tag :strong, &-> do
24
+ concat link_to("Child", device_path(d))
25
+ end
26
+ end
27
+ end
28
+ end
29
+ else
30
+ concat content_tag(:div, "#{i}U")
31
+ end
32
+ end
33
+ end
34
+ i -= 1
35
+ end
36
+ end
37
+
38
+ def grid_empty_style(cell, id)
39
+ case cell[1]
40
+ when ["right"]
41
+ column = "grid-area: rack-#{id}-row-#{cell[0]}-right-empty"
42
+ when ["left"]
43
+ column = "grid-area: rack-#{id}-row-#{cell[0]}-left-empty"
44
+ when ["left", "right"] || ["right", "left"]
45
+ column = "grid-area: rack-#{id}-row-#{cell[0]}-full-empty"
46
+ end
47
+ return column
48
+ end
49
+
50
+ def margin_style(height)
51
+ result = "top: "
52
+ case height
53
+ when 1
54
+ result += "7%"
55
+ when 2
56
+ result += "30%"
57
+ when 3
58
+ result += "35%"
59
+ when 4
60
+ result += "40%"
61
+ else
62
+ result += "45%"
63
+ end
64
+ end
65
+
66
+ def grid_rack_style(device)
67
+ css_class_name = device.name.gsub(/[.@#\s]/, "_")
68
+ return "grid-area: #{css_class_name}"
69
+ end
70
+
71
+ def grid_rack_height(height)
72
+ return (height + 1)
73
+ end
74
+
75
+ def grid_template(grid, id, order)
76
+ result = []
77
+ gr = JSON.parse(grid)
78
+ gr = gr.to_a.reverse.to_h if order == "desc"
79
+
80
+ gr.each do |k,v|
81
+ row = "rack-#{id}-u#{k}"
82
+ if v.instance_of? Array
83
+ if k == "0"
84
+ row += " rack-#{id}-unpos-devs rack-#{id}-unpos-devs"
85
+ else
86
+ row += " rack-#{id}-row-#{k}-full-empty rack-#{id}-row-#{k}-full-empty"
87
+ end
88
+ else
89
+ if v.size > 1
90
+ row += " #{v['left']} #{v['right']}"
91
+ else
92
+ row += " #{v['left']} rack-#{id}-row-#{k}-right-empty" if v.key?("left")
93
+ row += " rack-#{id}-row-#{k}-left-empty #{v['right']}" if v.key?("right")
94
+ row += " #{v['full']} #{v['full']}" if v.key?("full")
95
+ end
96
+ end
97
+ result << row
98
+ end
99
+ final = result.join("' '").prepend("'") + "'"
100
+ return "grid-template-areas: #{final}; grid-template-columns: 22% 39% 39%"
101
+ end
102
+
7
103
  end
8
104
  end
@@ -34,6 +34,7 @@ module ForemanDatacenter
34
34
  validates :serial, length: { maximum: 50 }
35
35
  validates :rack_id, presence: true
36
36
  validates :position, numericality: { only_integer: true }, allow_nil: true
37
+ validate :correct_position
37
38
 
38
39
  after_create :create_interfaces
39
40
  after_create :import_interfaces_from_host
@@ -72,6 +73,10 @@ module ForemanDatacenter
72
73
  device_type.try(:subdevice_role) == 'Parent'
73
74
  end
74
75
 
76
+ def child?
77
+ device_type.try(:subdevice_role) == 'Child'
78
+ end
79
+
75
80
  def free_interfaces
76
81
  interfaces.where(mgmt_only: false).reject(&:connected?)
77
82
  end
@@ -139,6 +144,39 @@ module ForemanDatacenter
139
144
  end
140
145
  end
141
146
 
147
+ def correct_position
148
+ if position > 0
149
+ grid_template = JSON.parse(self.rack.grid_template_areas)
150
+ correct_pos = true
151
+ pos = []
152
+ sz = (size == 0 || size == 1) ? [position] : [*position..(position + (size - 1))]
153
+ sd = (side.nil?) ? "full" : side
154
+ sz.each do |i|
155
+ if grid_template["#{i}"].is_a?(Hash)
156
+ if grid_template["#{i}"].key?("full") && grid_template["#{i}"]["full"] != "device-#{self.id}"
157
+ correct_pos = false
158
+ pos << "#{i}(full)"
159
+ end
160
+ if sd == "full"
161
+ ["left", "right"].each do |s|
162
+ if grid_template["#{i}"].key?("#{s}") && grid_template["#{i}"]["#{s}"] != "device-#{self.id}"
163
+ correct_pos = false
164
+ pos << "#{i}(#{s})"
165
+ end
166
+ end
167
+ else
168
+ if grid_template["#{i}"].key?("#{sd}") && grid_template["#{i}"]["#{sd}"] != "device-#{self.id}"
169
+ correct_pos = false
170
+ pos << "#{i}(#{sd})"
171
+ end
172
+ end
173
+ end
174
+ end
175
+ pos.uniq!
176
+ errors.add(:position, "Positions: #{pos.join(', ')} already taken") if correct_pos == false
177
+ end
178
+ end
179
+
142
180
  private
143
181
 
144
182
  def create_interfaces
@@ -12,6 +12,7 @@ module ForemanDatacenter
12
12
  validates :facility_id, length: { maximum: 30 }
13
13
  validates :height, presence: true
14
14
  validates_numericality_of :height, only_integer: true, greater_than: 0
15
+ validate :correct_position
15
16
 
16
17
  scoped_search on: :name, complete_value: true
17
18
  scoped_search on: :height, validator: ScopedSearch::Validators::INTEGER
@@ -22,6 +23,7 @@ module ForemanDatacenter
22
23
  scoped_search in: :site, on: :name, complete_value: true, rename: :site
23
24
  scoped_search in: :rack_group, on: :name, complete_value: true, rename: :rack_group
24
25
 
26
+
25
27
  def device_at(position)
26
28
  devices.where(position: position).to_a
27
29
  end
@@ -43,7 +45,7 @@ module ForemanDatacenter
43
45
  end
44
46
 
45
47
  def unpositioned_devices
46
- devices.where(position: nil).to_a
48
+ devices.where(position: nil).or(devices.where(position: "0")).to_a
47
49
  end
48
50
 
49
51
  def devices_count
@@ -52,8 +54,108 @@ module ForemanDatacenter
52
54
  count
53
55
  end
54
56
 
57
+ def format_for_csv
58
+ positioned_devices = self.positioned_devices
59
+ unpositioned_devices = self.unpositioned_devices
60
+ csv_string = CSV.generate do |csv|
61
+ csv << ["positions", "left", "full", "right", "no side"]
62
+ positioned_devices.each do |i|
63
+ pos = i[0].map{|p| "#{p}"+"U"}.join(",")
64
+ if i[1].size > 0
65
+ sort = sort_for_csv(i[1])
66
+ csv << [pos, sort[0], sort[1], sort[2], sort[3]]
67
+ else
68
+ csv << [pos]
69
+ end
70
+ end
71
+ unless unpositioned_devices.empty?
72
+ csv << []
73
+ csv << ["Unpositioned", unpositioned_devices.map(&:name).join(",")]
74
+ end
75
+
76
+ end
77
+ File.open("#{self.name}.csv", "w") {|f| f << csv_string}
78
+ end
79
+
80
+ # Finding all empty cells for grid layout
81
+ def empty_cells
82
+ cells = {} # all cells of grid
83
+ devices_cells = {} # cells with devices
84
+ height.times.each do |i|
85
+ cells[i+1] = ["left", "right"]
86
+ end
87
+ devices.each do |d|
88
+ if d.size > 0
89
+ if !d.position.nil? && d.position != 0
90
+ s = (d.size == 0 || d.size == 1) ? 0 : (d.size - 1)
91
+ places = [*d.position..d.position+s]
92
+ places.each do |p|
93
+ devices_cells[p] = [] unless devices_cells.key?(p)
94
+ if (d.side == "full" || d.side.nil?)
95
+ ["left", "right"].each {|side| devices_cells[p] << side}
96
+ else
97
+ devices_cells[p] << d.side
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ devices_cells.each do |k,v|
104
+ v.each {|c| cells[k].delete(c)}
105
+ end
106
+ cells.each {|k,v| cells.except!(k) if cells[k] == []}
107
+ return cells
108
+ end
109
+
110
+ def grid_template_areas
111
+ result = {}
112
+ (height+1).times.each do |i|
113
+ if i == 0
114
+ result[0] = unpositioned_devices.map{|d| "device-#{d.id}"}
115
+ else
116
+ dev = {}
117
+ devices.each do |d|
118
+ if d.size > 0
119
+ if !d.position.nil? && d.position != 0
120
+ css_class_name = "device-#{d.id}"
121
+ s = (d.size == 1 || d.size == 0) ? 0 : (d.size - 1)
122
+ if [*d.position..(d.position + s)].include?(i)
123
+ if d.side.nil?
124
+ dev["full"] = css_class_name
125
+ else
126
+ dev["#{d.side}"] = css_class_name
127
+ end
128
+ result[i] = dev
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ (height+1).times.each do |i|
136
+ result[i] = [] if result[i].nil?
137
+ end
138
+ # result[0].reject! if result[0].empty?
139
+ return result.sort_by{|k,v| k}.to_h.to_json
140
+ end
141
+
142
+ def child_devices
143
+ devices.joins(:device_type).where("device_types.subdevice_role = ?", "Child")
144
+ end
145
+
55
146
  private
56
147
 
148
+ def sort_for_csv(device_array)
149
+ left, full, right, no_side = "", "", "", []
150
+ device_array.each do |d|
151
+ left = d.name if d.side == "left"
152
+ right = d.name if d.side == "right"
153
+ full = d.name if d.side == "full"
154
+ no_side << d.name if d.side == nil
155
+ end
156
+ [left, full, right, no_side.join(",")]
157
+ end
158
+
57
159
  def device_sorting(devices)
58
160
  devices.reverse.map { |d| [d[0].reverse, d[1]] }
59
161
  end
@@ -70,7 +70,7 @@
70
70
  </tr>
71
71
  <tr>
72
72
  <td><%= _("Position") %></td>
73
- <td><%= @device.positions.join(", ") %> / <%= @device.face %> / <%= @device.side %></td>
73
+ <td><%= (@device.position > 0) ? (@device.child? ? "#{@device.position} (Child)" : @device.positions.join(", ")) : 0 %> / <%= @device.face %> / <%= @device.side %></td>
74
74
  </tr>
75
75
  <tr>
76
76
  <td><%= _("Device Type") %></td>
@@ -1,5 +1,14 @@
1
1
  <div class="panel panel-default">
2
- <div class="panel-heading text-center"><h4 class="nonmargintop nonmarginbottom"><strong><%= link_to _("#{rack.name}"), rack_path(rack) %></strong></h4></div>
2
+ <div class="panel-heading text-center">
3
+ <h4 class="nonmargintop nonmarginbottom">
4
+ <strong>
5
+ <%= link_to _("#{rack.name}"), rack_path(rack) %>
6
+ </strong>
7
+ <strong>
8
+ <%= link_to 'Export to CSV', export_to_csv_rack_path(rack.id), class: "btn btn-primary btn-xs pull-right" %>
9
+ </strong>
10
+ </h4>
11
+ </div>
3
12
  <table class="<%= table_css_classes 'table-hover panel-body nonmarginbottom' %>">
4
13
  <tbody>
5
14
  <% rack.positioned_devices.each do |positions, devices| %>
@@ -0,0 +1,91 @@
1
+ <div class="panel panel-default no-border">
2
+ <div class="panel-heading text-center panel-head-border">
3
+ <h4 class="nonmargintop nonmarginbottom">
4
+ <strong>
5
+ <%= link_to rack.name, rack_path(rack) %>
6
+ </strong>
7
+ <strong>
8
+ <%= link_to 'Export to CSV', export_to_csv_rack_path(rack.id), class: "btn btn-primary btn-xs pull-right" %>
9
+ </strong>
10
+ <strong>
11
+ <% if @order == "asc" %>
12
+ <%= link_to 'DESC', url_for(), class: "btn btn-success btn-xs pull-left" %>
13
+ <% else %>
14
+ <%= link_to 'ASC', url_for(order: "asc"), class: "btn btn-success btn-xs pull-left" %>
15
+ <% end %>
16
+ </strong>
17
+ </h4>
18
+ </div>
19
+
20
+ <!-- Grid -->
21
+ <div class="rack-grid rack-<%= rack.id %>" style="<%= grid_template(rack.grid_template_areas, rack.id, @order) %>">
22
+
23
+ <!-- Rows -->
24
+ <% (rack.height+1).times.each do |i| %>
25
+ <% if i == 0 && !rack.unpositioned_devices.empty? %>
26
+ <div class="item item-row" style="grid-area: rack-<%= rack.id %>-u<%= i %>">
27
+ <%= i == 0 ? "Unpositioned Devices" : "#{i}U"%>
28
+ </div>
29
+ <% else %>
30
+ <% if i != 0 %>
31
+ <div class="item item-row" style="grid-area: rack-<%= rack.id %>-u<%= i %>">
32
+ <%= "#{i}U" %>
33
+ <% rack.child_devices.each do |d| %>
34
+ <% if d.position == i %>
35
+ <strong>
36
+ <%= link_to "Child", device_path(d), class: "btn btn-primary btn-xs pull-right" %>
37
+ </strong>
38
+ <% end %>
39
+ <% end %>
40
+ </div>
41
+ <% else %>
42
+ <% next %>
43
+ <% end %>
44
+ <% end %>
45
+ <% end %>
46
+
47
+ <!-- Empty cells -->
48
+ <% rack.empty_cells.each do |c| %>
49
+ <div class="item item-empty-cell" style="<%= grid_empty_style(c, rack.id)%>">
50
+ &nbsp;
51
+ </div>
52
+ <% end %>
53
+
54
+ <!-- Devices -->
55
+ <% rack.devices.each do |d| %>
56
+ <% if !d.position.nil? && d.size != 0 && d.position != 0 %>
57
+ <div class="item item-dev" style="grid-area: device-<%= d.id %>">
58
+ <div class="dev-cell" style="<%= margin_style(d.size) %>">
59
+ <div class="<%= "label " + d.device_role.color.downcase.gsub(" ","") %>" style="border-radius:40px;border:0px;">&nbsp;&nbsp;</div>
60
+ <%= link_to d.name, device_path(d) %>
61
+ <% if d.side.nil? %>
62
+ *
63
+ <% end %>
64
+ <% if d.size.nil? %>
65
+ **
66
+ <% end %>
67
+ </div>
68
+ </div>
69
+ <% end %>
70
+ <% end %>
71
+
72
+ <!-- Unpositioned devices -->
73
+ <% if rack.unpositioned_devices.count != 0 %>
74
+ <div class="item item-dev" style="grid-area: rack-<%= rack.id %>-unpos-devs">
75
+ <% rack.unpositioned_devices.each do |d| %>
76
+ <div class="<%= "label " + d.device_role.color.downcase.gsub(" ","") %>" style="border-radius:40px;border:0px;">&nbsp;&nbsp;</div>
77
+ <%= link_to d.name, device_path(d) %>
78
+ <% if d.side.nil? %>
79
+ *
80
+ <% end %>
81
+ <% if d.size.nil? %>
82
+ **
83
+ <% end %>
84
+ <br>
85
+ <% end %>
86
+ </div>
87
+ <% end %>
88
+
89
+ </div>
90
+
91
+ </div>