1090 lines
37 KiB
PHP
1090 lines
37 KiB
PHP
<?php
|
|
|
|
if (!defined('GLPI_ROOT')) {
|
|
die("Sorry. You can't access directly to this file");
|
|
}
|
|
|
|
/**
|
|
* ---------------------------------------------------------------------
|
|
* GLPI - Gestionnaire Libre de Parc Informatique
|
|
* Copyright (C) 2015-2020 Teclib' and contributors.
|
|
*
|
|
* http://glpi-project.org
|
|
*
|
|
* based on GLPI - Gestionnaire Libre de Parc Informatique
|
|
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
|
|
*
|
|
* ---------------------------------------------------------------------
|
|
*
|
|
* LICENSE
|
|
*
|
|
* This file is part of GLPI.
|
|
*
|
|
* GLPI is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GLPI is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
|
|
* ---------------------------------------------------------------------
|
|
**/
|
|
class Item_Rack extends CommonDBRelation {
|
|
|
|
static public $itemtype_1 = 'Rack';
|
|
static public $items_id_1 = 'racks_id';
|
|
static public $itemtype_2 = 'itemtype';
|
|
static public $items_id_2 = 'items_id';
|
|
static public $checkItem_2_Rights = self::DONT_CHECK_ITEM_RIGHTS;
|
|
static public $mustBeAttached_1 = false;
|
|
static public $mustBeAttached_2 = false;
|
|
|
|
static function getTypeName($nb = 0) {
|
|
return _n('Item', 'Item', $nb);
|
|
}
|
|
|
|
|
|
function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) {
|
|
$nb = 0;
|
|
switch ($item->getType()) {
|
|
default:
|
|
if ($_SESSION['glpishow_count_on_tabs']) {
|
|
$nb = countElementsInTable(
|
|
self::getTable(),
|
|
['racks_id' => $item->getID()]
|
|
);
|
|
$nb+= countElementsInTable(
|
|
PDU_Rack::getTable(),
|
|
['racks_id' => $item->getID()]
|
|
);
|
|
}
|
|
return self::createTabEntry(self::getTypeName(Session::getPluralNumber()), $nb);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) {
|
|
self::showItems($item, $withtemplate);
|
|
}
|
|
|
|
function getForbiddenStandardMassiveAction() {
|
|
$forbidden = parent::getForbiddenStandardMassiveAction();
|
|
$forbidden[] = 'MassiveAction:update';
|
|
$forbidden[] = 'CommonDBConnexity:affect';
|
|
$forbidden[] = 'CommonDBConnexity:unaffect';
|
|
|
|
return $forbidden;
|
|
}
|
|
|
|
/**
|
|
* Print racks items
|
|
* @param Rack $rack the current rack instance
|
|
* @return void
|
|
*/
|
|
static function showItems(Rack $rack) {
|
|
global $DB, $CFG_GLPI;
|
|
|
|
$ID = $rack->getID();
|
|
$rand = mt_rand();
|
|
|
|
if (!$rack->getFromDB($ID)
|
|
|| !$rack->can($ID, READ)) {
|
|
return false;
|
|
}
|
|
$canedit = $rack->canEdit($ID);
|
|
|
|
$items = $DB->request([
|
|
'FROM' => self::getTable(),
|
|
'WHERE' => [
|
|
'racks_id' => $rack->getID()
|
|
],
|
|
'ORDER' => 'position DESC'
|
|
]);
|
|
$link = new self();
|
|
|
|
if ($canedit) {
|
|
Session::initNavigateListItems(
|
|
self::getType(),
|
|
//TRANS : %1$s is the itemtype name,
|
|
// %2$s is the name of the item (used for headings of a list)
|
|
sprintf(
|
|
__('%1$s = %2$s'),
|
|
$rack->getTypeName(1),
|
|
$rack->getName()
|
|
)
|
|
);
|
|
}
|
|
|
|
echo "<div id='switchview'>";
|
|
echo "<i id='sviewlist' class='pointer fa fa-list-alt' title='".__('View as list')."'></i>";
|
|
echo "<i id='sviewgraph' class='pointer fa fa-th-large selected' title='".__('View graphical representation')."'></i>";
|
|
echo "</div>";
|
|
|
|
$items = iterator_to_array($items);
|
|
echo "<div id='viewlist'>";
|
|
|
|
echo "<h2>".__("Racked items")."</h2>";
|
|
if (!count($items)) {
|
|
echo "<table class='tab_cadre_fixe'><tr><th>".__('No item found')."</th></tr>";
|
|
echo "</table>";
|
|
} else {
|
|
if ($canedit) {
|
|
Html::openMassiveActionsForm('mass'.__CLASS__.$rand);
|
|
$massiveactionparams = [
|
|
'num_displayed' => min($_SESSION['glpilist_limit'], count($items)),
|
|
'container' => 'mass'.__CLASS__.$rand
|
|
];
|
|
Html::showMassiveActions($massiveactionparams);
|
|
}
|
|
|
|
echo "<table class='tab_cadre_fixehov'>";
|
|
$header = "<tr>";
|
|
if ($canedit) {
|
|
$header .= "<th width='10'>";
|
|
$header .= Html::getCheckAllAsCheckbox('mass'.__CLASS__.$rand);
|
|
$header .= "</th>";
|
|
}
|
|
$header .= "<th>"._n('Item', 'Items', 1)."</th>";
|
|
$header .= "<th>".__('Position')."</th>";
|
|
$header .= "<th>".__('Orientation')."</th>";
|
|
$header .= "</tr>";
|
|
|
|
echo $header;
|
|
foreach ($items as $row) {
|
|
$item = new $row['itemtype'];
|
|
$item->getFromDB($row['items_id']);
|
|
echo "<tr lass='tab_bg_1'>";
|
|
if ($canedit) {
|
|
echo "<td>";
|
|
Html::showMassiveActionCheckBox(__CLASS__, $row["id"]);
|
|
echo "</td>";
|
|
}
|
|
echo "<td>" . $item->getLink() . "</td>";
|
|
echo "<td>{$row['position']}</td>";
|
|
$txt_orientation = $row['orientation'] == Rack::FRONT ? __('Front') : __('Rear');
|
|
echo "<td>$txt_orientation</td>";
|
|
echo "</tr>";
|
|
}
|
|
echo $header;
|
|
echo "</table>";
|
|
|
|
if ($canedit && count($items)) {
|
|
$massiveactionparams['ontop'] = false;
|
|
Html::showMassiveActions($massiveactionparams);
|
|
}
|
|
if ($canedit) {
|
|
Html::closeForm();
|
|
}
|
|
}
|
|
|
|
PDU_Rack::showListForRack($rack);
|
|
|
|
echo "</div>";
|
|
echo "<div id='viewgraph'>";
|
|
|
|
$data = [];
|
|
//all rows; empty
|
|
for ($i = (int)$rack->fields['number_units']; $i > 0; --$i) {
|
|
$data[Rack::FRONT][$i] = false;
|
|
$data[Rack::REAR][$i] = false;
|
|
}
|
|
|
|
//fill rows
|
|
$outbound = [];
|
|
foreach ($items as $row) {
|
|
$rel = new self;
|
|
$rel->getFromDB($row['id']);
|
|
$item = new $row['itemtype'];
|
|
if (!$item->getFromDB($row['items_id'])) {
|
|
continue;
|
|
}
|
|
|
|
$position = $row['position'];
|
|
|
|
$gs_item = [
|
|
'id' => $row['id'],
|
|
'name' => $item->getName(),
|
|
'x' => $row['hpos'] >= 2 ? 1 : 0,
|
|
'y' => $rack->fields['number_units'] - $row['position'],
|
|
'height' => 1,
|
|
'width' => 2,
|
|
'bgcolor' => $row['bgcolor'],
|
|
'picture_f' => null,
|
|
'picture_r' => null,
|
|
'url' => $item->getLinkURL(),
|
|
'rel_url' => $rel->getLinkURL(),
|
|
'rear' => false,
|
|
'half_rack' => false,
|
|
'reserved' => (bool) $row['is_reserved'],
|
|
];
|
|
|
|
$model_class = $item->getType() . 'Model';
|
|
$modelsfield = strtolower($item->getType()) . 'models_id';
|
|
$model = new $model_class;
|
|
if ($model->getFromDB($item->fields[$modelsfield])) {
|
|
$item->model = $model;
|
|
|
|
if ($item->model->fields['required_units'] > 1) {
|
|
$gs_item['height'] = $item->model->fields['required_units'];
|
|
$gs_item['y'] = $rack->fields['number_units'] + 1
|
|
- $row['position']
|
|
- $item->model->fields['required_units'];
|
|
}
|
|
|
|
if ($item->model->fields['is_half_rack'] == 1) {
|
|
$gs_item['half_rack'] = true;
|
|
$gs_item['width'] = 1;
|
|
$row['position'].= "_".$gs_item['x'];
|
|
if ($row['orientation'] == Rack::REAR) {
|
|
$gs_item['x'] = $row['hpos'] == 2 ? 0 : 1;
|
|
}
|
|
}
|
|
|
|
if (!empty($item->model->fields['picture_front'])) {
|
|
$gs_item['picture_f'] = Toolbox::getPictureUrl($item->model->fields['picture_front']);
|
|
}
|
|
if (!empty($item->model->fields['picture_rear'])) {
|
|
$gs_item['picture_r'] = Toolbox::getPictureUrl($item->model->fields['picture_rear']);
|
|
}
|
|
} else {
|
|
$item->model = null;
|
|
}
|
|
|
|
if (isset($data[$row['orientation']][$position])) {
|
|
$data[$row['orientation']][$row['position']] = [
|
|
'row' => $row,
|
|
'item' => $item,
|
|
'gs_item' => $gs_item
|
|
];
|
|
|
|
//add to other side if needed
|
|
if ($item->model == null
|
|
|| $item->model->fields['depth'] >= 1) {
|
|
$gs_item['rear'] = true;
|
|
$flip_orientation = (int) !((bool) $row['orientation']);
|
|
if ($gs_item['half_rack']) {
|
|
$gs_item['x'] = (int) !((bool) $gs_item['x']);
|
|
//$row['position'] = substr($row['position'], 0, -2)."_".$gs_item['x'];
|
|
}
|
|
$data[$flip_orientation][$row['position']] = [
|
|
'row' => $row,
|
|
'item' => $item,
|
|
'gs_item' => $gs_item
|
|
];
|
|
}
|
|
} else {
|
|
$outbound[] = ['row' => $row, 'item' => $item, 'gs_item' => $gs_item];
|
|
}
|
|
}
|
|
|
|
if (count($outbound)) {
|
|
echo "<table class='outbound'><thead><th>";
|
|
echo __('Following elements are out of rack bounds');
|
|
echo "</th></thead><tbody>";
|
|
foreach ($outbound as $out) {
|
|
echo "<tr><td>".self::getCell($out)."</td></tr>";
|
|
}
|
|
echo "</tbody></table>";
|
|
}
|
|
|
|
$nb_top_pdu = count(PDU_Rack::getForRackSide($rack, PDU_Rack::SIDE_TOP));
|
|
$nb_bot_pdu = count(PDU_Rack::getForRackSide($rack, PDU_Rack::SIDE_BOTTOM));
|
|
|
|
echo '
|
|
<div class="racks_row">
|
|
<span class="racks_view_controls">
|
|
<span class="mini_toggle active"
|
|
id="toggle_images">'.__('images').'</span>
|
|
<span class="mini_toggle active"
|
|
id="toggle_text">'.__('texts').'</span>
|
|
<div class="sep"></div>
|
|
</span>
|
|
<div class="racks_col">
|
|
<h2>'.__('Front').'</h2>
|
|
<div class="rack_side rack_front">';
|
|
// append some spaces on top for having symetrical view between front and rear
|
|
for ($i = 0; $i < $nb_top_pdu; $i++) {
|
|
echo "<div class='virtual_pdu_space'></div>";
|
|
}
|
|
echo '<ul class="indexes"></ul>
|
|
<div class="grid-stack grid-stack-2 grid-rack"
|
|
id="grid-front"
|
|
data-gs-column="2"
|
|
data-gs-max-row="'.($rack->fields['number_units'] + 1).'">';
|
|
|
|
if ($link->canCreate()) {
|
|
echo '<div class="racks_add"></div>';
|
|
}
|
|
|
|
foreach ($data[Rack::FRONT] as $current_item) {
|
|
echo self::getCell($current_item);
|
|
}
|
|
echo ' <div class="grid-stack-item lock-bottom"
|
|
data-gs-no-resize="true" data-gs-no-move="true"
|
|
data-gs-height="1" data-gs-width="2" data-gs-x="0" data-gs-y="'.$rack->fields['number_units'].'"></div>
|
|
</div>
|
|
<ul class="indexes"></ul>';
|
|
// append some spaces on bottom for having symetrical view between front and rear
|
|
for ($i = 0; $i < $nb_bot_pdu; $i++) {
|
|
echo "<div class='virtual_pdu_space'></div>";
|
|
}
|
|
echo '</div>
|
|
</div>
|
|
<div class="racks_col">
|
|
<h2>'.__('Rear').'</h2>';
|
|
echo '<div class="rack_side rack_rear">';
|
|
PDU_Rack::showVizForRack($rack, PDU_Rack::SIDE_TOP);
|
|
PDU_Rack::showVizForRack($rack, PDU_Rack::SIDE_LEFT);
|
|
echo '<ul class="indexes"></ul>
|
|
<div class="grid-stack grid-stack-2 grid-rack"
|
|
id="grid2-rear"
|
|
data-gs-column="2"
|
|
data-gs-max-row="'.($rack->fields['number_units'] + 1).'">';
|
|
|
|
if ($link->canCreate()) {
|
|
echo '<div class="racks_add"></div>';
|
|
}
|
|
|
|
foreach ($data[Rack::REAR] as $current_item) {
|
|
echo self::getCell($current_item);
|
|
}
|
|
echo ' <div class="grid-stack-item lock-bottom"
|
|
data-gs-no-resize="true" data-gs-no-move="true"
|
|
data-gs-height="1" data-gs-width="2" data-gs-x="0" data-gs-y="'.$rack->fields['number_units'].'">
|
|
</div>
|
|
</div>
|
|
<ul class="indexes"></ul>';
|
|
PDU_Rack::showVizForRack($rack, PDU_Rack::SIDE_RIGHT);
|
|
PDU_Rack::showVizForRack($rack, PDU_Rack::SIDE_BOTTOM);
|
|
echo '</div>';
|
|
echo '
|
|
</div>
|
|
<div class="racks_col">';
|
|
self::showStats($rack);
|
|
PDU_Rack::showStatsForRack($rack);
|
|
echo '</div>'; // .racks_col
|
|
echo '</div>'; // .racks_row
|
|
echo '<div class="sep"></div>';
|
|
echo "<div id='grid-dialog'></div>";
|
|
echo "</div>"; // #viewgraph
|
|
|
|
$rack_add_tip = __s('Insert an item here');
|
|
$ajax_url = $CFG_GLPI['root_doc']."/ajax/rack.php";
|
|
|
|
$js = <<<JAVASCRIPT
|
|
// init variables to pass to js/rack.js
|
|
var grid_link_url = "{$link->getFormURL()}";
|
|
var grid_item_ajax_url = "{$ajax_url}";
|
|
var grid_rack_id = $ID;
|
|
var grid_rack_units = {$rack->fields['number_units']};
|
|
var grid_rack_add_tip = "{$rack_add_tip}";
|
|
|
|
$(function() {
|
|
// initialize grid with function defined in js/rack.js
|
|
initRack();
|
|
|
|
// drag&drop scenario for item_rack:
|
|
// - we start by storing position before drag
|
|
// - we send position to db by ajax after drag stop event
|
|
// - if ajax answer return a fail, we restore item to the old position
|
|
// and we display a message explaning the failure
|
|
// - else we move the other side of asset (if exists)
|
|
$('.grid-stack.grid-rack')
|
|
.on('change', function(event, items) {
|
|
if (dirty) {
|
|
return;
|
|
}
|
|
var grid = $(event.target).data('gridstack');
|
|
var is_rack_rear = $(grid.container).parents('.racks_col').hasClass('rack_rear');
|
|
$.each(items, function(index, item) {
|
|
var is_half_rack = item.el.hasClass('half_rack');
|
|
var is_el_rear = item.el.hasClass('rear');
|
|
var new_pos = grid_rack_units - item.y - item.height + 1;
|
|
$.post(grid_item_ajax_url, {
|
|
id: item.id,
|
|
action: 'move_item',
|
|
position: new_pos,
|
|
hpos: getHpos(item.x, is_half_rack, is_rack_rear),
|
|
}, function(answer) {
|
|
var answer = jQuery.parseJSON(answer);
|
|
|
|
// revert to old position
|
|
if (!answer.status) {
|
|
dirty = true;
|
|
grid.move(item.el, x_before_drag, y_before_drag);
|
|
dirty = false;
|
|
displayAjaxMessageAfterRedirect();
|
|
} else {
|
|
// move other side if needed
|
|
var other_side_cls = $(item.el).hasClass('item_rear')
|
|
? "item_front"
|
|
: "item_rear";
|
|
var other_side_el = $('.grid-stack-item.'+other_side_cls+'[data-gs-id='+item.id+']');
|
|
|
|
if (other_side_el.length) {
|
|
var other_side_grid = $(other_side_el).parent().data('gridstack');
|
|
new_x = item.x;
|
|
new_y = item.y;
|
|
if (item.width == 1) {
|
|
new_x = (item.x == 0 ? 1 : 0);
|
|
}
|
|
dirty = true;
|
|
other_side_grid.move(other_side_el, new_x, new_y);
|
|
dirty = false;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
})
|
|
});
|
|
JAVASCRIPT;
|
|
echo Html::scriptBlock($js);
|
|
}
|
|
|
|
/**
|
|
* Display a mini stats block (wiehgt, power, etc) for the current rack instance
|
|
* @param Rack $rack the current rack instance
|
|
* @return void
|
|
*/
|
|
static function showStats(Rack $rack) {
|
|
global $DB;
|
|
|
|
$items = $DB->request([
|
|
'FROM' => self::getTable(),
|
|
'WHERE' => [
|
|
'racks_id' => $rack->getID()
|
|
]
|
|
]);
|
|
|
|
$weight = 0;
|
|
$power = 0;
|
|
$units = [
|
|
Rack::FRONT => array_fill(0, $rack->fields['number_units'], 0),
|
|
Rack::REAR => array_fill(0, $rack->fields['number_units'], 0),
|
|
];
|
|
|
|
$rel = new self;
|
|
while ($row = $items->next()) {
|
|
$rel->getFromDB($row['id']);
|
|
|
|
$item = new $row['itemtype'];
|
|
$item->getFromDB($row['items_id']);
|
|
|
|
$model_class = $item->getType() . 'Model';
|
|
$modelsfield = strtolower($item->getType()) . 'models_id';
|
|
$model = new $model_class;
|
|
|
|
if ($model->getFromDB($item->fields[$modelsfield])) {
|
|
$required_units = $model->fields['required_units'];
|
|
|
|
for ($i = 0; $i < $model->fields['required_units']; $i++) {
|
|
$units[$row['orientation']][$row['position'] + $i] = 1;
|
|
if ($model->fields['depth'] == 1) {
|
|
$other_side = (int) !(bool) $row['orientation'];
|
|
$units[$other_side][$row['position'] + $i] = 1;
|
|
}
|
|
}
|
|
|
|
if (array_key_exists('power_consumption', $model->fields)) { // PDU does not consume energy
|
|
$power += $model->fields['power_consumption'];
|
|
}
|
|
|
|
$weight += $model->fields['weight'];
|
|
} else {
|
|
$units[Rack::FRONT][$row['position']] = 1;
|
|
$units[Rack::REAR][$row['position']] = 1;
|
|
}
|
|
}
|
|
|
|
$nb_units = max(
|
|
array_sum($units[Rack::FRONT]),
|
|
array_sum($units[Rack::REAR])
|
|
);
|
|
|
|
$space_prct = round(100 * $nb_units / max($rack->fields['number_units'], 1));
|
|
$weight_prct = round(100 * $weight / max($rack->fields['max_weight'], 1));
|
|
$power_prct = round(100 * $power / max($rack->fields['max_power'], 1));
|
|
|
|
echo "<div id='rack_stats' class='rack_side_block'>";
|
|
|
|
echo "<h2>".__("Rack stats")."</h2>";
|
|
|
|
echo "<div class='rack_side_block_content'>";
|
|
echo "<h3>".__("Space")."</h3>";
|
|
Html::progressBar('rack_space', [
|
|
'create' => true,
|
|
'percent' => $space_prct,
|
|
'message' => $space_prct."%",
|
|
]);
|
|
|
|
echo "<h3>".__("Weight")."</h3>";
|
|
Html::progressBar('rack_weight', [
|
|
'create' => true,
|
|
'percent' => $weight_prct,
|
|
'message' => $weight." / ".$rack->fields['max_weight']
|
|
]);
|
|
|
|
echo "<h3>".__("Power")."</h3>";
|
|
Html::progressBar('rack_power', [
|
|
'create' => true,
|
|
'percent' => $power_prct,
|
|
'message' => $power." / ".$rack->fields['max_power']
|
|
]);
|
|
echo "</div>";
|
|
echo "</div>";
|
|
}
|
|
|
|
function showForm($ID, $options = []) {
|
|
global $DB, $CFG_GLPI;
|
|
|
|
$colspan = 4;
|
|
|
|
echo "<div class='center'>";
|
|
|
|
$this->initForm($ID, $options);
|
|
$this->showFormHeader();
|
|
|
|
$rack = new Rack();
|
|
$rack->getFromDB($this->fields['racks_id']);
|
|
|
|
$rand = mt_rand();
|
|
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td><label for='dropdown_itemtype$rand'>".__('Item type')."</label></td>";
|
|
echo "<td>";
|
|
|
|
if (isset($options['_onlypdu']) && $options['_onlypdu']) {
|
|
$this->fields['itemtype'] = 'PDU';
|
|
echo Html::hidden(
|
|
'itemtype',
|
|
[
|
|
'id' => "itemtype_$rand",
|
|
'value' => 'PDU'
|
|
]
|
|
);
|
|
echo PDU::getTypeName(1);
|
|
} else {
|
|
$types = array_combine($CFG_GLPI['rackable_types'], $CFG_GLPI['rackable_types']);
|
|
foreach ($types as $type => &$text) {
|
|
$text = $type::getTypeName(1);
|
|
}
|
|
Dropdown::showFromArray(
|
|
'itemtype',
|
|
$types, [
|
|
'display_emptychoice' => true,
|
|
'value' => $this->fields["itemtype"],
|
|
'rand' => $rand
|
|
]
|
|
);
|
|
}
|
|
|
|
//get all used items
|
|
$used = $used_reserved = [];
|
|
$iterator = $DB->request([
|
|
'FROM' => $this->getTable()
|
|
]);
|
|
while ($row = $iterator->next()) {
|
|
$used[$row['itemtype']][] = $row['items_id'];
|
|
}
|
|
// find used pdu (not racked)
|
|
foreach (PDU_Rack::getUsed() as $used_pdu) {
|
|
$used['PDU'][] = $used_pdu['pdus_id'];
|
|
}
|
|
// get all reserved items
|
|
$iterator = $DB->request([
|
|
'FROM' => $this->getTable(),
|
|
'WHERE' => [
|
|
'is_reserved' => true
|
|
]
|
|
]);
|
|
while ($row = $iterator->next()) {
|
|
$used_reserved[$row['itemtype']][] = $row['items_id'];
|
|
}
|
|
|
|
//items part of an enclosure should not be listed
|
|
$iterator = $DB->request([
|
|
'FROM' => Item_Enclosure::getTable()
|
|
]);
|
|
while ($row = $iterator->next()) {
|
|
$used[$row['itemtype']][] = $row['items_id'];
|
|
}
|
|
echo Html::hidden(
|
|
'used',
|
|
[
|
|
'id' => "used_$rand",
|
|
'value' => json_encode($used)
|
|
]
|
|
);
|
|
|
|
//TODO: update possible positions according to selected item number of units
|
|
//TODO: update positions on rack selection
|
|
//TODO: update hpos from item model info is_half_rack
|
|
//TODO: update orientation according to item model depth
|
|
|
|
echo "</td>";
|
|
echo "<td><label for='dropdown_items_id$rand'>"._n('Item', 'Items', 1)."</label></td>";
|
|
echo "<td id='items_id'>";
|
|
if (isset($this->fields['itemtype']) && !empty($this->fields['itemtype'])) {
|
|
$itemtype = $this->fields['itemtype'];
|
|
$itemtype = new $itemtype();
|
|
$itemtype::dropdown([
|
|
'name' => "items_id",
|
|
'value' => $this->fields['items_id'],
|
|
'rand' => $rand
|
|
]);
|
|
} else {
|
|
Dropdown::showFromArray(
|
|
'items_id',
|
|
[], [
|
|
'display_emptychoice' => true,
|
|
'rand' => $rand
|
|
]
|
|
);
|
|
}
|
|
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td><label for='dropdown_racks_id$rand'>".Rack::getTypeName(1)."</label></td>";
|
|
echo "<td>";
|
|
Rack::dropdown(['value' => $this->fields["racks_id"], 'rand' => $rand]);
|
|
echo "</td>";
|
|
echo "<td><label for='dropdown_position$rand'>".__('Position')."</label></td>";
|
|
echo "<td >";
|
|
Dropdown::showNumber(
|
|
'position', [
|
|
'value' => $this->fields["position"],
|
|
'min' => 1,
|
|
'max' => $rack->fields['number_units'],
|
|
'step' => 1,
|
|
'used' => $rack->getFilled($this->fields['itemtype'], $this->fields['items_id']),
|
|
'rand' => $rand
|
|
]
|
|
);
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td><label for='dropdown_orientation$rand'>".__('Orientation (front rack point of view)')."</label></td>";
|
|
echo "<td >";
|
|
Dropdown::showFromArray(
|
|
'orientation', [
|
|
Rack::FRONT => __('Front'),
|
|
Rack::REAR => __('Rear')
|
|
], [
|
|
'value' => $this->fields["orientation"],
|
|
'rand' => $rand
|
|
]
|
|
);
|
|
echo "</td>";
|
|
echo "<td><label for='bgcolor$rand'>".__('Background color')."</label></td>";
|
|
echo "<td>";
|
|
Html::showColorField(
|
|
'bgcolor', [
|
|
'value' => $this->fields['bgcolor'],
|
|
'rand' => $rand
|
|
]
|
|
);
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
|
|
echo "<tr class='tab_bg_1'>";
|
|
echo "<td><label for='dropdown_hpos$rand'>".__('Horizontal position (from rack point of view)')."</label></td>";
|
|
echo "<td>";
|
|
Dropdown::showFromArray(
|
|
'hpos',
|
|
[
|
|
Rack::POS_NONE => __('None'),
|
|
Rack::POS_LEFT => __('Left'),
|
|
Rack::POS_RIGHT => __('Right')
|
|
], [
|
|
'value' => $this->fields['hpos'],
|
|
'rand' => $rand
|
|
]
|
|
);
|
|
echo "</td>";
|
|
echo "<td><label for='dropdown_is_reserved$rand'>".__('Reserved position ?')."</label></td>";
|
|
echo "<td>";
|
|
|
|
echo Html::scriptBlock("
|
|
var toggleUsed = function(reserved) {
|
|
if (reserved == 1) {
|
|
$('#used_$rand').val('".json_encode($used_reserved)."');
|
|
} else {
|
|
$('#used_$rand').val('".json_encode($used)."');
|
|
}
|
|
// force change of itemtype dropdown to have a correct (with empty/filled used input)
|
|
// filtered items list
|
|
$('#dropdown_itemtype$rand').trigger('change');
|
|
}
|
|
");
|
|
Dropdown::showYesNo(
|
|
'is_reserved',
|
|
$this->fields['is_reserved'],
|
|
-1, [
|
|
'rand' => $rand,
|
|
'on_change' => 'toggleUsed(this.value)'
|
|
]
|
|
);
|
|
|
|
$entities = $rack->fields['entities_id'];
|
|
if ($rack->fields['is_recursive']) {
|
|
$entities = getSonsOf('glpi_entities', $entities);
|
|
}
|
|
|
|
Ajax::updateItemOnSelectEvent(
|
|
["dropdown_itemtype$rand", "dropdown_is_reserved$rand", "used_$rand"],
|
|
"items_id",
|
|
$CFG_GLPI["root_doc"]."/ajax/dropdownAllItems.php", [
|
|
'idtable' => '__VALUE0__',
|
|
'name' => 'items_id',
|
|
'value' => $this->fields['items_id'],
|
|
'rand' => $rand,
|
|
'is_reserved' => '__VALUE1__',
|
|
'used' => '__VALUE2__',
|
|
'entity_restrict' => $entities,
|
|
]
|
|
);
|
|
echo "</td>";
|
|
echo "</tr>";
|
|
|
|
$this->showFormButtons($options);
|
|
}
|
|
|
|
function post_getEmpty() {
|
|
$this->fields['bgcolor'] = '#69CEBA';
|
|
}
|
|
|
|
/**
|
|
* Get cell content
|
|
*
|
|
* @param mixed $cell Rack cell (array or false)
|
|
*
|
|
* @return string
|
|
*/
|
|
private static function getCell($cell) {
|
|
if ($cell) {
|
|
$item = $cell['item'];
|
|
$gs_item = $cell['gs_item'];
|
|
$name = $gs_item['name'];
|
|
$typename = is_object($item)
|
|
? $item->getTypeName()
|
|
: "";
|
|
$serial = is_object($item)
|
|
? $item->fields['serial']
|
|
: "";
|
|
$otherserial = is_object($item)
|
|
? $item->fields['otherserial']
|
|
: "";
|
|
$model = is_object($item)
|
|
&& is_object($item->model)
|
|
&& isset($item->model->fields['name'])
|
|
? $item->model->fields['name']
|
|
: '';
|
|
$rear = $gs_item['rear'];
|
|
$back_class = $rear
|
|
? "item_rear"
|
|
: "item_front";
|
|
$half_class = $gs_item['half_rack']
|
|
? "half_rack"
|
|
: "";
|
|
$reserved = $gs_item['reserved'];
|
|
$reserved_cl = $reserved
|
|
? "reserved"
|
|
: "";
|
|
$icon = $reserved
|
|
? self::getItemIcon("Reserved")
|
|
: self::getItemIcon(get_class($item));
|
|
$bg_color = $gs_item['bgcolor'];
|
|
if ($item->maybeDeleted() && $item->isDeleted()) {
|
|
$bg_color = '#ff0000'; //red for deleted items
|
|
}
|
|
$fg_color = !empty($bg_color)
|
|
? Html::getInvertedColor($gs_item['bgcolor'])
|
|
: "";
|
|
$fg_color_s = "color: $fg_color;";
|
|
$img_class = "";
|
|
$img_s = "";
|
|
if ($gs_item['picture_f'] && !$rear && !$reserved) {
|
|
$img_s = "background: $bg_color url(\"".$gs_item['picture_f']."\") no-repeat top left/100% 100%;";
|
|
$img_class = 'with_picture';
|
|
}
|
|
if ($gs_item['picture_r'] && $rear && !$reserved) {
|
|
$img_s = "background: $bg_color url(\"".$gs_item['picture_r']."\") no-repeat top left/100% 100%;";
|
|
$img_class = 'with_picture';
|
|
}
|
|
|
|
$tip = "<span class='tipcontent'>";
|
|
$tip.= "<span>
|
|
<label>".
|
|
($rear
|
|
? __("asset rear side")
|
|
: __("asset front side"))."
|
|
</label>
|
|
</span>";
|
|
if (!empty($typename)) {
|
|
$tip.= "<span>
|
|
<label>"._n('Type', 'Types', 1).":</label>
|
|
$typename
|
|
</span>";
|
|
}
|
|
if (!empty($name)) {
|
|
$tip.= "<span>
|
|
<label>".__('name').":</label>
|
|
$name
|
|
</span>";
|
|
}
|
|
if (!empty($serial)) {
|
|
$tip.= "<span>
|
|
<label>".__('serial').":</label>
|
|
$serial
|
|
</span>";
|
|
}
|
|
if (!empty($otherserial)) {
|
|
$tip.= "<span>
|
|
<label>".__('Inventory number').":</label>
|
|
$otherserial
|
|
</span>";
|
|
}
|
|
if (!empty($model)) {
|
|
$tip.= "<span>
|
|
<label>".__('model').":</label>
|
|
$model
|
|
</span>";
|
|
}
|
|
|
|
$tip.= "</span>";
|
|
|
|
return "
|
|
<div class='grid-stack-item $back_class $half_class $reserved_cl $img_class'
|
|
data-gs-width='{$gs_item['width']}' data-gs-height='{$gs_item['height']}'
|
|
data-gs-x='{$gs_item['x']}' data-gs-y='{$gs_item['y']}'
|
|
data-gs-id='{$gs_item['id']}'
|
|
style='background-color: $bg_color; color: $fg_color;'>
|
|
<div class='grid-stack-item-content' style='$fg_color_s $img_s'>
|
|
$icon".
|
|
(!empty($gs_item['url'])
|
|
? "<a href='{$gs_item['url']}' class='itemrack_name' style='$fg_color_s'>{$gs_item['name']}</a>"
|
|
: "<span class='itemrack_name'>".$gs_item['name']."</span>")."
|
|
<a href='{$gs_item['rel_url']}'>
|
|
<i class='fa fa-pencil-alt rel-link'
|
|
style='$fg_color_s'
|
|
title='".__("Edit rack relation")."'></i>
|
|
</a>
|
|
$tip
|
|
</div>
|
|
</div>";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return an i html tag with a dedicated icon for the itemtype
|
|
* @param string $itemtype A rackable itemtype
|
|
* @return string The i html tag
|
|
*/
|
|
private static function getItemIcon($itemtype = "") {
|
|
$icon = "";
|
|
switch ($itemtype) {
|
|
case "Computer":
|
|
$icon = "fas fa-server";
|
|
break;
|
|
case "Reserved":
|
|
$icon = "fas fa-lock";
|
|
break;
|
|
|
|
default:
|
|
$icon = $itemtype::getIcon();
|
|
break;
|
|
}
|
|
|
|
if (!empty($icon)) {
|
|
$icon = "<i class='item_rack_icon $icon'></i>";
|
|
}
|
|
|
|
return $icon;
|
|
}
|
|
|
|
function prepareInputForAdd($input) {
|
|
return $this->prepareInput($input);
|
|
}
|
|
|
|
function prepareInputForUpdate($input) {
|
|
return $this->prepareInput($input);
|
|
}
|
|
|
|
/**
|
|
* Prepares input (for update and add)
|
|
*
|
|
* @param array $input Input data
|
|
*
|
|
* @return array
|
|
*/
|
|
private function prepareInput($input) {
|
|
$error_detected = [];
|
|
|
|
$itemtype = !$this->isNewItem() ? $this->fields['itemtype'] : null;
|
|
$items_id = !$this->isNewItem() ? $this->fields['items_id'] : null;
|
|
$racks_id = !$this->isNewItem() ? $this->fields['racks_id'] : null;
|
|
$position = !$this->isNewItem() ? $this->fields['position'] : null;
|
|
$hpos = !$this->isNewItem() ? $this->fields['hpos'] : null;
|
|
$orientation = !$this->isNewItem() ? $this->fields['orientation'] : null;
|
|
|
|
//check for requirements
|
|
if (($this->isNewItem() && (!isset($input['itemtype']) || empty($input['itemtype'])))
|
|
|| (isset($input['itemtype']) && empty($input['itemtype']))) {
|
|
$error_detected[] = __('An item type is required');
|
|
}
|
|
if (($this->isNewItem() && (!isset($input['items_id']) || empty($input['items_id'])))
|
|
|| (isset($input['items_id']) && empty($input['items_id']))) {
|
|
$error_detected[] = __('An item is required');
|
|
}
|
|
if (($this->isNewItem() && (!isset($input['racks_id']) || empty($input['racks_id'])))
|
|
|| (isset($input['racks_id']) && empty($input['racks_id']))) {
|
|
$error_detected[] = __('A rack is required');
|
|
}
|
|
if (($this->isNewItem() && (!isset($input['position']) || empty($input['position'])))
|
|
|| (isset($input['position']) && empty($input['position']))) {
|
|
$error_detected[] = __('A position is required');
|
|
}
|
|
|
|
if (isset($input['itemtype'])) {
|
|
$itemtype = $input['itemtype'];
|
|
}
|
|
if (isset($input['items_id'])) {
|
|
$items_id = $input['items_id'];
|
|
}
|
|
if (isset($input['racks_id'])) {
|
|
$racks_id = $input['racks_id'];
|
|
}
|
|
if (isset($input['position'])) {
|
|
$position = $input['position'];
|
|
}
|
|
if (isset($input['hpos'])) {
|
|
$hpos = $input['hpos'];
|
|
}
|
|
if (isset($input['orientation'])) {
|
|
$orientation = $input['orientation'];
|
|
}
|
|
|
|
if (!count($error_detected)) {
|
|
//check if required U are available at position
|
|
$rack = new Rack();
|
|
$rack->getFromDB($racks_id);
|
|
|
|
if ($this->isNewItem()) {
|
|
$filled = $rack->getFilled();
|
|
} else {
|
|
// If object is existing, exclude current state from used positions
|
|
$filled = $rack->getFilled($this->fields['itemtype'], $this->fields['items_id']);
|
|
}
|
|
|
|
$item = new $itemtype;
|
|
$item->getFromDB($items_id);
|
|
$model_class = $item->getType() . 'Model';
|
|
$modelsfield = strtolower($item->getType()) . 'models_id';
|
|
$model = new $model_class;
|
|
if ($model->getFromDB($item->fields[$modelsfield])) {
|
|
$item->model = $model;
|
|
} else {
|
|
$item->model = null;
|
|
}
|
|
|
|
$required_units = 1;
|
|
$width = 1;
|
|
$depth = 1;
|
|
if ($item->model != null) {
|
|
if ($item->model->fields['required_units'] > 1) {
|
|
$required_units = $item->model->fields['required_units'];
|
|
}
|
|
if ($item->model->fields['is_half_rack'] == 1) {
|
|
if ($this->isNewItem() && !isset($input['hpos']) || $input['hpos'] == 0) {
|
|
$error_detected[] = __('You must define an horizontal position for this item');
|
|
}
|
|
$width = 0.5;
|
|
}
|
|
if ($item->model->fields['depth'] != 1) {
|
|
if ($this->isNewItem() && !isset($input['orientation'])) {
|
|
$error_detected[] = __('You must define an orientation for this item');
|
|
}
|
|
$depth = $item->model->fields['depth'];
|
|
}
|
|
}
|
|
|
|
if ($position > $rack->fields['number_units'] ||
|
|
$position + $required_units > $rack->fields['number_units'] + 1
|
|
) {
|
|
$error_detected[] = __('Item is out of rack bounds');
|
|
} else if (!count($error_detected)) {
|
|
$i = 0;
|
|
while ($i < $required_units) {
|
|
$current_position = $position + $i;
|
|
if (isset($filled[$current_position])) {
|
|
$content_filled = $filled[$current_position];
|
|
|
|
if ($hpos == Rack::POS_NONE || $hpos == Rack::POS_LEFT) {
|
|
$d = 0;
|
|
while ($d/4 < $depth) {
|
|
$pos = ($orientation == Rack::REAR) ? 3 - $d : $d;
|
|
$val = 1;
|
|
if (isset($content_filled[Rack::POS_LEFT][$pos]) && $content_filled[Rack::POS_LEFT][$pos] != 0) {
|
|
$error_detected[] = __('Not enough space available to place item');
|
|
break 2;
|
|
}
|
|
++$d;
|
|
}
|
|
}
|
|
|
|
if ($hpos == Rack::POS_NONE || $hpos == Rack::POS_RIGHT) {
|
|
$d = 0;
|
|
while ($d/4 < $depth) {
|
|
$pos = ($orientation == Rack::REAR) ? 3 - $d : $d;
|
|
$val = 1;
|
|
if (isset($content_filled[Rack::POS_RIGHT][$pos]) && $content_filled[Rack::POS_RIGHT][$pos] != 0) {
|
|
$error_detected[] = __('Not enough space available to place item');
|
|
break 2;
|
|
}
|
|
++$d;
|
|
}
|
|
}
|
|
}
|
|
++$i;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count($error_detected)) {
|
|
foreach ($error_detected as $error) {
|
|
Session::addMessageAfterRedirect(
|
|
$error,
|
|
true,
|
|
ERROR
|
|
);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return $input;
|
|
}
|
|
|
|
protected function computeFriendlyName() {
|
|
$rack = new Rack();
|
|
$rack->getFromDB($this->fields['racks_id']);
|
|
$name = sprintf(
|
|
__('Item for rack "%1$s"'),
|
|
$rack->getName()
|
|
);
|
|
|
|
return $name;
|
|
}
|
|
|
|
}
|