diff --git a/app/controllers/power_distribution_units_controller.rb b/app/controllers/power_distribution_units_controller.rb
new file mode 100644
index 000000000..bd7b1666b
--- /dev/null
+++ b/app/controllers/power_distribution_units_controller.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+class PowerDistributionUnitsController < ApplicationController
+ before_action :set_pdu, only: %i[show edit update destroy destroy_connections]
+
+ def index
+ @pdus = Server.only_pdus.includes(:frame, :room, :islet, bay: :frames, modele: :category)
+ .references(:room, :islet, :bay, modele: :category)
+ .order(:name)
+ @filter = ProcessorFilter.new(@pdus, params)
+
+ @pdus = @filter.results
+ @search_params = search_params
+
+ respond_to do |format|
+ format.json
+ format.html { @pagy, @pdus = pagy(@pdus) }
+ end
+ end
+
+ def show; end
+
+ def new
+ @pdu = Server.new
+ end
+
+ def edit; end
+
+ def create
+ @pdu = Server.new(pdu_params)
+
+ respond_to do |format|
+ if @pdu.save
+ format.html { redirect_to power_distribution_unit_path(@pdu), notice: t(".flashes.created") }
+ format.json { render :show, status: :created, location: @pdu }
+ else
+ format.html { render :new }
+ format.json { render json: @pdu.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def update
+ respond_to do |format|
+ if @pdu.update(pdu_params)
+ format.html { redirect_to power_distribution_unit_path(@pdu), notice: t(".flashes.updated") }
+ format.json { render :show, status: :ok, location: @pdu }
+ else
+ format.html { render :edit }
+ format.json { render json: @pdu.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def destroy
+ respond_to do |format|
+ if @pdu.destroy
+ format.html { redirect_to power_distribution_units_path(search_params), notice: t(".flashes.destroyed") }
+ format.json { head :no_content }
+ else
+ format.html { redirect_to power_distribution_units_path(search_params), alert: t(".flashes.not_destroyed") }
+ format.json { head :bad_request }
+ end
+ end
+ end
+
+ def duplicate
+ @original_pdu = Server.friendly.find(params[:id].to_s.downcase)
+ @pdu = @original_pdu.deep_dup
+ end
+
+ def destroy_connections
+ if @pdu.destroy_connections!
+ flash[:notice] = t(".flashes.connections_destroyed")
+ else
+ flash[:alert] = t(".flashes.connections_not_destroyed")
+ end
+
+ redirect_to power_distribution_unit_path(@pdu)
+ end
+
+ private
+
+ def set_pdu
+ @pdu = Server.friendly_find_by_numero_or_name(params[:id])
+ end
+
+ def pdu_params
+ params.expect(
+ power_distribution_unit: [
+ :photo, :comment, :position, :frame_id, :gestion_id, :fc_futur, :rj45_cm, :name, :modele_id, :numero, :critique,
+ :domaine_id, :fc_total, :fc_utilise, :rj45_total, :rj45_utilise, :rj45_futur, :ipmi_utilise, :ipmi_futur,
+ :ipmi_dedie,
+ :frame, # TODO: Check if it should be removed or if it's used somewhere
+ { cards_attributes: [%i[composant_id card_type_id twin_card_id orientation name first_position _destroy id]] },
+ { documents_attributes: [%i[document id _destroy]] },
+ ]
+ )
+ end
+
+ def search_params
+ params.permit(:sort, :sort_by, :page, :per_page, :q,
+ network_types: [], bay_ids: [], islet_ids: [], room_ids: [], frame_ids: [], cluster_ids: [],
+ gestion_ids: [], domaine_ids: [], modele_ids: [], server_state_ids: [], stack_ids: [])
+ end
+end
diff --git a/app/controllers/servers_controller.rb b/app/controllers/servers_controller.rb
index 42fbfe2a3..21fd34a61 100644
--- a/app/controllers/servers_controller.rb
+++ b/app/controllers/servers_controller.rb
@@ -13,7 +13,7 @@ def index
logger.warn("DEPRECATION WARNING: Search with 'name' is now deprecated. Use 'q' instead.")
end
- @servers = Server.includes(:frame, :room, :islet, bay: :frames, modele: :category)
+ @servers = Server.no_pdus.includes(:frame, :room, :islet, bay: :frames, modele: :category)
.references(:room, :islet, :bay, modele: :category)
.order(:name)
@filter = ProcessorFilter.new(@servers, params)
@@ -27,27 +27,6 @@ def index
end
end
- def grid
- @servers = ServersGrid.new(params[:servers_grid])
- end
-
- def sort
- room = Room.find_by_name(params[:room]) unless params[:room].include?('non ')
- frame = room.frames.where('islets.name = ? AND frames.name = ?', params[:islet], params[:frame]).first
- positions = params[:positions].split(',')
-
- params[:server].each_with_index do |id, index|
- if positions[index].present?
- server = Server.find_by_id(id)
- new_params = { position: positions[index] }
- new_params[:frame_id] = frame.id if frame.present?
-
- server.update(new_params)
- end
- end if params[:server].present?
- head :ok # render empty body, status only
- end
-
def show; end
def new
@@ -82,6 +61,39 @@ def update
end
end
+ def destroy
+ respond_to do |format|
+ if @server.destroy
+ format.html { redirect_to servers_path(search_params), notice: t(".flashes.destroyed") }
+ format.json { head :no_content }
+ else
+ format.html { redirect_to servers_path(search_params), alert: t(".flashes.not_destroyed") }
+ format.json { head :bad_request }
+ end
+ end
+ end
+
+ def grid
+ @servers = ServersGrid.new(params[:servers_grid])
+ end
+
+ def sort
+ room = Room.find_by_name(params[:room]) unless params[:room].include?('non ')
+ frame = room.frames.where('islets.name = ? AND frames.name = ?', params[:islet], params[:frame]).first
+ positions = params[:positions].split(',')
+
+ params[:server].each_with_index do |id, index|
+ if positions[index].present?
+ server = Server.find_by_id(id)
+ new_params = { position: positions[index] }
+ new_params[:frame_id] = frame.id if frame.present?
+
+ server.update(new_params)
+ end
+ end if params[:server].present?
+ head :ok # render empty body, status only
+ end
+
def import_csv; end
def import
@@ -96,18 +108,6 @@ def import
end
end
- def destroy
- respond_to do |format|
- if @server.destroy
- format.html { redirect_to servers_path(search_params), notice: t(".flashes.destroyed") }
- format.json { head :no_content }
- else
- format.html { redirect_to servers_path(search_params), alert: t(".flashes.not_destroyed") }
- format.json { head :bad_request }
- end
- end
- end
-
def duplicate
@original_server = Server.friendly.find(params[:id].to_s.downcase)
@server = @original_server.deep_dup
diff --git a/app/models/modele.rb b/app/models/modele.rb
index dfdf3c9c9..56b55ee3c 100644
--- a/app/models/modele.rb
+++ b/app/models/modele.rb
@@ -24,6 +24,8 @@ class Modele < ApplicationRecord
scope :sorted, -> { order(:name) }
scope :with_servers, -> { joins(:servers).uniq }
scope :glpi_synchronizable, -> { where(category: Category.glpi_synchronizable) }
+ scope :no_pdus, -> { joins(:category).where("categories.name<>'Pdu'") }
+ scope :only_pdus, -> { joins(:category).where("categories.name='Pdu'").order(:name) }
def self.all_sorted
Modele.includes(:manufacturer).all.sort { |f1, f2| f1.name_with_brand.capitalize <=> f2.name_with_brand.capitalize }
diff --git a/app/models/server.rb b/app/models/server.rb
index e680d2951..8b1f559a6 100644
--- a/app/models/server.rb
+++ b/app/models/server.rb
@@ -40,23 +40,23 @@ class Server < ApplicationRecord
validate :validate_network_types_values
accepts_nested_attributes_for :cards,
- :allow_destroy => true,
- :reject_if => :all_blank
+ allow_destroy: true,
+ reject_if: :all_blank
accepts_nested_attributes_for :disks,
- :allow_destroy => true,
- :reject_if => :all_blank
+ allow_destroy: true,
+ reject_if: :all_blank
accepts_nested_attributes_for :memory_components,
- :allow_destroy => true,
- :reject_if => :all_blank
+ allow_destroy: true,
+ reject_if: :all_blank
accepts_nested_attributes_for :documents,
- :allow_destroy => true,
- :reject_if => :all_blank
+ allow_destroy: true,
+ reject_if: :all_blank
normalizes :network_types, with: ->(values) { values.compact_blank }
before_create :set_default_network_types
- scope :sorted, -> { order(:position => :desc) }
+ scope :sorted, -> { order(position: :desc) }
scope :sorted_by_name, -> { order('LOWER(name) ASC') }
scope :glpi_synchronizable, -> { joins(modele: :category).merge(Modele.glpi_synchronizable) }
diff --git a/app/views/cables/index.html.erb b/app/views/cables/index.html.erb
index c7f53034b..f104e65ee 100644
--- a/app/views/cables/index.html.erb
+++ b/app/views/cables/index.html.erb
@@ -97,7 +97,7 @@
<%= cable.decorated.server_connected_with_link(from_connection, from: true) %>
<%= cable.decorated.draw_port(from_connection) %>
-
+
<%= cable.decorated.draw_port(to_connection) %>
<%= cable.decorated.server_connected_with_link(to_connection) %>
diff --git a/app/views/layouts/_sidebar.html.erb b/app/views/layouts/_sidebar.html.erb
index 5c6b5c515..004c4dc33 100644
--- a/app/views/layouts/_sidebar.html.erb
+++ b/app/views/layouts/_sidebar.html.erb
@@ -45,7 +45,7 @@
-
+
+
+ <%= link_to t("power_distribution_units.index.title"),
+ power_distribution_units_path,
+ class: class_names("mydcim-sidebar-links-link d-inline-block rounded",
+ active: controller.controller_name == "power_distribution_units") %>
+
<%= link_to AirConditioner.model_name.human.pluralize,
air_conditioners_path,
diff --git a/app/views/power_distribution_units/_form.html.erb b/app/views/power_distribution_units/_form.html.erb
new file mode 100644
index 000000000..e4680c28e
--- /dev/null
+++ b/app/views/power_distribution_units/_form.html.erb
@@ -0,0 +1,192 @@
+<%
+ is_edit = controller.action_name == "edit"
+ is_new = !is_edit
+
+ dynamic_url = is_edit ? power_distribution_unit_path(@pdu) : power_distribution_units_path
+%>
+
+<%= form_for(@pdu, as: :power_distribution_unit,
+ url: dynamic_url,
+ html: { class: "col-12 col-md-10 col-lg-8 mx-auto", role: "form" }) do |f| %>
+ <%= render FormErrorsComponent.new(@pdu) %>
+
+ <%= render CardComponent.new(extra_classes: "bg-body-tertiary") do |card| %>
+ <% card.with_header do %>
+ <%= t("show.cards.identification") %>
+ <% end %>
+
+
+
+
+ <% end %>
+
+ <%= render CardComponent.new(extra_classes: "mt-4 bg-body-tertiary") do |card| %>
+ <% card.with_header do %>
+ <%= Modele.model_name.human %>
+ <% end %>
+
+
+
+
+ <% end %>
+
+ <%= render CardComponent.new(extra_classes: "mt-4 bg-body-tertiary") do |card| %>
+ <% card.with_header do %>
+ <%= t("show.cards.features") %>
+ <% end %>
+
+
+
+
+ <% end %>
+
+ <%= render CardComponent.new(type: :primary, extra_classes: "mt-4 bg-body-tertiary") do |card| %>
+ <% card.with_header do %>
+ <%= t("layouts.sidebar.location.title") %>
+ <% end %>
+
+
+
+
+ <% end %>
+
+ <%= render CardComponent.new(type: :success, extra_classes: "mt-4 bg-body-tertiary") do |card| %>
+ <% card.with_header do %>
+ <%= t("layouts.sidebar.exploitation.title") %>
+ <% end %>
+
+
+
+
+ <% end %>
+
+
+ <% if @pdu.modele %>
+ <% @pdu.modele.enclosures.each do |enclosure| %>
+
+
+ <%= f.fields_for :cards, f.object.cards.klass.new, child_index: '__NEW_RECORD__' do |card_fields| %>
+ <%= render partial: "servers/card_fields", locals: { f: card_fields, server: @pdu, enclosure: enclosure } %>
+ <% end %>
+
+
+ <%= render CardComponent.new(extra_classes: "bg-body") do |card| %>
+ <% card.with_header do %>
+ <%= "#{Enclosure.model_name.human} #{enclosure.position}" %>
+ <% end %>
+
+
+ <% cards = @pdu.cards.select { |c| c.composant.enclosure == enclosure }
+ .sort_by! { |c| c.composant.position } %>
+ <%= f.fields_for :cards, cards do |card_fields| %>
+ <%= render partial: "servers/card_fields", locals: { f: card_fields, server: @pdu, enclosure: enclosure } %>
+ <% end %>
+
+
+
+
+ <% card.with_footer do %>
+
+ <% end %>
+ <% end %>
+
+ <% end %>
+ <% end %>
+
+ <%= render CardComponent.new(extra_classes: "bg-body-tertiary mt-4") do |card| %>
+
+ <% end %>
+
+
+
+ <%= render CardComponent.new(extra_classes: "bg-body-tertiary mt-4") do |card| %>
+
+ <% end %>
+
+ <%#= render Form::ActionsComponent.new(f) %>
+
+ <%= link_to(t("action.cancel"), dynamic_url, class: "btn btn-outline-secondary me-2") %>
+ <%= f.submit(class: class_names("btn", 'btn-info': is_edit, 'btn-success': is_new)) %>
+
+<% end %>
diff --git a/app/views/power_distribution_units/duplicate.html.erb b/app/views/power_distribution_units/duplicate.html.erb
new file mode 100644
index 000000000..7b333f880
--- /dev/null
+++ b/app/views/power_distribution_units/duplicate.html.erb
@@ -0,0 +1,26 @@
+<% provide(:title, "#{t("power_distribution_units.index.title")} | #{@original_pdu.name} | #{t("action.duplicate")}") %>
+<% breadcrumb_steps = {
+ t("power_distribution_units.index.title") => power_distribution_units_path,
+ @original_pdu.name => power_distribution_unit_path(@original_pdu),
+ t("action.duplicate") => ""
+} %>
+
+<%#= render Page::HeadingComponent.new(
+ resource: @original_pdu, title: t(".title", pdu_name: @original_pdu.name), breadcrumb_steps:
+) %>
+<%= render Page::HeadingComponent.new(
+ title: t(".title", pdu_name: @original_pdu.name), breadcrumb_steps:, back_button_url: power_distribution_units_path
+) do |heading| %>
+ <% heading.with_right_content do %>
+
+ <%= render ButtonComponent.new(t("action.show"),
+ url: power_distribution_unit_path(@original_pdu),
+ variant: :primary,
+ icon: "eye",
+ is_responsive: true) %>
+
+ <% end %>
+<% end %>
+
+ <%= render "form" %>
+
diff --git a/app/views/power_distribution_units/edit.html.erb b/app/views/power_distribution_units/edit.html.erb
new file mode 100644
index 000000000..7c9d6cf03
--- /dev/null
+++ b/app/views/power_distribution_units/edit.html.erb
@@ -0,0 +1,23 @@
+<% provide(:title, "#{t("power_distribution_units.index.title")} | #{@pdu.name} | #{t(".title")}") %>
+<% breadcrumb_steps = {
+ t("power_distribution_units.index.title") => power_distribution_units_path, # TODO: prefer use ActiveRecord translation of Modele name
+ @pdu.name => power_distribution_unit_path(@pdu),
+ t("action.edit") => ""
+} %>
+
+<%= render Page::HeadingComponent.new(
+ title: t(".title"), breadcrumb_steps:, back_button_url: power_distribution_unit_path(@pdu)
+) do |heading| %>
+ <% heading.with_right_content do %>
+
+ <%= render ButtonComponent.new(t("action.show"),
+ url: power_distribution_unit_path(@pdu),
+ variant: :primary,
+ icon: "eye",
+ is_responsive: true) %>
+
+ <% end %>
+<% end %>
+
+ <%= render "form" %>
+
diff --git a/app/views/power_distribution_units/index.html.erb b/app/views/power_distribution_units/index.html.erb
new file mode 100644
index 000000000..acd911416
--- /dev/null
+++ b/app/views/power_distribution_units/index.html.erb
@@ -0,0 +1,146 @@
+<% provide(:title, t(".title")) # TODO: prefer use ActiveRecord translation of Modele name %>
+
+<%= render Page::HeadingComponent.new(title: t(".title"), breadcrumb_steps: { t(".title") => "" } ) do |heading| %>
+ <% heading.with_right_content do %>
+ <%= render ButtonComponent.new(t(".new_power_distributon_unit"),
+ url: new_power_distribution_unit_path,
+ variant: :success,
+ icon: "plus-lg",
+ is_responsive: true) %>
+ <% end %>
+<% end %>
+
+
+ <%= render FilterComponent.new(@filter) do |c| %>
+ <% c.with_form do |f| %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <% end %>
+ <% end %>
+
+ <%= turbo_frame_tag(dom_id(Server, :table), data: { turbo_action: :advance }) do %>
+
+ <%= render List::DataTableComponent.new(@pdus) do |table| %>
+ <% table.with_column(Server.human_attribute_name(:name), sort_by: :name) do |pdu| %>
+ <%= link_to pdu.name, power_distribution_unit_path(pdu), class: "fw-bold", data: { turbo_frame: :_top } %>
+ <% end %>
+
+ <% table.with_column(Server.human_attribute_name(:numero), sort_by: :numero) do |pdu| %>
+ <%= link_to pdu.numero, power_distribution_unit_path(pdu), class: "fw-bold", data: { turbo_frame: :_top } %>
+ <% end %>
+
+ <% table.with_column(Server.human_attribute_name(:room), sort_by: :"rooms.name") do |pdu| %>
+ <%= link_to pdu.room, room_path(pdu.room), data: { turbo_frame: :_top } if pdu.room %>
+ <% end %>
+
+ <% table.with_column(Islet.model_name.human, sort_by: :"islets.name") do |pdu| %>
+ <%= link_to pdu.islet, islet_path(pdu.islet), data: { turbo_frame: :_top } if pdu.islet %>
+ <% end %>
+
+ <% table.with_column(Bay.model_name.human, sort_by: :"bays.id") do |pdu| %>
+ <%= link_to pdu.bay, bay_path(pdu.bay), data: { turbo_frame: :_top } if pdu.bay %>
+ <% end %>
+
+ <% table.with_column(style: "min-width: 100px; width: 100px") do |pdu| %>
+
+ <%= link_to duplicate_power_distribution_unit_path(pdu), class: "btn btn-success", data: { turbo_frame: :_top } do %>
+ " aria-hidden="true"
+ data-controller="tooltip"
+ data-bs-placement="left">
+ <%= t("action.duplicate") %>
+ <% end %>
+ <%= link_to edit_power_distribution_unit_path(pdu), class: "btn btn-info", data: { turbo_frame: :_top } do %>
+ " aria-hidden="true"
+ data-controller="tooltip"
+ data-bs-placement="left">
+ <%= t("action.edit") %>
+ <% end %>
+ <%= link_to power_distribution_unit_path(pdu, @search_params),
+ method: :delete,
+ data: { turbo_frame: :_top, confirm: t("action.confirm") },
+ class: "btn btn-danger" do %>
+ " aria-hidden="true"
+ data-controller="tooltip"
+ data-bs-placement="left">
+ <%= t("action.delete") %>
+ <% end %>
+
+ <% end %>
+ <% end %>
+
+ <% if @pagy.pages > 1 %>
+ <%== pagy_bootstrap_nav(@pagy) %>
+ <% end %>
+
+ <% end %>
+
diff --git a/app/views/power_distribution_units/index.json.jbuilder b/app/views/power_distribution_units/index.json.jbuilder
new file mode 100644
index 000000000..20474a8b7
--- /dev/null
+++ b/app/views/power_distribution_units/index.json.jbuilder
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+json.array!(@pdus) do |pdu|
+ json.extract! pdu, :id, :name, :modele_id,
+ :numero, :cluster, :critique
+ json.url power_distribution_unit_url(pdu, format: :json)
+end
diff --git a/app/views/power_distribution_units/new.html.erb b/app/views/power_distribution_units/new.html.erb
new file mode 100644
index 000000000..b6add08e4
--- /dev/null
+++ b/app/views/power_distribution_units/new.html.erb
@@ -0,0 +1,14 @@
+<% provide(:title, "#{t("power_distribution_units.index.title")} | #{t("action.create")}") # TODO: prefer use ActiveRecord translation of Modele name %>
+<% breadcrumb_steps = {
+ t("power_distribution_units.index.title") => power_distribution_units_path,
+ t("action.create") => ""
+} %>
+
+<%#= render Page::HeadingNewComponent.new(resource: @server, title: t(".title"), breadcrumb_steps:) %>
+<%= render Page::HeadingComponent.new(
+ title: t(".title"), breadcrumb_steps:, back_button_url: power_distribution_units_path
+) %>
+
+
+ <%= render "form" %>
+
diff --git a/app/views/power_distribution_units/show.html.erb b/app/views/power_distribution_units/show.html.erb
new file mode 100644
index 000000000..4a6cb5724
--- /dev/null
+++ b/app/views/power_distribution_units/show.html.erb
@@ -0,0 +1,375 @@
+<% title = @pdu.name %>
+<% provide(:title, "#{t("power_distribution_units.index.title")} | #{title}") %>
+
+<% breadcrumb_steps = {
+ t("power_distribution_units.index.title") => power_distribution_units_path, # TODO: prefer use ActiveRecord translation of Modele name
+ @pdu.name => ""
+} %>
+
+<%#<%= render Page::HeadingShowComponent.new(resource: @pdu, title: title, breadcrumb_steps:) do |heading| %>
+ <%#<% heading.with_extra_buttons do %>
+ <%#<%= render ButtonComponent.new(t("action.duplicate"),
+ url: duplicate_power_distribution_unit_path(@pdu.slug),
+ variant: :success,
+ icon: "copy",
+ is_responsive: true,
+ extra_classes: "me-2") %>
+ <% # end %>
+<% # end %>
+
+<%= render Page::HeadingComponent.new(
+ title: title, breadcrumb_steps:, back_button_url: power_distribution_units_path
+) do |heading| %>
+ <% heading.with_right_content do %>
+
+ <%= render ButtonComponent.new(t("action.duplicate"),
+ url: duplicate_power_distribution_unit_path(@pdu.slug),
+ variant: :success,
+ icon: "copy",
+ is_responsive: true,
+ extra_classes: "me-2") %>
+ <%= render ButtonComponent.new(t("action.edit"),
+ url: edit_power_distribution_unit_path(@pdu.slug),
+ variant: :info,
+ icon: "pencil",
+ is_responsive: true,
+ extra_classes: "me-2") %>
+
+ <% end %>
+<% end %>
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+ <% bgModeleColor = define_background_color(server: @pdu, mode: params["bg"]) %>
+
"
+ style="background-color:<%= bgModeleColor %> !important;">
+ <%= render partial: "servers/draw_server_compact_front", locals: { server: @pdu } %>
+
+
+
+
+ <%= render partial: "servers/draw_server_compact", locals: { server: @pdu } %>
+
+
+
+
+ <% if @pdu.comment.present? %>
+
+
+ <%= @pdu.comment %>
+
+
+ <% end %>
+
+
+
+ <%= render CardComponent.new do |card| %>
+ <% card.with_header do %>
+ <%= t("show.cards.identification") %>
+ <% end %>
+
+
+ - <%= Server.human_attribute_name(:name) %>
+ - <%= @pdu.name %>
+
+ <% end %>
+
+ <%= render CardComponent.new(extra_classes: "mt-4") do |card| %>
+ <% card.with_header do %>
+ <%= Modele.model_name.human %>
+ <% end %>
+
+
+ - <%= Modele.model_name.human %>
+ -
+ <%= link_to @pdu.modele, modele_path(@pdu.modele) if @pdu.modele.try(:name) %>
+
+
+ - <%= Manufacturer.model_name.human %>
+ -
+ <% if @pdu.modele&.manufacturer.present? %>
+ <%= link_to @pdu.modele.manufacturer, manufacturer_path(@pdu.modele.manufacturer) %>
+ <% end %>
+ <% if (documentation_url = @pdu.documentation_url).present? %>
+ (
+ <%= link_to t(".documentation"), documentation_url, target: :_blank, rel: :noopener %>
+ )
+ <% end %>
+
+
+ - <%= Modele.human_attribute_name(:nb_elts) %>
+ - <%= @pdu.modele.try(:nb_elts) %>
+
+ - <%= Architecture.model_name.human %>
+ -
+ <% if @pdu.modele&.architecture.present? %>
+ <%= link_to @pdu.modele.architecture, architecture_path(@pdu.modele.architecture) %>
+ <% end %>
+
+
+ <% end %>
+
+ <%= render CardComponent.new(extra_classes: "mt-4") do |card| %>
+ <% card.with_header do %>
+ <%= t("show.cards.features") %>
+ <% end %>
+
+
+ - <%= Server.human_attribute_name(:numero) %>
+ - <%= @pdu.try(:numero) %>
+
+ - <%= Card.model_name.human.pluralize %>
+ -
+ <% @pdu.modele.composants.ordered.slots.each do |slot| %>
+ <% @pdu.cards.where(:composant_id => slot.id).each do |card| %>
+
+ <%= card.composant.name ? card.composant.name : card.composant.position %>
+ :
+ <%= "#{card.card_type.try(:name)} (#{card.card_type.try(:port_quantity)}#{card.card_type.try(:port_type).try(:name)})" %>
+
+ <% end %>
+ <% end if @pdu.modele.present? %>
+
+
+ <% end %>
+
+ <%= render CardComponent.new(type: :success, extra_classes: "mt-4") do |card| %>
+ <% card.with_header do %>
+ <%= t("layouts.sidebar.exploitation.title") %>
+ <% end %>
+
+
+ - <%= Gestion.model_name.human %>
+ - <%= link_to_if @pdu.gestion, @pdu.gestion %>
+
+ <% end %>
+
+ <% if @pdu.photo.attached? %>
+
+ <%= render CardComponent.new(extra_classes: "mt-4 d-none d-lg-flex") do |card| %>
+ <% card.with_header do %>
+ <%= Server.human_attribute_name(:photo) %>
+ <% end %>
+
+ <%= link_to @pdu.photo, rel: :noopener, target: :_blank do %>
+ <%= image_tag @pdu.photo.representation(resize_to_limit: [1200, 1200]), class: "w-100" %>
+ <% end %>
+ <% end %>
+ <% end %>
+
+
+
+ <%= render CardComponent.new(type: :primary) do |card| %>
+ <% card.with_header do %>
+ <%= t("layouts.sidebar.location.title") %>
+ <% end %>
+
+
+ - <%= Site.model_name.human %>
+ - <%= link_to @pdu.room.site, site_path(@pdu.room.site) %>
+
+ - <%= Room.model_name.human %>
+ -
+ <%= link_to @pdu.room.try(:name), @pdu.room %>
+ <%= link_to visualization_room_path(@pdu.room),
+ class: "btn btn-primary btn-sm",
+ data: { turbo_frame: :_top } do %>
+ ">
+ <%= t("visualization.title") %>
+ <% end %>
+
+
+ - <%= Islet.model_name.human %>
+ -
+ <%= link_to @pdu.islet.name_with_room, islet_path(@pdu.islet) %>
+ <%= link_to visualization_room_path(@pdu.room, islet: @pdu.islet.name),
+ class: "btn btn-primary btn-sm",
+ data: { turbo_frame: :_top } do %>
+ ">
+ <%= t("visualization.title") %>
+ <% end %>
+
+
+ - <%= Bay.model_name.human %>
+ -
+ <%= link_to @pdu.bay, bay_path(@pdu.bay) %>
+ <%= link_to visualization_bay_path(@pdu.bay),
+ class: "btn btn-primary btn-sm",
+ data: { turbo_frame: :_top } do %>
+ ">
+ <%= t("visualization.title") %>
+ <% end %>
+
+
+ - <%= Frame.model_name.human %>
+ -
+ <%= link_to @pdu.frame.try(:name), frame_path(@pdu.frame) %>
+ <%= link_to visualization_frame_path(@pdu.frame),
+ class: "btn btn-primary btn-sm",
+ data: { turbo_frame: :_top } do %>
+ ">
+ <%= t("visualization.title") %>
+ <% end %>
+
+
+ <% end %>
+
+ <%= render CardComponent.new(extra_classes: "mt-4") do |card| %>
+ <% card.with_header do %>
+ <%= t(".glpi_data") %>
+ <% end %>
+ <%
+ begin
+ Timeout.timeout(3) do
+ client = GlpiClient.new
+ computer = client.computer(serial: Rails.env.production? ? @pdu.numero : "AZERTY") %>
+ <% if computer.present? %>
+
+ - <%= t("glpi.computer.name") %>
+ - <%= computer.name %>
+
+ - <%= t("glpi.computer.id") %>
+ - <%= computer.id %>
+
+ - <%= t("glpi.computer.serial") %>
+ - <%= computer.serial %>
+
+ - <%= t("glpi.computer.contact") %>
+ - <%= computer.contact %>
+
+
+ - <%= t("glpi.computer.disks_count") %>
+ - <%= computer.hard_drives&.size %>
+
+ - <%= t("glpi.computer.disks_capacity") %>
+ - <%= number_to_human_size(computer.hard_drives_total_capacity * 1048576) %>
+
+ - <%= t("glpi.computer.memories_count") %>
+ - <%= computer.memories&.size %>
+
+ - <%= t("glpi.computer.memories_total") %>
+ - <%= number_to_human_size(computer.memories_total_size * 1048576) %>
+
+ - <%= t("glpi.computer.cpus") %>
+ -
+ <%= computer.processors.group_by { |p| p[1]["designation"] }
+ .map { |designation, processors| "#{processors.size} x #{designation}" }
+ .join(", ") %>
+
+
+ <% card.with_footer do %>
+ <%= link_to t(".glpi_visit_page"),
+ "#{Rails.application.credentials.glpi_url}/front/computer.form.php?id=#{computer.id}",
+ target: :_blank %>
+ <% end %>
+ <% else %>
+ <%= t(".no_match_serial", serial: @pdu.numero) %>
+ <% end %>
+ <% end
+ rescue Timeout::Error => e
+ Rails.logger.warn "WARNING: couldn't get GLPI data because the operation timed out"
+ rescue Exception => e
+ Rails.logger.warn "WARNING: couldn't get GLPI data because of an error: #{e.message}" %>
+ <%= t(".glpi_connection_error") %> :
<%= e&.message.to_s %>
+ <% end %>
+ <% end %>
+
+ <% if @pdu.photo.attached? %>
+
+ <%= render CardComponent.new(extra_classes: "d-lg-none mt-4") do |card| %>
+ <% card.with_header do %>
+ <%= Server.human_attribute_name(:photo) %>
+ <% end %>
+
+ <%= link_to @pdu.photo, rel: :noopener, target: :_blank do %>
+ <%= image_tag @pdu.photo.representation(resize_to_limit: [1200, 1200]), class: "w-100" %>
+ <% end %>
+ <% end %>
+ <% end %>
+
+ <% if @pdu.documents.present? %>
+ <%= render CardComponent.new(extra_classes: "mt-4") do |card| %>
+ <% card.with_header do %>
+ <%= t(".attached_documents") %>
+ <% end %>
+
+ <% @pdu.documents.each do |doc| %>
+ <%- next unless doc.document.present? %>
+ -
+ <%= link_to(doc.document.metadata["filename"], doc.document_url, { target: :_blank }) %>
+
+ <% end %>
+
+ <% end %>
+ <% end %>
+
+
+
+ <%= render ChangelogEntries::ObjectListComponent.new(@pdu) %>
+
+
+ <%= render HasManyTurboFrameComponent.new(
+ Connection.model_name.human.pluralize,
+ url: cables_path(server_ids: @pdu.id),
+ frame_id: dom_id(Connection, :table),
+ extra_classes: "bg-body-tertiary"
+ ) do |c| %>
+ <% c.with_actions do %>
+ <%= render ButtonComponent.new(t(".destroy_connections"),
+ url: destroy_connections_server_path(@pdu),
+ icon: "trash",
+ variant: :danger,
+ size: :sm,
+ data: { confirm: t(".destroy_connections_confirmation") }) %>
+ <% end %>
+ <% end %>
+
+
+
+<%= render "ports/modal_edit_port" %>
diff --git a/app/views/power_distribution_units/show.json.jbuilder b/app/views/power_distribution_units/show.json.jbuilder
new file mode 100644
index 000000000..bafaeef1f
--- /dev/null
+++ b/app/views/power_distribution_units/show.json.jbuilder
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+json.extract! @pdu, :id, :name, :modele, :numero, :critique, :domaine, :gestion, :created_at, :updated_at
+
+json.cards @pdu.cards do |card|
+ json.extract! card, :id, :name, :first_position, :orientation, :card_type_id, :composant_id, :twin_card_id
+ json.ports card.ports.order(:position), :id, :position, :vlans, :color, :cablename
+end
diff --git a/app/views/servers/_form.html.erb b/app/views/servers/_form.html.erb
index 03d31bfbe..c78362cd7 100644
--- a/app/views/servers/_form.html.erb
+++ b/app/views/servers/_form.html.erb
@@ -23,7 +23,7 @@