From a79953c589c9bc9b70562d6360fe8ee5ec389278 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Tue, 6 May 2025 00:59:03 +0200 Subject: [PATCH 01/25] feature_managementhdd --- classes/DeviceTemplate.class.php | 67 +++++++++++ classes/People.class.php | 52 +++++---- classes/hdd.class.php | 192 +++++++++++++++++++++++++++++++ configuration.php | 10 ++ db-23.04-to-24.01.sql | 24 ++++ device_templates.php | 26 +++++ devices.php | 17 ++- managementhdd.php | 103 +++++++++++++++++ usermgr.php | 3 + 9 files changed, 470 insertions(+), 24 deletions(-) create mode 100644 classes/hdd.class.php create mode 100644 managementhdd.php diff --git a/classes/DeviceTemplate.class.php b/classes/DeviceTemplate.class.php index d4b62b29d..ef7a67a8e 100644 --- a/classes/DeviceTemplate.class.php +++ b/classes/DeviceTemplate.class.php @@ -40,6 +40,9 @@ class DeviceTemplate { var $SNMPVersion; var $CustomValues; var $GlobalID; + //for feature management hdd at the bottom of the page + public $EnableHDDFeature = 0; + public $HDDCount = 0; public function __construct($dtid=false){ if($dtid){ @@ -748,6 +751,70 @@ static function getAvailableImages(){ } return $array; } + + // method for feature management hdd + //HDD data is a logical complement to the equipment model. It depends directly on the TemplateID. + public function UpdateTemplateHDD() { + global $dbh; + + $EnableHDDFeature = isset($_POST['EnableHDDFeature']) ? intval($_POST['EnableHDDFeature']) : 0; + $HDDCount = isset($_POST['HDDCount']) ? intval($_POST['HDDCount']) : 0; + + $check = $dbh->prepare("SELECT COUNT(*) FROM fac_DeviceTemplateHdd WHERE TemplateID = ?"); + $check->execute([$this->TemplateID]); + + if ($check->fetchColumn() > 0) { + $sql = "UPDATE fac_DeviceTemplateHdd SET EnableHDDFeature = ?, HDDCount = ? WHERE TemplateID = ?"; + $dbh->prepare($sql)->execute([$EnableHDDFeature, $HDDCount, $this->TemplateID]); + } else { + $sql = "INSERT INTO fac_DeviceTemplateHdd (TemplateID, EnableHDDFeature, HDDCount) VALUES (?, ?, ?)"; + $dbh->prepare($sql)->execute([$this->TemplateID, $EnableHDDFeature, $HDDCount]); + } + } + + public function LoadHDDConfig() { + global $dbh; + + $this->EnableHDDFeature = 0; + $this->HDDCount = 0; + + if ($this->TemplateID > 0) { + $sql = "SELECT EnableHDDFeature, HDDCount FROM fac_DeviceTemplateHdd WHERE TemplateID = ?"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$this->TemplateID]); + if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $this->EnableHDDFeature = $row['EnableHDDFeature']; + $this->HDDCount = $row['HDDCount']; + } + } + } + + public function DeleteTemplateHDD() { + global $dbh; + + if ($this->TemplateID > 0) { + $sql = "DELETE FROM fac_DeviceTemplateHdd WHERE TemplateID = ?"; + $stmt = $dbh->prepare($sql); + return $stmt->execute([$this->TemplateID]); + } + return false; + } + + public function ExportTemplateHDD($asJSON = false) { + global $dbh; + + $sql = "SELECT EnableHDDFeature, HDDCount FROM fac_DeviceTemplateHdd WHERE TemplateID = ?"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$this->TemplateID]); + + if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + return $asJSON ? json_encode($row) : $row; + } else { + $data = ['EnableHDDFeature' => 0, 'HDDCount' => 0]; + return $asJSON ? json_encode($data) : $data; + } + } + } ?> diff --git a/classes/People.class.php b/classes/People.class.php index b53378def..522bdf86c 100644 --- a/classes/People.class.php +++ b/classes/People.class.php @@ -31,27 +31,28 @@ class People { things greatly. */ - var $PersonID; - var $UserID; - var $LastName; - var $FirstName; - var $Phone1; - var $Phone2; - var $countryCode; - var $Email; - var $AdminOwnDevices; - var $ReadAccess; - var $WriteAccess; - var $DeleteAccess; - var $ContactAdmin; - var $RackRequest; - var $RackAdmin; - var $BulkOperations; - var $SiteAdmin; - var $APIKey; - var $Disabled; - var $LastActivity; - var $ExpirationDate; + public $PersonID; + public $UserID; + public $LastName; + public $FirstName; + public $Phone1; + public $Phone2; + public $countryCode; + public $Email; + public $AdminOwnDevices; + public $ReadAccess; + public $WriteAccess; + public $DeleteAccess; + public $ContactAdmin; + public $RackRequest; + public $RackAdmin; + public $BulkOperations; + public $SiteAdmin; + public $APIKey; + public $Disabled; + public $LastActivity; + public $ExpirationDate; + public $ManageHDD; function MakeSafe(){ $this->PersonID=intval($this->PersonID); @@ -71,6 +72,7 @@ function MakeSafe(){ $this->RackAdmin=intval($this->RackAdmin); $this->BulkOperations=intval($this->BulkOperations); $this->SiteAdmin=intval($this->SiteAdmin); + $this->ManageHDD=intval($this->ManageHDD); $this->Disabled=intval($this->Disabled); $this->ExpirationDate=sanitize($this->ExpirationDate); } @@ -95,6 +97,7 @@ function MakeDisplay(){ $this->RackAdmin=intval($this->RackAdmin); $this->BulkOperations=intval($this->BulkOperations); $this->SiteAdmin=intval($this->SiteAdmin); + $this->ManageHDD=intval($this->ManageHDD); $this->Disabled=intval($this->Disabled); } @@ -117,6 +120,7 @@ static function RowToObject($row){ $person->RackAdmin=$row["RackAdmin"]; $person->BulkOperations=$row["BulkOperations"]; $person->SiteAdmin=$row["SiteAdmin"]; + $person->ManageHDD=$row["ManageHDD"]; $person->APIKey=$row["APIKey"]; $person->Disabled=$row["Disabled"]; $person->LastActivity=$row["LastActivity"]; @@ -164,6 +168,7 @@ function revokeAll() { $this->ContactAdmin = false; $this->BulkOperations = false; $this->SiteAdmin = false; + $this->ManageHDD = false; } function canRead( $Owner ) { @@ -208,7 +213,7 @@ function CreatePerson() { AdminOwnDevices=$this->AdminOwnDevices, ReadAccess=$this->ReadAccess, WriteAccess=$this->WriteAccess, DeleteAccess=$this->DeleteAccess, ContactAdmin=$this->ContactAdmin, RackRequest=$this->RackRequest, - RackAdmin=$this->RackAdmin, BulkOperations=$this->BulkOperations, SiteAdmin=$this->SiteAdmin, + RackAdmin=$this->RackAdmin, BulkOperations=$this->BulkOperations, SiteAdmin=$this->SiteAdmin,ManageHDD=$this->ManageHDD, APIKey=\"$this->APIKey\", Disabled=$this->Disabled, ExpirationDate=\"$this->ExpirationDate\";"; if(!$this->query($sql)){ @@ -233,6 +238,7 @@ static function Current(){ $cperson->ReadAccess=true; $cperson->WriteAccess=true; $cperson->SiteAdmin=true; + $cperson->ManageHDD=true; $cperson->Disabled=false; }elseif(AUTHENTICATION=="Apache"){ if(!isset($_SERVER["REMOTE_USER"])){ @@ -451,7 +457,7 @@ function UpdatePerson() { AdminOwnDevices=$this->AdminOwnDevices, ReadAccess=$this->ReadAccess, WriteAccess=$this->WriteAccess, DeleteAccess=$this->DeleteAccess, ContactAdmin=$this->ContactAdmin, RackRequest=$this->RackRequest, - RackAdmin=$this->RackAdmin, BulkOperations=$this->BulkOperations, SiteAdmin=$this->SiteAdmin, + RackAdmin=$this->RackAdmin, BulkOperations=$this->BulkOperations, SiteAdmin=$this->SiteAdmin,ManageHDD=$this->ManageHDD, APIKey=\"$this->APIKey\", ExpirationDate=\"$formattedDate\", Disabled=$this->Disabled WHERE PersonID=$this->PersonID;"; diff --git a/classes/hdd.class.php b/classes/hdd.class.php new file mode 100644 index 000000000..4d1d148b7 --- /dev/null +++ b/classes/hdd.class.php @@ -0,0 +1,192 @@ +HDDID = intval($this->HDDID); + $this->DeviceID = intval($this->DeviceID); + $this->Label = sanitize($this->Label); + $this->SerialNo = sanitize($this->SerialNo); + $this->Status = sanitize($this->Status); + $this->Size = intval($this->Size); + $this->TypeMedia = sanitize($this->TypeMedia); + $this->Note = sanitize($this->Note); + } + + function MakeDisplay() { + // Si besoin d'afficher des données formatées + } + + static function RowToObject($row){ + $hdd = new HDD(); + foreach($row as $prop => $val){ + $hdd->$prop = $val; + } + $hdd->MakeDisplay(); + return $hdd; + } + + function CreateHDD() { + global $dbh; + + $this->MakeSafe(); + + $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, Size, TypeMedia, DateAdd, StatusDestruction, Note) + VALUES (:DeviceID, :Label, :SerialNo, :Status, :Size, :TypeMedia, NOW(), 'none', :Note)"; + + $stmt = $dbh->prepare($sql); + $stmt->execute([ + ":DeviceID" => $this->DeviceID, + ":Label" => $this->Label, + ":SerialNo" => $this->SerialNo, + ":Status" => $this->Status, + ":Size" => $this->Size, + ":TypeMedia" => $this->TypeMedia, + ":Note" => $this->Note + ]); + + $this->HDDID = $dbh->lastInsertId(); + } + + function UpdateHDD() { + global $dbh; + + $this->MakeSafe(); + + $sql = "UPDATE fac_HDD SET + DeviceID = :DeviceID, + Label = :Label, + SerialNo = :SerialNo, + Status = :Status, + Size = :Size, + TypeMedia = :TypeMedia, + Note = :Note + WHERE HDDID = :HDDID"; + + $stmt = $dbh->prepare($sql); + return $stmt->execute([ + ":DeviceID" => $this->DeviceID, + ":Label" => $this->Label, + ":SerialNo" => $this->SerialNo, + ":Status" => $this->Status, + ":Size" => $this->Size, + ":TypeMedia" => $this->TypeMedia, + ":Note" => $this->Note, + ":HDDID" => $this->HDDID + ]); + } + + function DeleteHDD() { + global $dbh; + + $this->MakeSafe(); + + $sql = "DELETE FROM fac_HDD WHERE HDDID = :HDDID"; + + $stmt = $dbh->prepare($sql); + return $stmt->execute([":HDDID" => $this->HDDID]); + } + + function SendForDestruction($note = '') { + global $dbh; + + $this->MakeSafe(); + $note = sanitize($note); + + $sql = "UPDATE fac_HDD SET + Status = 'pending_destruction', + StatusDestruction = 'pending', + DateWithdrawn = NOW(), + Note = CONCAT(Note, ' ', :Note) + WHERE HDDID = :HDDID"; + + $stmt = $dbh->prepare($sql); + return $stmt->execute([ + ":HDDID" => $this->HDDID, + ":Note" => $note + ]); + } + + function MarkAsDestroyed($note = '') { + global $dbh; + + $this->MakeSafe(); + $note = sanitize($note); + + $sql = "UPDATE fac_HDD SET + Status = 'destroyed_h2', + StatusDestruction = 'destroyed', + DateDestruction = NOW(), + Note = CONCAT(Note, ' ', :Note) + WHERE HDDID = :HDDID"; + + $stmt = $dbh->prepare($sql); + return $stmt->execute([ + ":HDDID" => $this->HDDID, + ":Note" => $note + ]); + } + + static function GetHDDByDevice($DeviceID) { + global $dbh; + + $DeviceID = intval($DeviceID); + + $sql = "SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID ORDER BY Label ASC"; + + $stmt = $dbh->prepare($sql); + $stmt->execute([":DeviceID" => $DeviceID]); + + $hddList = array(); + while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ + $hddList[] = self::RowToObject($row); + } + + return $hddList; + } + + static function SearchBySerial($SerialNo) { + global $dbh; + + $SerialNo = "%".sanitize($SerialNo)."%"; + + $sql = "SELECT * FROM fac_HDD WHERE SerialNo LIKE :SerialNo"; + + $stmt = $dbh->prepare($sql); + $stmt->execute([":SerialNo" => $SerialNo]); + + $hddList = array(); + while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ + $hddList[] = self::RowToObject($row); + } + + return $hddList; + } + + static function GetPendingDestruction() { + global $dbh; + + $sql = "SELECT * FROM fac_HDD WHERE StatusDestruction = 'pending'"; + + $stmt = $dbh->query($sql); + + $hddList = array(); + while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ + $hddList[] = self::RowToObject($row); + } + + return $hddList; + } +} +?> diff --git a/configuration.php b/configuration.php index 029605263..e96fb2d80 100644 --- a/configuration.php +++ b/configuration.php @@ -1928,6 +1928,16 @@ function uploadifive() {
+
+
+
+
+
+
+
diff --git a/db-23.04-to-24.01.sql b/db-23.04-to-24.01.sql index 22c9ac1e1..9c8f6bac0 100755 --- a/db-23.04-to-24.01.sql +++ b/db-23.04-to-24.01.sql @@ -3,3 +3,27 @@ --- UPDATE fac_Config set Value="24.01" WHERE Parameter="Version"; +INSERT INTO fac_Config (Parameter, Value) VALUES ('feature_hdd', 'disabled') +ON DUPLICATE KEY UPDATE Value = Value; +ALTER TABLE fac_People ADD COLUMN ManageHDD TINYINT(1) DEFAULT 0; +CREATE TABLE fac_DeviceTemplateHdd ( + TemplateID INT NOT NULL, + EnableHDDFeature TINYINT(1) DEFAULT 0, + HDDCount INT DEFAULT 0, + PRIMARY KEY (TemplateID) +); +CREATE TABLE fac_HDD ( + HDDID INT NOT NULL AUTO_INCREMENT, + DeviceID INT NOT NULL, + Label VARCHAR(100), + SerialNo VARCHAR(100) UNIQUE, + Status ENUM('on','off','replace','pending_destruction','destroyed_h2') DEFAULT 'on', + Size INT, -- en Go + TypeMedia ENUM('SATA', 'SCSI', 'SD'), + DateAdd DATETIME DEFAULT CURRENT_TIMESTAMP, + DateWithdrawn DATETIME, + DateDestruction DATETIME, + StatusDestruction ENUM('none', 'pending', 'destroyed'), + Note TEXT, + PRIMARY KEY (HDDID) +); \ No newline at end of file diff --git a/device_templates.php b/device_templates.php index a09fcc480..f42cea5ee 100644 --- a/device_templates.php +++ b/device_templates.php @@ -65,6 +65,7 @@ } // Transfers are done, delete this shit $template->DeleteTemplate(); + $tempplate->DeleteTemplateHDD(); // feature management hdd: delete } echo '1'; exit; @@ -82,6 +83,7 @@ $template->TemplateID=$_REQUEST['TemplateID']; $template->GetTemplateByID(); $deviceList = Device::GetDevicesByTemplate( $template->TemplateID ); + $Template->LoadHDDConfig(); // feature management hdd: Load } if(isset($_POST['action'])){ @@ -240,6 +242,7 @@ function updatecdu($template,$status){ if($template->CreateTemplate()){ $oldstatus=$status; $status=UpdateSlotsPorts($template,$status); + $template->UpdateTemplateHDD(); // feature management hdd if($oldstatus==$status){ $status=UpdateCustomValues($template,$status); } @@ -255,6 +258,7 @@ function updatecdu($template,$status){ $status=($template->UpdateTemplate())?__("Updated"):__("Error updating template"); if($status==__("Updated")){ $status=UpdateSlotsPorts($template,$status); + $template->UpdateTemplateHDD();// feature management hdd } if($status==__("Updated")){ $status=UpdateCustomValues($template,$status); @@ -271,6 +275,7 @@ function updatecdu($template,$status){ $status=($template->UpdateTemplate())?__("Updated"):__("Error updating template"); if ($status==__("Updated")){ $status=UpdateSlotsPorts($template,$status); + $template->UpdateTemplateHDD();// feature management hdd } if ($status==__("Updated")){ $status=UpdateCustomValues($template,$status); @@ -288,6 +293,7 @@ function updatecdu($template,$status){ $status=($template->UpdateTemplate())?__("Updated"):__("Error"); if ($status==__("Updated")){ $status=UpdateSlotsPorts($template,$status); + $template->UpdateTemplateHDD();// feature management hdd } if ($status==__("Updated")){ $status=UpdateCustomValues($template,$status); @@ -826,6 +832,26 @@ function applynames(inputs,portnames,e){
'; +// feature management hdd +if($config->ParameterArray['feature_hdd'] == 'enabled'){ + echo ' +
+
+
+
+ +
+
+
+
+
+
+
'; +} + foreach($dcaList as $dca) { $templatedcaChecked = ""; $templatedcaDisabled = ""; diff --git a/devices.php b/devices.php index 26d2e822d..992f6d5d4 100644 --- a/devices.php +++ b/devices.php @@ -2065,7 +2065,22 @@ function setPreferredLayout() { '; } - + //feature management hdd + if( + $config->ParameterArray['feature_hdd'] == 'enabled' && + $template->EnableHDDFeature == 1 && + $dev->DeviceID > 0 && + $person->ManageHDD == 1 + ){ + echo '
+ ',__("Manage HDD"),' +
',__("This device supports HDD management."),'
+
+ ', __("Manage HDDs"),' +
+
'; + } + //device images echo '
'.__("Device Images").'
'; diff --git a/managementhdd.php b/managementhdd.php new file mode 100644 index 000000000..0acaeba93 --- /dev/null +++ b/managementhdd.php @@ -0,0 +1,103 @@ +ManageHDD) { + header("Location: index.php"); + exit; +} + +$device = new Device(); + +if (isset($_GET['DeviceID']) && is_numeric($_GET['DeviceID'])) { + $device->DeviceID = intval($_GET['DeviceID']); + if (!$device->GetDevice()) { + echo __("Invalid DeviceID"); + exit; + } +} else { + echo __("DeviceID is required"); + exit; +} + +$template = new DeviceTemplate(); +$template->TemplateID = $device->TemplateID; +$template->GetTemplateByID(); +$template->LoadHDDConfig(); + +$hddList = HDD::GetHDDByDevice($device->DeviceID); +?> + + + + + + openDCIM Data Center Inventory + + + + + + + + +
+ +
+
+

Label); ?>

+
+ + + + + + + + + + + + + + + + + + + + + + + + "; + $i++; +} +?> + +
#
$i
+ +
+
+
+ +
+ + diff --git a/usermgr.php b/usermgr.php index b9c23e541..7b513abe4 100644 --- a/usermgr.php +++ b/usermgr.php @@ -47,6 +47,7 @@ $userRights->RackAdmin=(isset($_POST['RackAdmin']))?1:0; $userRights->BulkOperations=(isset($_POST['BulkOperations']))?1:0; $userRights->SiteAdmin=(isset($_POST['SiteAdmin']))?1:0; + $userRights->ManageHDD=(isset($_POST['ManageHDD']))?1:0; $userRights->Disabled=(isset($_POST['Disabled']))?1:0; if($_POST['action']=='Create'){ @@ -87,6 +88,7 @@ $RackAdmin=($userRights->RackAdmin)?"checked":""; $BulkOperations=($userRights->BulkOperations)?"checked":""; $admin=($userRights->SiteAdmin)?"checked":""; + $ManageHDD=($userRights->ManageHDD)?"checked":""; $Disabled=($userRights->Disabled)?"checked":""; ?> @@ -328,6 +330,7 @@ function showdept(){


+

From 78e0f06bd6dd4682f536b0f4f79038df34ded8c2 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Wed, 7 May 2025 19:27:00 +0200 Subject: [PATCH 02/25] feature_managementhdd --- classes/hdd.class.php | 127 ++++++++++++++++++++++++++++++++++++++---- hdd_log_view.php | 71 +++++++++++++++++++++++ managementhdd.php | 80 ++++++++++++++++++++++---- savehdd.php | 91 ++++++++++++++++++++++++++++++ 4 files changed, 347 insertions(+), 22 deletions(-) create mode 100644 hdd_log_view.php create mode 100644 savehdd.php diff --git a/classes/hdd.class.php b/classes/hdd.class.php index 4d1d148b7..7eff65684 100644 --- a/classes/hdd.class.php +++ b/classes/hdd.class.php @@ -1,17 +1,17 @@ HDDID = intval($this->HDDID); @@ -189,4 +189,107 @@ static function GetPendingDestruction() { return $hddList; } } + + private static function logAction($action, $HDDID) { + global $person, $dbh; + $sql = "INSERT INTO fac_GenericLog (UserID, Time, ItemType, ItemID, LogText) VALUES (?, NOW(), 'HDD', ?, ?)"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$person->UserID, $HDDID, $action]); + } + + public static function WithdrawByID($id) { + global $dbh; + $sql = "UPDATE fac_HDD SET Status='pending_destruction', dateWithdrawn=NOW() WHERE HDDID=?"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$id]); + self::logAction("Withdrawn (pending destruction)", $id); + } + + public static function DeleteByID($id) { + global $dbh; + $sql = "DELETE FROM fac_HDD WHERE HDDID=?"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$id]); + self::logAction("Deleted", $id); + } + + public static function MarkDestroyed($id) { + global $dbh; + $sql = "UPDATE fac_HDD SET StatusDestruction='destroyed', dateDestruction=NOW() WHERE HDDID=?"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$id]); + self::logAction("Marked as destroyed", $id); + } + + public static function ReassignToDevice($id, $deviceID) { + global $dbh; + $sql = "UPDATE fac_HDD SET Status='on', DeviceID=?, dateWithdrawn=NULL, dateDestruction=NULL, StatusDestruction=NULL WHERE HDDID=?"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$deviceID, $id]); + self::logAction("Reassigned to DeviceID $deviceID", $id); + } + + public static function MarkAsSpare($id) { + global $dbh; + $sql = "UPDATE fac_HDD SET Status='spare' WHERE HDDID=?"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$id]); + self::logAction("Marked as spare", $id); + } + + public static function CreateEmpty($deviceID) { + global $dbh; + $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, TypeMedia, Size, dateAdd) VALUES (?, '', '', 'on', 'SATA', 0, NOW())"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$deviceID]); + $id = $dbh->lastInsertId(); + self::logAction("Created new empty HDD", $id); + } + + public static function DuplicateToEmptySlots($sourceHDDID) { + global $dbh; + $sql = "SELECT * FROM fac_HDD WHERE HDDID=?"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$sourceHDDID]); + if (!$hdd = $stmt->fetch(PDO::FETCH_ASSOC)) return; + + $sql = "SELECT DeviceID FROM fac_HDD WHERE HDDID=?"; + $devID = $dbh->prepare($sql); + $devID->execute([$sourceHDDID]); + $DeviceID = $devID->fetchColumn(); + + $sql = "SELECT HDDCount FROM fac_DeviceTemplateHdd dt INNER JOIN fac_Device d ON d.TemplateID=dt.TemplateID WHERE d.DeviceID=?"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$DeviceID]); + $max = $stmt->fetchColumn(); + + $sql = "SELECT COUNT(*) FROM fac_HDD WHERE DeviceID=?"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$DeviceID]); + $current = $stmt->fetchColumn(); + + $remaining = $max - $current; + if ($remaining <= 0) return; + + $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, TypeMedia, Size, dateAdd) VALUES (?, ?, '', ?, ?, ?, NOW())"; + $stmt = $dbh->prepare($sql); + for ($i = 0; $i < $remaining; $i++) { + $stmt->execute([$DeviceID, $hdd['Label'], $hdd['Status'], $hdd['TypeMedia'], $hdd['Size']]); + $id = $dbh->lastInsertId(); + self::logAction("Duplicated from HDDID $sourceHDDID", $id); + } + } + + public static function ExportPendingDestruction($deviceID) { + global $dbh; + $sql = "SELECT Label, SerialNo, dateWithdrawn FROM fac_HDD WHERE DeviceID=? AND Status='pending_destruction'"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$deviceID]); + + echo "Label\tSerial Number\tDate Withdrawn\n"; + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + echo "{$row['Label']}\t{$row['SerialNo']}\t{$row['dateWithdrawn']}\n"; + } + } +} ?> diff --git a/hdd_log_view.php b/hdd_log_view.php new file mode 100644 index 000000000..80e60a318 --- /dev/null +++ b/hdd_log_view.php @@ -0,0 +1,71 @@ +ManageHDD) { + header("Location: index.php"); + exit; +} + +$deviceID = isset($_GET['DeviceID']) ? intval($_GET['DeviceID']) : 0; + +if (!$deviceID) { + echo __("DeviceID is required"); + exit; +} + +$sql = " + SELECT g.Time, g.UserID, g.LogText, h.Label, h.SerialNo + FROM fac_GenericLog g + JOIN fac_HDD h ON g.ItemID = h.hddID + WHERE g.ItemType = 'HDD' AND h.DeviceID = ? + ORDER BY g.Time DESC +"; + +$stmt = $dbh->prepare($sql); +$stmt->execute([$deviceID]); +$logEntries = $stmt->fetchAll(PDO::FETCH_ASSOC); +?> + + + + + + <?php echo $subheader; ?> + + + + +
+ +
+

+ + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/managementhdd.php b/managementhdd.php index 0acaeba93..16f09550a 100644 --- a/managementhdd.php +++ b/managementhdd.php @@ -28,7 +28,13 @@ $template->GetTemplateByID(); $template->LoadHDDConfig(); +if (!$template->EnableHDDFeature) { + echo '
'.__("This equipment does not support HDD management.").'
'; + exit; +} + $hddList = HDD::GetHDDByDevice($device->DeviceID); +$hddWaitList = HDD::GetRetiredHDDByDevice($device->DeviceID); ?> @@ -38,11 +44,13 @@ openDCIM Data Center Inventory - + @@ -54,9 +62,11 @@
+

+ @@ -71,32 +81,82 @@ $i = 1; foreach ($hddList as $hdd) { echo " + - - - + + - - - + + "; $i++; } ?>
#
$i + + + + +
- +

+ + + +

+ +

+ + + + + + + + + + + + + + + + + + "; +} +?> + +
" . htmlentities($hdd->Label) . "" . htmlentities($hdd->SerialNo) . "{$hdd->dateWithdrawn} + + + +
+

+ + +

+
+
+ + + +
diff --git a/savehdd.php b/savehdd.php new file mode 100644 index 000000000..a85b3f7ee --- /dev/null +++ b/savehdd.php @@ -0,0 +1,91 @@ +ManageHDD) { + header("Location: index.php"); + exit; +} + +$deviceID = isset($_POST['DeviceID']) ? intval($_POST['DeviceID']) : 0; + +if (!$deviceID) { + header("Location: index.php"); + exit; +} + +$action = isset($_POST['action']) ? $_POST['action'] : ''; + +switch (true) { + case preg_match('/^update_(\d+)$/', $action, $m): + $hdd = new HDD(); + $hdd->HDDID = $m[1]; + $hdd->Label = $_POST['Label'][$hdd->HDDID]; + $hdd->SerialNo = $_POST['SerialNo'][$hdd->HDDID]; + $hdd->Status = $_POST['Status'][$hdd->HDDID]; + $hdd->TypeMedia = $_POST['TypeMedia'][$hdd->HDDID]; + $hdd->Size = $_POST['Size'][$hdd->HDDID]; + $hdd->Update(); + break; + + case preg_match('/^remove_(\d+)$/', $action, $m): + $hdd = new HDD(); + $hdd->HDDID = $m[1]; + $hdd->Withdraw(); + break; + + case preg_match('/^delete_(\d+)$/', $action, $m): + $hdd = new HDD(); + $hdd->HDDID = $m[1]; + $hdd->Delete(); + break; + + case preg_match('/^duplicate_(\d+)$/', $action, $m): + HDD::DuplicateToEmptySlots($m[1]); + break; + + case preg_match('/^destroy_(\d+)$/', $action, $m): + HDD::MarkDestroyed($m[1]); + break; + + case preg_match('/^reassign_(\d+)$/', $action, $m): + HDD::ReassignToDevice($m[1], $deviceID); + break; + + case preg_match('/^spare_(\d+)$/', $action, $m): + HDD::MarkAsSpare($m[1]); + break; + + case $action === "add_hdd": + HDD::CreateEmpty($deviceID); + break; + + case $action === "bulk_remove": + foreach ($_POST['select_active'] ?? [] as $id) { + HDD::WithdrawByID($id); + } + break; + + case $action === "bulk_delete": + foreach ($_POST['select_active'] ?? [] as $id) { + HDD::DeleteByID($id); + } + break; + + case $action === "bulk_destroy": + foreach ($_POST['select_pending'] ?? [] as $id) { + HDD::MarkDestroyed($id); + } + break; + + case $action === "print_list": + header('Content-Type: application/vnd.ms-excel'); + header('Content-Disposition: attachment; filename="HDD_List_Device_' . $deviceID . '.xls"'); + HDD::ExportPendingDestruction($deviceID); + exit; + break; +} + +header("Location: managementhdd.php?DeviceID=$deviceID"); +exit; From 0b5beac3de93b47b2c6cd3b8dc24eccf88a8da96 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Wed, 7 May 2025 19:31:51 +0200 Subject: [PATCH 03/25] feature_managementhdd --- devices.php | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/devices.php b/devices.php index 992f6d5d4..5107ae19b 100644 --- a/devices.php +++ b/devices.php @@ -2052,19 +2052,9 @@ function setPreferredLayout() {DeviceType){$selected=" selected";}else{$selected="";} print "\t\t\t\n"; } -echo ' + echo ' - - -'; - - if ($dev->DeviceType=='Sensor'){ -echo '
- '.__("Sensor Readings").' -
-
-
'; - } + '; //feature management hdd if( $config->ParameterArray['feature_hdd'] == 'enabled' && @@ -2072,14 +2062,24 @@ function setPreferredLayout() {DeviceID > 0 && $person->ManageHDD == 1 ){ - echo '
- ',__("Manage HDD"),' -
',__("This device supports HDD management."),'
-
- ', __("Manage HDDs"),' + echo ' +
+
+
+
',__("Manage HDDs"),'
+
'; + } + echo' +
'; + + if ($dev->DeviceType=='Sensor'){ +echo '
+ '.__("Sensor Readings").' +
-
'; + '; } + //device images echo '
'.__("Device Images").' From 32deb20ed7dff4fd53ba1eb6947e877341d1dbf1 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Wed, 7 May 2025 20:03:03 +0200 Subject: [PATCH 04/25] feature_managementhdd --- classes/DeviceTemplate.class.php | 2 + classes/hdd.class.php | 392 ++++++++++++++++--------------- 2 files changed, 205 insertions(+), 189 deletions(-) diff --git a/classes/DeviceTemplate.class.php b/classes/DeviceTemplate.class.php index ef7a67a8e..b19e4d39c 100644 --- a/classes/DeviceTemplate.class.php +++ b/classes/DeviceTemplate.class.php @@ -103,6 +103,7 @@ static function RowToObject($row,$extendmodel=true){ $Template->GlobalID = $row["GlobalID"]; $Template->MakeDisplay(); $Template->GetCustomValues(); + $Template->LoadHDDConfig(); if($extendmodel){ // Extend our device model @@ -254,6 +255,7 @@ function DeleteTemplate(){ // If we're removing the template clean up the children $this->DeleteSlots(); $this->DeletePorts(); + $this->DeleteTemplateHDD(); //feature managementHdd $sql="DELETE FROM fac_DeviceTemplate WHERE TemplateID=$this->TemplateID;"; (class_exists('LogActions'))?LogActions::LogThis($this):''; diff --git a/classes/hdd.class.php b/classes/hdd.class.php index 7eff65684..eaf999a02 100644 --- a/classes/hdd.class.php +++ b/classes/hdd.class.php @@ -1,205 +1,214 @@ HDDID = intval($this->HDDID); - $this->DeviceID = intval($this->DeviceID); - $this->Label = sanitize($this->Label); - $this->SerialNo = sanitize($this->SerialNo); - $this->Status = sanitize($this->Status); - $this->Size = intval($this->Size); - $this->TypeMedia = sanitize($this->TypeMedia); - $this->Note = sanitize($this->Note); - } - - function MakeDisplay() { - // Si besoin d'afficher des données formatées - } - - static function RowToObject($row){ - $hdd = new HDD(); - foreach($row as $prop => $val){ - $hdd->$prop = $val; - } - $hdd->MakeDisplay(); - return $hdd; - } - - function CreateHDD() { - global $dbh; - - $this->MakeSafe(); - - $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, Size, TypeMedia, DateAdd, StatusDestruction, Note) - VALUES (:DeviceID, :Label, :SerialNo, :Status, :Size, :TypeMedia, NOW(), 'none', :Note)"; - - $stmt = $dbh->prepare($sql); - $stmt->execute([ - ":DeviceID" => $this->DeviceID, - ":Label" => $this->Label, - ":SerialNo" => $this->SerialNo, - ":Status" => $this->Status, - ":Size" => $this->Size, - ":TypeMedia" => $this->TypeMedia, - ":Note" => $this->Note - ]); - - $this->HDDID = $dbh->lastInsertId(); - } - - function UpdateHDD() { - global $dbh; - - $this->MakeSafe(); - - $sql = "UPDATE fac_HDD SET - DeviceID = :DeviceID, - Label = :Label, - SerialNo = :SerialNo, - Status = :Status, - Size = :Size, - TypeMedia = :TypeMedia, - Note = :Note - WHERE HDDID = :HDDID"; - - $stmt = $dbh->prepare($sql); - return $stmt->execute([ - ":DeviceID" => $this->DeviceID, - ":Label" => $this->Label, - ":SerialNo" => $this->SerialNo, - ":Status" => $this->Status, - ":Size" => $this->Size, - ":TypeMedia" => $this->TypeMedia, - ":Note" => $this->Note, - ":HDDID" => $this->HDDID - ]); - } - - function DeleteHDD() { - global $dbh; - - $this->MakeSafe(); - - $sql = "DELETE FROM fac_HDD WHERE HDDID = :HDDID"; - - $stmt = $dbh->prepare($sql); - return $stmt->execute([":HDDID" => $this->HDDID]); - } - - function SendForDestruction($note = '') { - global $dbh; - - $this->MakeSafe(); - $note = sanitize($note); - - $sql = "UPDATE fac_HDD SET - Status = 'pending_destruction', - StatusDestruction = 'pending', - DateWithdrawn = NOW(), - Note = CONCAT(Note, ' ', :Note) - WHERE HDDID = :HDDID"; - - $stmt = $dbh->prepare($sql); - return $stmt->execute([ - ":HDDID" => $this->HDDID, - ":Note" => $note - ]); - } - - function MarkAsDestroyed($note = '') { - global $dbh; - - $this->MakeSafe(); - $note = sanitize($note); - - $sql = "UPDATE fac_HDD SET - Status = 'destroyed_h2', - StatusDestruction = 'destroyed', - DateDestruction = NOW(), - Note = CONCAT(Note, ' ', :Note) - WHERE HDDID = :HDDID"; - - $stmt = $dbh->prepare($sql); - return $stmt->execute([ - ":HDDID" => $this->HDDID, - ":Note" => $note - ]); - } + public $HDDID; + public $DeviceID; + public $Label; + public $SerialNo; + public $Status; + public $Size; + public $TypeMedia; + public $DateAdd; + public $DateWithdrawn; + public $DateDestruction; + public $StatusDestruction; + public $Note; + + function MakeSafe() { + $this->HDDID = intval($this->HDDID); + $this->DeviceID = intval($this->DeviceID); + $this->Label = sanitize($this->Label); + $this->SerialNo = sanitize($this->SerialNo); + $this->Status = sanitize($this->Status); + $this->Size = intval($this->Size); + $this->TypeMedia = sanitize($this->TypeMedia); + $this->Note = sanitize($this->Note); + } - static function GetHDDByDevice($DeviceID) { - global $dbh; - - $DeviceID = intval($DeviceID); - - $sql = "SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID ORDER BY Label ASC"; - - $stmt = $dbh->prepare($sql); - $stmt->execute([":DeviceID" => $DeviceID]); - - $hddList = array(); - while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ - $hddList[] = self::RowToObject($row); - } + function MakeDisplay() { + // Placeholder pour formatage si nécessaire + } - return $hddList; - } + static function RowToObject($row) { + $hdd = new HDD(); + foreach ($row as $prop => $val) { + $hdd->$prop = $val; + } + $hdd->MakeDisplay(); + return $hdd; + } - static function SearchBySerial($SerialNo) { - global $dbh; + public static function GetHDDByID($id) { + global $dbh; + $id = intval($id); + $sql = "SELECT * FROM fac_HDD WHERE HDDID = ?"; + $stmt = $dbh->prepare($sql); + $stmt->execute([$id]); + if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + return self::RowToObject($row); + } + return null; + } - $SerialNo = "%".sanitize($SerialNo)."%"; + function Create() { + global $dbh; + $this->MakeSafe(); - $sql = "SELECT * FROM fac_HDD WHERE SerialNo LIKE :SerialNo"; + $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, Size, TypeMedia, DateAdd, StatusDestruction, Note) + VALUES (:DeviceID, :Label, :SerialNo, :Status, :Size, :TypeMedia, NOW(), 'none', :Note)"; - $stmt = $dbh->prepare($sql); - $stmt->execute([":SerialNo" => $SerialNo]); + $stmt = $dbh->prepare($sql); + $stmt->execute([ + ":DeviceID" => $this->DeviceID, + ":Label" => $this->Label, + ":SerialNo" => $this->SerialNo, + ":Status" => $this->Status, + ":Size" => $this->Size, + ":TypeMedia" => $this->TypeMedia, + ":Note" => $this->Note + ]); + + $this->HDDID = $dbh->lastInsertId(); + self::logAction("Created", $this->HDDID); + } - $hddList = array(); - while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ - $hddList[] = self::RowToObject($row); - } + function Update() { + global $dbh; + $this->MakeSafe(); + + $sql = "UPDATE fac_HDD SET + DeviceID = :DeviceID, + Label = :Label, + SerialNo = :SerialNo, + Status = :Status, + Size = :Size, + TypeMedia = :TypeMedia, + Note = :Note + WHERE HDDID = :HDDID"; - return $hddList; - } + $stmt = $dbh->prepare($sql); + $result = $stmt->execute([ + ":DeviceID" => $this->DeviceID, + ":Label" => $this->Label, + ":SerialNo" => $this->SerialNo, + ":Status" => $this->Status, + ":Size" => $this->Size, + ":TypeMedia" => $this->TypeMedia, + ":Note" => $this->Note, + ":HDDID" => $this->HDDID + ]); + + if ($result) { + self::logAction("Updated", $this->HDDID); + } + return $result; + } - static function GetPendingDestruction() { - global $dbh; + function Delete() { + global $dbh; + $this->MakeSafe(); - $sql = "SELECT * FROM fac_HDD WHERE StatusDestruction = 'pending'"; + $sql = "DELETE FROM fac_HDD WHERE HDDID = :HDDID"; + $stmt = $dbh->prepare($sql); + $result = $stmt->execute([":HDDID" => $this->HDDID]); + if ($result) { + self::logAction("Deleted", $this->HDDID); + } + return $result; + } - $stmt = $dbh->query($sql); + function SendForDestruction($note = '') { + global $dbh; + $this->MakeSafe(); + $note = sanitize($note); - $hddList = array(); - while($row = $stmt->fetch(PDO::FETCH_ASSOC)){ - $hddList[] = self::RowToObject($row); - } + $sql = "UPDATE fac_HDD SET + Status = 'pending_destruction', + StatusDestruction = 'pending', + DateWithdrawn = NOW(), + Note = CONCAT(Note, ' ', :Note) + WHERE HDDID = :HDDID"; - return $hddList; - } -} + $stmt = $dbh->prepare($sql); + return $stmt->execute([ + ":HDDID" => $this->HDDID, + ":Note" => $note + ]); + } + + function MarkAsDestroyed($note = '') { + global $dbh; + $this->MakeSafe(); + $note = sanitize($note); + + $sql = "UPDATE fac_HDD SET + Status = 'destroyed_h2', + StatusDestruction = 'destroyed', + DateDestruction = NOW(), + Note = CONCAT(Note, ' ', :Note) + WHERE HDDID = :HDDID"; + $stmt = $dbh->prepare($sql); + return $stmt->execute([ + ":HDDID" => $this->HDDID, + ":Note" => $note + ]); + } + + static function GetHDDByDevice($DeviceID) { + global $dbh; + $DeviceID = intval($DeviceID); + + $sql = "SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID ORDER BY Label ASC"; + $stmt = $dbh->prepare($sql); + $stmt->execute([":DeviceID" => $DeviceID]); + + $list = []; + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $list[] = self::RowToObject($row); + } + return $list; + } + + static function SearchBySerial($SerialNo) { + global $dbh; + $SerialNo = "%" . sanitize($SerialNo) . "%"; + + $sql = "SELECT * FROM fac_HDD WHERE SerialNo LIKE :SerialNo"; + $stmt = $dbh->prepare($sql); + $stmt->execute([":SerialNo" => $SerialNo]); + + $list = []; + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $list[] = self::RowToObject($row); + } + return $list; + } + + static function GetPendingDestruction() { + global $dbh; + $sql = "SELECT * FROM fac_HDD WHERE StatusDestruction = 'pending'"; + $stmt = $dbh->query($sql); + + $list = []; + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $list[] = self::RowToObject($row); + } + return $list; + } + + // LOGGING private static function logAction($action, $HDDID) { global $person, $dbh; - $sql = "INSERT INTO fac_GenericLog (UserID, Time, ItemType, ItemID, LogText) VALUES (?, NOW(), 'HDD', ?, ?)"; + $sql = "INSERT INTO fac_GenericLog (UserID, Time, ItemType, ItemID, LogText) + VALUES (?, NOW(), 'HDD', ?, ?)"; $stmt = $dbh->prepare($sql); $stmt->execute([$person->UserID, $HDDID, $action]); } + // STATIC ACTIONS UTILITAIRES public static function WithdrawByID($id) { global $dbh; - $sql = "UPDATE fac_HDD SET Status='pending_destruction', dateWithdrawn=NOW() WHERE HDDID=?"; + $sql = "UPDATE fac_HDD SET Status='pending_destruction', DateWithdrawn=NOW() WHERE HDDID=?"; $stmt = $dbh->prepare($sql); $stmt->execute([$id]); self::logAction("Withdrawn (pending destruction)", $id); @@ -215,7 +224,7 @@ public static function DeleteByID($id) { public static function MarkDestroyed($id) { global $dbh; - $sql = "UPDATE fac_HDD SET StatusDestruction='destroyed', dateDestruction=NOW() WHERE HDDID=?"; + $sql = "UPDATE fac_HDD SET StatusDestruction='destroyed', DateDestruction=NOW() WHERE HDDID=?"; $stmt = $dbh->prepare($sql); $stmt->execute([$id]); self::logAction("Marked as destroyed", $id); @@ -223,7 +232,7 @@ public static function MarkDestroyed($id) { public static function ReassignToDevice($id, $deviceID) { global $dbh; - $sql = "UPDATE fac_HDD SET Status='on', DeviceID=?, dateWithdrawn=NULL, dateDestruction=NULL, StatusDestruction=NULL WHERE HDDID=?"; + $sql = "UPDATE fac_HDD SET Status='on', DeviceID=?, DateWithdrawn=NULL, DateDestruction=NULL, StatusDestruction=NULL WHERE HDDID=?"; $stmt = $dbh->prepare($sql); $stmt->execute([$deviceID, $id]); self::logAction("Reassigned to DeviceID $deviceID", $id); @@ -239,7 +248,8 @@ public static function MarkAsSpare($id) { public static function CreateEmpty($deviceID) { global $dbh; - $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, TypeMedia, Size, dateAdd) VALUES (?, '', '', 'on', 'SATA', 0, NOW())"; + $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd) + VALUES (?, '', '', 'on', 'SATA', 0, NOW())"; $stmt = $dbh->prepare($sql); $stmt->execute([$deviceID]); $id = $dbh->lastInsertId(); @@ -248,21 +258,23 @@ public static function CreateEmpty($deviceID) { public static function DuplicateToEmptySlots($sourceHDDID) { global $dbh; + $sql = "SELECT * FROM fac_HDD WHERE HDDID=?"; $stmt = $dbh->prepare($sql); $stmt->execute([$sourceHDDID]); if (!$hdd = $stmt->fetch(PDO::FETCH_ASSOC)) return; - $sql = "SELECT DeviceID FROM fac_HDD WHERE HDDID=?"; - $devID = $dbh->prepare($sql); - $devID->execute([$sourceHDDID]); - $DeviceID = $devID->fetchColumn(); + $DeviceID = $hdd['DeviceID']; - $sql = "SELECT HDDCount FROM fac_DeviceTemplateHdd dt INNER JOIN fac_Device d ON d.TemplateID=dt.TemplateID WHERE d.DeviceID=?"; + $sql = "SELECT dt.HDDCount FROM fac_DeviceTemplateHdd dt + INNER JOIN fac_Device d ON d.TemplateID = dt.TemplateID + WHERE d.DeviceID = ?"; $stmt = $dbh->prepare($sql); $stmt->execute([$DeviceID]); $max = $stmt->fetchColumn(); + if (!$max || !$DeviceID) return; + $sql = "SELECT COUNT(*) FROM fac_HDD WHERE DeviceID=?"; $stmt = $dbh->prepare($sql); $stmt->execute([$DeviceID]); @@ -271,7 +283,8 @@ public static function DuplicateToEmptySlots($sourceHDDID) { $remaining = $max - $current; if ($remaining <= 0) return; - $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, TypeMedia, Size, dateAdd) VALUES (?, ?, '', ?, ?, ?, NOW())"; + $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd) + VALUES (?, ?, '', ?, ?, ?, NOW())"; $stmt = $dbh->prepare($sql); for ($i = 0; $i < $remaining; $i++) { $stmt->execute([$DeviceID, $hdd['Label'], $hdd['Status'], $hdd['TypeMedia'], $hdd['Size']]); @@ -282,13 +295,14 @@ public static function DuplicateToEmptySlots($sourceHDDID) { public static function ExportPendingDestruction($deviceID) { global $dbh; - $sql = "SELECT Label, SerialNo, dateWithdrawn FROM fac_HDD WHERE DeviceID=? AND Status='pending_destruction'"; + $sql = "SELECT Label, SerialNo, DateWithdrawn FROM fac_HDD + WHERE DeviceID = ? AND Status = 'pending_destruction'"; $stmt = $dbh->prepare($sql); $stmt->execute([$deviceID]); echo "Label\tSerial Number\tDate Withdrawn\n"; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { - echo "{$row['Label']}\t{$row['SerialNo']}\t{$row['dateWithdrawn']}\n"; + echo "{$row['Label']}\t{$row['SerialNo']}\t{$row['DateWithdrawn']}\n"; } } } From 39724d90aa82c104e5fbdab04101623f3bdc8f10 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Thu, 8 May 2025 07:54:12 +0200 Subject: [PATCH 05/25] feature_managementhdd --- classes/DeviceTemplate.class.php | 21 +++++----- classes/hdd.class.php | 68 ++++++++++++++++++++++++-------- device_templates.php | 21 +++++----- devices.php | 22 +++++++---- managementhdd.php | 42 ++++++++++---------- 5 files changed, 110 insertions(+), 64 deletions(-) diff --git a/classes/DeviceTemplate.class.php b/classes/DeviceTemplate.class.php index b19e4d39c..d195f972f 100644 --- a/classes/DeviceTemplate.class.php +++ b/classes/DeviceTemplate.class.php @@ -40,9 +40,9 @@ class DeviceTemplate { var $SNMPVersion; var $CustomValues; var $GlobalID; - //for feature management hdd at the bottom of the page public $EnableHDDFeature = 0; public $HDDCount = 0; + public function __construct($dtid=false){ if($dtid){ @@ -245,6 +245,8 @@ function UpdateTemplate(){ (class_exists('LogActions'))?LogActions::LogThis($this,$old):''; $this->MakeDisplay(); + $this->UpdateTemplateHDD(); // manageHDD + return true; } } @@ -255,7 +257,8 @@ function DeleteTemplate(){ // If we're removing the template clean up the children $this->DeleteSlots(); $this->DeletePorts(); - $this->DeleteTemplateHDD(); //feature managementHdd + $this->DeleteTemplateHDD(); + $sql="DELETE FROM fac_DeviceTemplate WHERE TemplateID=$this->TemplateID;"; (class_exists('LogActions'))?LogActions::LogThis($this):''; @@ -753,12 +756,10 @@ static function getAvailableImages(){ } return $array; } - - // method for feature management hdd - //HDD data is a logical complement to the equipment model. It depends directly on the TemplateID. + //feature manager for HDD public function UpdateTemplateHDD() { global $dbh; - + $EnableHDDFeature = isset($_POST['EnableHDDFeature']) ? intval($_POST['EnableHDDFeature']) : 0; $HDDCount = isset($_POST['HDDCount']) ? intval($_POST['HDDCount']) : 0; @@ -772,11 +773,14 @@ public function UpdateTemplateHDD() { $sql = "INSERT INTO fac_DeviceTemplateHdd (TemplateID, EnableHDDFeature, HDDCount) VALUES (?, ?, ?)"; $dbh->prepare($sql)->execute([$this->TemplateID, $EnableHDDFeature, $HDDCount]); } + $this->EnableHDDFeature = $EnableHDDFeature; // update data in the object + $this->HDDCount = $HDDCount; // update HDD count in the object } public function LoadHDDConfig() { global $dbh; - + error_log("LoadHDDConfig called for TemplateID: " . $this->TemplateID); + $this->EnableHDDFeature = 0; $this->HDDCount = 0; @@ -818,5 +822,4 @@ public function ExportTemplateHDD($asJSON = false) { } } - -?> +?> \ No newline at end of file diff --git a/classes/hdd.class.php b/classes/hdd.class.php index eaf999a02..da8b11c83 100644 --- a/classes/hdd.class.php +++ b/classes/hdd.class.php @@ -1,4 +1,6 @@ query($sql); $list = []; @@ -199,19 +201,27 @@ static function GetPendingDestruction() { // LOGGING private static function logAction($action, $HDDID) { global $person, $dbh; - $sql = "INSERT INTO fac_GenericLog (UserID, Time, ItemType, ItemID, LogText) - VALUES (?, NOW(), 'HDD', ?, ?)"; + $sql = "INSERT INTO fac_GenericLog (UserID, Class, ObjectID, ChildID, Property, Action, OldVal, NewVal, Time) + VALUES (:UserID, 'HDD', :ObjectID, :ChildID, :Property, :Action, :OldVal, :NewVal, CURRENT_TIMESTAMP)"; $stmt = $dbh->prepare($sql); - $stmt->execute([$person->UserID, $HDDID, $action]); + $stmt->execute([ + ':UserID' => $person->UserID, + ':ObjectID' => $HDDID, + ':ChildID' => null, + ':Property' => null, + ':Action' => $action, + ':OldVal' => null, + ':NewVal' => null + ]); } // STATIC ACTIONS UTILITAIRES public static function WithdrawByID($id) { global $dbh; - $sql = "UPDATE fac_HDD SET Status='pending_destruction', DateWithdrawn=NOW() WHERE HDDID=?"; + $sql = "UPDATE fac_HDD SET Status='Pending_destruction', DateWithdrawn=NOW() WHERE HDDID=?"; $stmt = $dbh->prepare($sql); $stmt->execute([$id]); - self::logAction("Withdrawn (pending destruction)", $id); + self::logAction("Withdrawn (Pending destruction)", $id); } public static function DeleteByID($id) { @@ -224,7 +234,7 @@ public static function DeleteByID($id) { public static function MarkDestroyed($id) { global $dbh; - $sql = "UPDATE fac_HDD SET StatusDestruction='destroyed', DateDestruction=NOW() WHERE HDDID=?"; + $sql = "UPDATE fac_HDD SET StatusDestruction='Destroyed', DateDestruction=NOW() WHERE HDDID=?"; $stmt = $dbh->prepare($sql); $stmt->execute([$id]); self::logAction("Marked as destroyed", $id); @@ -232,7 +242,7 @@ public static function MarkDestroyed($id) { public static function ReassignToDevice($id, $deviceID) { global $dbh; - $sql = "UPDATE fac_HDD SET Status='on', DeviceID=?, DateWithdrawn=NULL, DateDestruction=NULL, StatusDestruction=NULL WHERE HDDID=?"; + $sql = "UPDATE fac_HDD SET Status='On', DeviceID=?, DateWithdrawn=NULL, DateDestruction=NULL, StatusDestruction=NULL WHERE HDDID=?"; $stmt = $dbh->prepare($sql); $stmt->execute([$deviceID, $id]); self::logAction("Reassigned to DeviceID $deviceID", $id); @@ -240,21 +250,28 @@ public static function ReassignToDevice($id, $deviceID) { public static function MarkAsSpare($id) { global $dbh; - $sql = "UPDATE fac_HDD SET Status='spare' WHERE HDDID=?"; + $sql = "UPDATE fac_HDD SET Status='Spare' WHERE HDDID=?"; $stmt = $dbh->prepare($sql); $stmt->execute([$id]); - self::logAction("Marked as spare", $id); + self::logAction("Marked as Spare", $id); } public static function CreateEmpty($deviceID) { global $dbh; + + $SerialNo = uniqid('HDD_', true); // Génère un identifiant unique $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd) - VALUES (?, '', '', 'on', 'SATA', 0, NOW())"; + VALUES (:DeviceID, '', :SerialNo, 'On', 'SATA', 0, NOW())"; $stmt = $dbh->prepare($sql); - $stmt->execute([$deviceID]); + $stmt->execute([ + ':DeviceID' => intval($deviceID), + ':SerialNo' => $SerialNo + ]); $id = $dbh->lastInsertId(); self::logAction("Created new empty HDD", $id); + return $id; } + public static function DuplicateToEmptySlots($sourceHDDID) { global $dbh; @@ -296,7 +313,7 @@ public static function DuplicateToEmptySlots($sourceHDDID) { public static function ExportPendingDestruction($deviceID) { global $dbh; $sql = "SELECT Label, SerialNo, DateWithdrawn FROM fac_HDD - WHERE DeviceID = ? AND Status = 'pending_destruction'"; + WHERE DeviceID = ? AND Status = 'Pending_destruction'"; $stmt = $dbh->prepare($sql); $stmt->execute([$deviceID]); @@ -305,5 +322,22 @@ public static function ExportPendingDestruction($deviceID) { echo "{$row['Label']}\t{$row['SerialNo']}\t{$row['DateWithdrawn']}\n"; } } + public static function GetRetiredHDDByDevice($DeviceID) { + global $dbh; + $DeviceID = intval($DeviceID); + $sql = "SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID AND Status = 'Retired'"; + $stmt = $dbh->prepare($sql); + $stmt->execute([':DeviceID' => $DeviceID]); + + $hddList = array(); + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $hdd = new HDD(); + foreach ($row as $key => $value) { + $hdd->$key = $value; + } + $hddList[] = $hdd; + } + return $hddList; + } } ?> diff --git a/device_templates.php b/device_templates.php index f42cea5ee..9c96e580f 100644 --- a/device_templates.php +++ b/device_templates.php @@ -1,4 +1,8 @@ TemplateID=$_REQUEST['TemplateID']; $template->GetTemplateByID(); $deviceList = Device::GetDevicesByTemplate( $template->TemplateID ); - $Template->LoadHDDConfig(); // feature management hdd: Load + $template->LoadHDDConfig(); // feature management hdd: Load } if(isset($_POST['action'])){ @@ -243,6 +247,7 @@ function updatecdu($template,$status){ $oldstatus=$status; $status=UpdateSlotsPorts($template,$status); $template->UpdateTemplateHDD(); // feature management hdd + //$template->LoadHDDConfig(); // load data from hdd if($oldstatus==$status){ $status=UpdateCustomValues($template,$status); } @@ -835,21 +840,17 @@ function applynames(inputs,portnames,e){ // feature management hdd if($config->ParameterArray['feature_hdd'] == 'enabled'){ echo ' -
-
- -
+
-
-
'; + '; } foreach($dcaList as $dca) { diff --git a/devices.php b/devices.php index 5107ae19b..74915675b 100644 --- a/devices.php +++ b/devices.php @@ -2058,17 +2058,23 @@ function setPreferredLayout() {ParameterArray['feature_hdd'] == 'enabled' && - $template->EnableHDDFeature == 1 && $dev->DeviceID > 0 && $person->ManageHDD == 1 ){ - echo ' - -
-
-
',__("Manage HDDs"),'
-
'; - } + $template = new DeviceTemplate($dev->TemplateID); + $template->GetTemplateByID(); + $template->LoadHDDConfig(); + if( + $template->EnableHDDFeature == 1 + ){ + echo ' +
+
+
',__("Manage HDDs"),'
+
'; + } + } + echo'
'; diff --git a/managementhdd.php b/managementhdd.php index 16f09550a..e6f38f551 100644 --- a/managementhdd.php +++ b/managementhdd.php @@ -1,4 +1,7 @@ - + $i - - - + + - - + - - - - + + + + "; $i++; @@ -130,14 +133,14 @@ function confirmDelete() { - + " . htmlentities($hdd->Label) . " " . htmlentities($hdd->SerialNo) . " {$hdd->dateWithdrawn} - - - + + + "; } @@ -146,18 +149,17 @@ function confirmDelete() {

- +

- - - -
+
- +
+ + From 2e1a528f795c77ddb71b4135ec767b6e8de276d6 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Fri, 9 May 2025 00:38:45 +0200 Subject: [PATCH 06/25] feature_managementhdd --- classes/hdd.class.php | 25 ++++++++++--- managementhdd.php | 81 ++++++++++++++++++++++++++++++++++++++++--- savehdd.php | 15 +++++++- 3 files changed, 112 insertions(+), 9 deletions(-) diff --git a/classes/hdd.class.php b/classes/hdd.class.php index da8b11c83..5ff3f328d 100644 --- a/classes/hdd.class.php +++ b/classes/hdd.class.php @@ -73,6 +73,23 @@ function Create() { self::logAction("Created", $this->HDDID); } + public static function CreateFromForm($deviceID, $label, $serialNo, $typeMedia, $size) { + global $dbh; + $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd) + VALUES (:DeviceID, :Label, :SerialNo, 'On', :TypeMedia, :Size, NOW())"; + $stmt = $dbh->prepare($sql); + $stmt->execute([ + ':DeviceID' => intval($deviceID), + ':Label' => sanitize($label), + ':SerialNo' => sanitize($serialNo), + ':TypeMedia' => sanitize($typeMedia), + ':Size' => intval($size) + ]); + $id = $dbh->lastInsertId(); + self::logAction("Created from form", $id); + return $id; + } + function Update() { global $dbh; $this->MakeSafe(); @@ -200,10 +217,10 @@ static function GetPendingDestruction() { // LOGGING private static function logAction($action, $HDDID) { - global $person, $dbh; - $sql = "INSERT INTO fac_GenericLog (UserID, Class, ObjectID, ChildID, Property, Action, OldVal, NewVal, Time) - VALUES (:UserID, 'HDD', :ObjectID, :ChildID, :Property, :Action, :OldVal, :NewVal, CURRENT_TIMESTAMP)"; - $stmt = $dbh->prepare($sql); + //global $person, $dbh; + //$sql = "INSERT INTO fac_GenericLog (UserID, Class, ObjectID, ChildID, Property, Action, OldVal, NewVal, Time) + // VALUES (:UserID, 'HDD', :ObjectID, :ChildID, :Property, :Action, :OldVal, :NewVal, CURRENT_TIMESTAMP)"; + //$stmt = $dbh->prepare($sql); $stmt->execute([ ':UserID' => $person->UserID, ':ObjectID' => $HDDID, diff --git a/managementhdd.php b/managementhdd.php index e6f38f551..26154ab9a 100644 --- a/managementhdd.php +++ b/managementhdd.php @@ -53,7 +53,32 @@ function confirmDelete() { return confirm(""); } + + function toggleAddHDDForm() { + document.getElementById("addHDDModal").style.display = "block"; + } + + function closeAddHDDForm() { + document.getElementById("addHDDModal").style.display = "none"; + } + @@ -113,7 +138,7 @@ function confirmDelete() {

- +

@@ -153,10 +178,58 @@ function confirmDelete() {

- - - + + + +
+ + + + + + + diff --git a/savehdd.php b/savehdd.php index a85b3f7ee..369b57dae 100644 --- a/savehdd.php +++ b/savehdd.php @@ -1,4 +1,4 @@ -DeviceID = $deviceID; + $hdd->Label = $_POST['Label_new'] ?? ''; + $hdd->SerialNo = $_POST['SerialNo_new'] ?? ''; + $hdd->Status = 'On'; + $hdd->TypeMedia = $_POST['TypeMedia_new'] ?? 'SATA'; + $hdd->Size = intval($_POST['Size_new'] ?? 0); + $hdd->Note = $_POST['Note_new'] ?? ''; + $hdd->Create(); + break; case $action === "bulk_remove": foreach ($_POST['select_active'] ?? [] as $id) { From 31874d5dc45bc365be375349ccf6e3faaff3bc18 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Tue, 13 May 2025 14:02:06 +0200 Subject: [PATCH 07/25] feature_managementhdd --- classes/hdd.class.php | 659 +++++++++++++++++++++--------------------- create.sql | 31 ++ db-23.04-to-24.01.sql | 4 +- managementhdd.php | 82 +++--- savehdd.php | 210 ++++++++------ 5 files changed, 526 insertions(+), 460 deletions(-) diff --git a/classes/hdd.class.php b/classes/hdd.class.php index 5ff3f328d..aaba1b75f 100644 --- a/classes/hdd.class.php +++ b/classes/hdd.class.php @@ -1,360 +1,347 @@ HDDID = intval($this->HDDID); - $this->DeviceID = intval($this->DeviceID); - $this->Label = sanitize($this->Label); - $this->SerialNo = sanitize($this->SerialNo); - $this->Status = sanitize($this->Status); - $this->Size = intval($this->Size); - $this->TypeMedia = sanitize($this->TypeMedia); - $this->Note = sanitize($this->Note); - } - - function MakeDisplay() { - // Placeholder pour formatage si nécessaire - } - - static function RowToObject($row) { - $hdd = new HDD(); - foreach ($row as $prop => $val) { - $hdd->$prop = $val; - } - $hdd->MakeDisplay(); - return $hdd; - } - - public static function GetHDDByID($id) { - global $dbh; - $id = intval($id); - $sql = "SELECT * FROM fac_HDD WHERE HDDID = ?"; - $stmt = $dbh->prepare($sql); - $stmt->execute([$id]); - if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { - return self::RowToObject($row); - } - return null; - } - - function Create() { - global $dbh; - $this->MakeSafe(); - - $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, Size, TypeMedia, DateAdd, StatusDestruction, Note) - VALUES (:DeviceID, :Label, :SerialNo, :Status, :Size, :TypeMedia, NOW(), 'none', :Note)"; - - $stmt = $dbh->prepare($sql); - $stmt->execute([ - ":DeviceID" => $this->DeviceID, - ":Label" => $this->Label, - ":SerialNo" => $this->SerialNo, - ":Status" => $this->Status, - ":Size" => $this->Size, - ":TypeMedia" => $this->TypeMedia, - ":Note" => $this->Note - ]); - - $this->HDDID = $dbh->lastInsertId(); - self::logAction("Created", $this->HDDID); - } - - public static function CreateFromForm($deviceID, $label, $serialNo, $typeMedia, $size) { - global $dbh; - $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd) - VALUES (:DeviceID, :Label, :SerialNo, 'On', :TypeMedia, :Size, NOW())"; - $stmt = $dbh->prepare($sql); - $stmt->execute([ - ':DeviceID' => intval($deviceID), - ':Label' => sanitize($label), - ':SerialNo' => sanitize($serialNo), - ':TypeMedia' => sanitize($typeMedia), - ':Size' => intval($size) - ]); - $id = $dbh->lastInsertId(); - self::logAction("Created from form", $id); - return $id; - } - - function Update() { - global $dbh; - $this->MakeSafe(); - - $sql = "UPDATE fac_HDD SET - DeviceID = :DeviceID, - Label = :Label, - SerialNo = :SerialNo, - Status = :Status, - Size = :Size, - TypeMedia = :TypeMedia, - Note = :Note - WHERE HDDID = :HDDID"; - $stmt = $dbh->prepare($sql); - $result = $stmt->execute([ - ":DeviceID" => $this->DeviceID, - ":Label" => $this->Label, - ":SerialNo" => $this->SerialNo, - ":Status" => $this->Status, - ":Size" => $this->Size, - ":TypeMedia" => $this->TypeMedia, - ":Note" => $this->Note, - ":HDDID" => $this->HDDID - ]); - - if ($result) { - self::logAction("Updated", $this->HDDID); - } - return $result; - } - - function Delete() { - global $dbh; - $this->MakeSafe(); - - $sql = "DELETE FROM fac_HDD WHERE HDDID = :HDDID"; - $stmt = $dbh->prepare($sql); - $result = $stmt->execute([":HDDID" => $this->HDDID]); - if ($result) { - self::logAction("Deleted", $this->HDDID); - } - return $result; - } - - function SendForDestruction($note = '') { - global $dbh; - $this->MakeSafe(); - $note = sanitize($note); - - $sql = "UPDATE fac_HDD SET - Status = 'Pending_destruction', - StatusDestruction = 'Pending', - DateWithdrawn = NOW(), - Note = CONCAT(Note, ' ', :Note) - WHERE HDDID = :HDDID"; - - $stmt = $dbh->prepare($sql); - return $stmt->execute([ - ":HDDID" => $this->HDDID, - ":Note" => $note - ]); - } - - function MarkAsDestroyed($note = '') { - global $dbh; - $this->MakeSafe(); - $note = sanitize($note); - - $sql = "UPDATE fac_HDD SET - Status = 'Destroyed_h2', - StatusDestruction = 'Destroyed', - DateDestruction = NOW(), - Note = CONCAT(Note, ' ', :Note) - WHERE HDDID = :HDDID"; - - $stmt = $dbh->prepare($sql); - return $stmt->execute([ - ":HDDID" => $this->HDDID, - ":Note" => $note - ]); - } - - static function GetHDDByDevice($DeviceID) { - global $dbh; - $DeviceID = intval($DeviceID); - - $sql = "SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID ORDER BY Label ASC"; - $stmt = $dbh->prepare($sql); - $stmt->execute([":DeviceID" => $DeviceID]); - - $list = []; - while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { - $list[] = self::RowToObject($row); - } - return $list; - } - - static function SearchBySerial($SerialNo) { - global $dbh; - $SerialNo = "%" . sanitize($SerialNo) . "%"; - - $sql = "SELECT * FROM fac_HDD WHERE SerialNo LIKE :SerialNo"; - $stmt = $dbh->prepare($sql); - $stmt->execute([":SerialNo" => $SerialNo]); - - $list = []; - while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { - $list[] = self::RowToObject($row); - } - return $list; - } - - static function GetPendingDestruction() { +class HDD { + // Properties + public int $HDDID = 0; + public int $DeviceID = 0; + public string $Label; + public string $SerialNo; + public string $Status; + public int $Size; + public string $TypeMedia; + public DateTimeImmutable $DateAdd; + public DateTimeImmutable $DateWithdrawn; + public DateTimeImmutable $DateDestruction; + public string $StatusDestruction; + public ?string $Note; + + private LoggerInterface $logger; + + // Constructor + public function __construct(LoggerInterface $logger = null) { + $this->logger = $logger ?? new NullLogger(); + } + + // Sanitize data before database operations + public function MakeSafe(): void { + //$this->HDDID = intval($this->HDDID); + $this->DeviceID = intval($this->DeviceID); + $this->Label = sanitize($this->Label); + $this->SerialNo = sanitize($this->SerialNo); + $this->Status = sanitize($this->Status); + $this->Size = intval($this->Size); + $this->TypeMedia = sanitize($this->TypeMedia); + $this->StatusDestruction = sanitize($this->StatusDestruction); + $this->Note = sanitize($this->Note); + } + + // Format fields for display if needed + public function MakeDisplay(): void { + // e.g. $this->DateAdd = date('Y-m-d H:i:s', strtotime($this->DateAdd)); + } + + // Convert a PDO row into an HDD object + public static function RowToObject(array $row): self { + $hdd = new self(); + foreach ($row as $prop => $val) { + if (property_exists($hdd, $prop)) { + $hdd->$prop = $val; + } + } + $hdd->MakeDisplay(); + return $hdd; + } + + // Get a HDD by its ID + public static function GetHDDByID(int $id): ?self { + global $dbh; + $stmt = $dbh->prepare("SELECT * FROM fac_HDD WHERE HDDID = ?"); + $stmt->execute([$id]); + if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + return self::RowToObject($row); + } + return null; + } + + // Create a HDD (instance method) + public function Create(): void { + global $dbh; + $this->MakeSafe(); + $sql = "INSERT INTO fac_HDD + (DeviceID, Label, SerialNo, Status, Size, TypeMedia, DateAdd, StatusDestruction, Note) + VALUES + (:DeviceID, :Label, :SerialNo, :Status, :Size, :TypeMedia, NOW(), :StatusDestruction, :Note)"; + $stmt = $dbh->prepare($sql); + $stmt->execute([ + ":DeviceID" => $this->DeviceID, + ":Label" => $this->Label, + ":SerialNo" => $this->SerialNo, + ":Status" => $this->Status, + ":Size" => $this->Size, + ":TypeMedia" => $this->TypeMedia, + ":StatusDestruction" => $this->StatusDestruction, + ":Note" => $this->Note + ]); + $this->HDDID = intval($dbh->lastInsertId()); + self::logAction("Created", $this->HDDID); + } + + // Quick creation from form data + public static function CreateFromForm(int $deviceID, string $label, string $serialNo, string $typeMedia, int $size): int { + global $dbh; + $stmt = $dbh->prepare( + "INSERT INTO fac_HDD + (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd, StatusDestruction) + VALUES + (:DeviceID, :Label, :SerialNo, 'On', :TypeMedia, :Size, NOW(), 'none')" + ); + $stmt->execute([ + ':DeviceID' => $deviceID, + ':Label' => sanitize($label), + ':SerialNo' => sanitize($serialNo), + ':TypeMedia' => sanitize($typeMedia), + ':Size' => $size + ]); + $newId = intval($dbh->lastInsertId()); + self::logAction("Created from form", $newId); + return $newId; + } + + // Update an existing HDD + public function Update(): bool { + global $dbh; + $this->MakeSafe(); + $stmt = $dbh->prepare( + "UPDATE fac_HDD SET + DeviceID = :DeviceID, + Label = :Label, + SerialNo = :SerialNo, + Status = :Status, + Size = :Size, + TypeMedia = :TypeMedia, + Note = :Note + WHERE HDDID = :HDDID" + ); + $res = $stmt->execute([ + ":DeviceID" => $this->DeviceID, + ":Label" => $this->Label, + ":SerialNo" => $this->SerialNo, + ":Status" => $this->Status, + ":Size" => $this->Size, + ":TypeMedia" => $this->TypeMedia, + ":Note" => $this->Note, + ":HDDID" => $this->HDDID + ]); + if ($res) self::logAction("Updated", $this->HDDID); + return $res; + } + + // Delete this HDD + public static function DeleteByID(int $id): bool { global $dbh; - $sql = "SELECT * FROM fac_HDD WHERE StatusDestruction = 'Pending'"; - $stmt = $dbh->query($sql); - - $list = []; - while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { - $list[] = self::RowToObject($row); + $stmt = $dbh->prepare("DELETE FROM fac_HDD WHERE HDDID = ?"); + $res = $stmt->execute([$id]); + if ($res) { + self::logAction("Deleted", $id); } - return $list; - } - - // LOGGING - private static function logAction($action, $HDDID) { - //global $person, $dbh; - //$sql = "INSERT INTO fac_GenericLog (UserID, Class, ObjectID, ChildID, Property, Action, OldVal, NewVal, Time) - // VALUES (:UserID, 'HDD', :ObjectID, :ChildID, :Property, :Action, :OldVal, :NewVal, CURRENT_TIMESTAMP)"; - //$stmt = $dbh->prepare($sql); - $stmt->execute([ - ':UserID' => $person->UserID, - ':ObjectID' => $HDDID, - ':ChildID' => null, - ':Property' => null, - ':Action' => $action, - ':OldVal' => null, - ':NewVal' => null - ]); - } - - // STATIC ACTIONS UTILITAIRES - public static function WithdrawByID($id) { - global $dbh; - $sql = "UPDATE fac_HDD SET Status='Pending_destruction', DateWithdrawn=NOW() WHERE HDDID=?"; - $stmt = $dbh->prepare($sql); - $stmt->execute([$id]); - self::logAction("Withdrawn (Pending destruction)", $id); - } - - public static function DeleteByID($id) { - global $dbh; - $sql = "DELETE FROM fac_HDD WHERE HDDID=?"; - $stmt = $dbh->prepare($sql); - $stmt->execute([$id]); - self::logAction("Deleted", $id); - } - - public static function MarkDestroyed($id) { - global $dbh; - $sql = "UPDATE fac_HDD SET StatusDestruction='Destroyed', DateDestruction=NOW() WHERE HDDID=?"; - $stmt = $dbh->prepare($sql); - $stmt->execute([$id]); - self::logAction("Marked as destroyed", $id); - } - - public static function ReassignToDevice($id, $deviceID) { - global $dbh; - $sql = "UPDATE fac_HDD SET Status='On', DeviceID=?, DateWithdrawn=NULL, DateDestruction=NULL, StatusDestruction=NULL WHERE HDDID=?"; - $stmt = $dbh->prepare($sql); - $stmt->execute([$deviceID, $id]); - self::logAction("Reassigned to DeviceID $deviceID", $id); + return $res; } - public static function MarkAsSpare($id) { - global $dbh; - $sql = "UPDATE fac_HDD SET Status='Spare' WHERE HDDID=?"; - $stmt = $dbh->prepare($sql); - $stmt->execute([$id]); - self::logAction("Marked as Spare", $id); - } - - public static function CreateEmpty($deviceID) { + // Duplicate this HDD + public static function DuplicateToEmptySlots(int $sourceHDDID): void { global $dbh; + // Récupère le disque source + $stmt = $dbh->prepare("SELECT * FROM fac_HDD WHERE HDDID = ?"); + $stmt->execute([$sourceHDDID]); + $hdd = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$hdd) { + return; + }// Nombre max de HDD permis par template + $deviceID = intval($hdd['DeviceID']); + $stmt = $dbh->prepare( + "SELECT dt.HDDCount + FROM fac_DeviceTemplateHdd dt + JOIN fac_Device d ON d.TemplateID = dt.TemplateID + WHERE d.DeviceID = ?" + ); + $stmt->execute([$deviceID]); + $max = intval($stmt->fetchColumn()); - $SerialNo = uniqid('HDD_', true); // Génère un identifiant unique - $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd) - VALUES (:DeviceID, '', :SerialNo, 'On', 'SATA', 0, NOW())"; - $stmt = $dbh->prepare($sql); - $stmt->execute([ - ':DeviceID' => intval($deviceID), - ':SerialNo' => $SerialNo - ]); - $id = $dbh->lastInsertId(); - self::logAction("Created new empty HDD", $id); - return $id; - } + if ($max <= 0) { + return; + }// Combien sont déjà présents ? + $stmt = $dbh->prepare("SELECT COUNT(*) FROM fac_HDD WHERE DeviceID = ?"); + $stmt->execute([$deviceID]); + $current = intval($stmt->fetchColumn()); - - public static function DuplicateToEmptySlots($sourceHDDID) { - global $dbh; - - $sql = "SELECT * FROM fac_HDD WHERE HDDID=?"; - $stmt = $dbh->prepare($sql); - $stmt->execute([$sourceHDDID]); - if (!$hdd = $stmt->fetch(PDO::FETCH_ASSOC)) return; - - $DeviceID = $hdd['DeviceID']; - - $sql = "SELECT dt.HDDCount FROM fac_DeviceTemplateHdd dt - INNER JOIN fac_Device d ON d.TemplateID = dt.TemplateID - WHERE d.DeviceID = ?"; - $stmt = $dbh->prepare($sql); - $stmt->execute([$DeviceID]); - $max = $stmt->fetchColumn(); - - if (!$max || !$DeviceID) return; - - $sql = "SELECT COUNT(*) FROM fac_HDD WHERE DeviceID=?"; - $stmt = $dbh->prepare($sql); - $stmt->execute([$DeviceID]); - $current = $stmt->fetchColumn(); - $remaining = $max - $current; - if ($remaining <= 0) return; - - $sql = "INSERT INTO fac_HDD (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd) - VALUES (?, ?, '', ?, ?, ?, NOW())"; - $stmt = $dbh->prepare($sql); + if ($remaining <= 0) { + return; + } // Insère les duplicata + $stmt = $dbh->prepare( + "INSERT INTO fac_HDD + (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd) + VALUES + (?, ?, ?, ?, ?, ?, NOW())" + ); for ($i = 0; $i < $remaining; $i++) { - $stmt->execute([$DeviceID, $hdd['Label'], $hdd['Status'], $hdd['TypeMedia'], $hdd['Size']]); - $id = $dbh->lastInsertId(); - self::logAction("Duplicated from HDDID $sourceHDDID", $id); + $stmt->execute([ + $deviceID, + $hdd['Label'], + uniqid('HDD_', true), + $hdd['Status'], + $hdd['TypeMedia'], + $hdd['Size'] + ]); + $newId = intval($dbh->lastInsertId()); + self::logAction("Duplicated from HDDID $sourceHDDID", $newId); } } - public static function ExportPendingDestruction($deviceID) { + // Send HDD for destruction + public function SendForDestruction(string $note = ''): bool { + global $dbh; + $this->MakeSafe(); + $stmt = $dbh->prepare( + "UPDATE fac_HDD SET + Status = 'Pending_destruction', + StatusDestruction = 'pending', + DateWithdrawn = NOW(), + Note = CONCAT(Note, ' ', :Note) + WHERE HDDID = :HDDID" + ); + return $stmt->execute([ + ":HDDID" => $this->HDDID, + ":Note" => sanitize($note) + ]); + } + + // Mark HDD as destroyed + public static function MarkDestroyed(int $id): bool { global $dbh; - $sql = "SELECT Label, SerialNo, DateWithdrawn FROM fac_HDD - WHERE DeviceID = ? AND Status = 'Pending_destruction'"; - $stmt = $dbh->prepare($sql); - $stmt->execute([$deviceID]); - - echo "Label\tSerial Number\tDate Withdrawn\n"; - while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { - echo "{$row['Label']}\t{$row['SerialNo']}\t{$row['DateWithdrawn']}\n"; + $stmt = $dbh->prepare( + "UPDATE fac_HDD SET + StatusDestruction = 'destroyed', + DateDestruction = NOW() + WHERE HDDID = ?" + ); + $res = $stmt->execute([$id]); + if ($res) { + self::logAction("Marked as destroyed", $id); } + return $res; } - public static function GetRetiredHDDByDevice($DeviceID) { - global $dbh; - $DeviceID = intval($DeviceID); - $sql = "SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID AND Status = 'Retired'"; - $stmt = $dbh->prepare($sql); - $stmt->execute([':DeviceID' => $DeviceID]); - $hddList = array(); - while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { - $hdd = new HDD(); - foreach ($row as $key => $value) { - $hdd->$key = $value; + //Reassign a HDD to another device + public static function ReassignToDevice(int $id, int $deviceID): bool { + global $dbh; + $stmt = $dbh->prepare( + "UPDATE fac_HDD SET + DeviceID = ?, + Status = 'On', + DateWithdrawn = NULL, + DateDestruction = NULL, + StatusDestruction = 'none' + WHERE HDDID = ?" + ); + $res = $stmt->execute([$deviceID, $id]); + if ($res) { + self::logAction("Reassigned to DeviceID $deviceID", $id); } - $hddList[] = $hdd; + return $res; } - return $hddList; + + //Mark a HDD as spare by its ID + public static function MarkAsSpare(int $id): bool { + global $dbh; + $stmt = $dbh->prepare( + "UPDATE fac_HDD SET + Status = 'Spare' + WHERE HDDID = ?" + ); + $res = $stmt->execute([$id]); + if ($res) { + self::logAction("Marked as Spare", $id); + } + return $res; } + + // List active HDDs for a device + public static function GetHDDByDevice(int $DeviceID): array { + global $dbh; + $stmt = $dbh->prepare("SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID AND StatusDestruction = 'none' ORDER BY Label ASC"); + $stmt->execute([":DeviceID" => $DeviceID]); + $list = []; + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $list[] = self::RowToObject($row); + } + return $list; + } + + // List HDDs pending destruction + public static function GetPendingByDevice(int $DeviceID): array { + global $dbh; + $stmt = $dbh->prepare( + "SELECT * FROM fac_HDD + WHERE DeviceID = :DeviceID + AND StatusDestruction = 'pending' + ORDER BY DateWithdrawn DESC" + ); + $stmt->execute([':DeviceID' => $DeviceID]); + + $list = []; + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $list[] = self::RowToObject($row); + } + return $list; + } + + // List retired HDDs (destruction completed) + public static function GetRetiredHDDByDevice(int $DeviceID): array { + global $dbh; + $stmt = $dbh->prepare( + "SELECT * FROM fac_HDD + WHERE DeviceID = :DeviceID + AND StatusDestruction = 'destroyed' + ORDER BY DateDestruction DESC" + ); + $stmt->execute([":DeviceID" => $DeviceID]); + $list = []; + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $list[] = self::RowToObject($row); + } + return $list; + } + + // Search HDDs by serial number + public static function SearchBySerial(string $SerialNo): array { + global $dbh; + $stmt = $dbh->prepare("SELECT * FROM fac_HDD WHERE SerialNo LIKE :SerialNo"); + $stmt->execute([":SerialNo" => "%" . sanitize($SerialNo) . "%"]); + $list = []; + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $list[] = self::RowToObject($row); + } + return $list; + } + + // Generic logging to fac_GenericLog + private static function logAction(string $action, int $HDDID): void { + global $person, $dbh; + $stmt = $dbh->prepare( + "INSERT INTO fac_GenericLog + (UserID, Class, ObjectID, Action, Time) + VALUES + (:UserID, 'HDD', :ObjectID, :Action, CURRENT_TIMESTAMP)" + ); + $stmt->execute([ + ":UserID" => $person->UserID, + ":ObjectID" => $HDDID, + ":Action" => $action + ]); + } } -?> +?> \ No newline at end of file diff --git a/create.sql b/create.sql index 3ac925d65..450010cc9 100644 --- a/create.sql +++ b/create.sql @@ -1078,3 +1078,34 @@ CREATE TABLE fac_Country ( -- INSERT INTO `fac_Country` VALUES ('AD','Andorra'),('AE','United Arab Emirates'),('AF','Afghanistan'),('AG','Antigua and Barbuda'),('AI','Anguilla'),('AL','Albania'),('AM','Armenia'),('AO','Angola'),('AQ','Antarctica'),('AR','Argentina'),('AS','American Samoa'),('AT','Austria'),('AU','Australia'),('AW','Aruba'),('AX','Åland'),('AZ','Azerbaijan'),('BA','Bosnia and Herzegovina'),('BB','Barbados'),('BD','Bangladesh'),('BE','Belgium'),('BF','Burkina Faso'),('BG','Bulgaria'),('BH','Bahrain'),('BI','Burundi'),('BJ','Benin'),('BL','Saint Barthélemy'),('BM','Bermuda'),('BN','Brunei'),('BO','Bolivia'),('BQ','Bonaire, Sint Eustatius, and Saba'),('BR','Brazil'),('BS','Bahamas'),('BT','Bhutan'),('BV','Bouvet Island'),('BW','Botswana'),('BY','Belarus'),('BZ','Belize'),('CA','Canada'),('CC','Cocos (Keeling) Islands'),('CD','DR Congo'),('CF','Central African Republic'),('CG','Congo Republic'),('CH','Switzerland'),('CI','Ivory Coast'),('CK','Cook Islands'),('CL','Chile'),('CM','Cameroon'),('CN','China'),('CO','Colombia'),('CR','Costa Rica'),('CU','Cuba'),('CV','Cabo Verde'),('CW','Curaçao'),('CX','Christmas Island'),('CY','Cyprus'),('CZ','Czechia'),('DE','Germany'),('DJ','Djibouti'),('DK','Denmark'),('DM','Dominica'),('DO','Dominican Republic'),('DZ','Algeria'),('EC','Ecuador'),('EE','Estonia'),('EG','Egypt'),('EH','Western Sahara'),('ER','Eritrea'),('ES','Spain'),('ET','Ethiopia'),('FI','Finland'),('FJ','Fiji'),('FK','Falkland Islands'),('FM','Micronesia'),('FO','Faroe Islands'),('FR','France'),('GA','Gabon'),('GB','United Kingdom'),('GD','Grenada'),('GE','Georgia'),('GF','French Guiana'),('GG','Guernsey'),('GH','Ghana'),('GI','Gibraltar'),('GL','Greenland'),('GM','The Gambia'),('GN','Guinea'),('GP','Guadeloupe'),('GQ','Equatorial Guinea'),('GR','Greece'),('GS','South Georgia and South Sandwich Islands'),('GT','Guatemala'),('GU','Guam'),('GW','Guinea-Bissau'),('GY','Guyana'),('HK','Hong Kong'),('HM','Heard and McDonald Islands'),('HN','Honduras'),('HR','Croatia'),('HT','Haiti'),('HU','Hungary'),('ID','Indonesia'),('IE','Ireland'),('IL','Israel'),('IM','Isle of Man'),('IN','India'),('IO','British Indian Ocean Territory'),('IQ','Iraq'),('IR','Iran'),('IS','Iceland'),('IT','Italy'),('JE','Jersey'),('JM','Jamaica'),('JO','Jordan'),('JP','Japan'),('KE','Kenya'),('KG','Kyrgyzstan'),('KH','Cambodia'),('KI','Kiribati'),('KM','Comoros'),('KN','St Kitts and Nevis'),('KP','North Korea'),('KR','South Korea'),('KW','Kuwait'),('KY','Cayman Islands'),('KZ','Kazakhstan'),('LA','Laos'),('LB','Lebanon'),('LC','Saint Lucia'),('LI','Liechtenstein'),('LK','Sri Lanka'),('LR','Liberia'),('LS','Lesotho'),('LT','Lithuania'),('LU','Luxembourg'),('LV','Latvia'),('LY','Libya'),('MA','Morocco'),('MC','Monaco'),('MD','Moldova'),('ME','Montenegro'),('MF','Saint Martin'),('MG','Madagascar'),('MH','Marshall Islands'),('MK','North Macedonia'),('ML','Mali'),('MM','Myanmar'),('MN','Mongolia'),('MO','Macao'),('MP','Northern Mariana Islands'),('MQ','Martinique'),('MR','Mauritania'),('MS','Montserrat'),('MT','Malta'),('MU','Mauritius'),('MV','Maldives'),('MW','Malawi'),('MX','Mexico'),('MY','Malaysia'),('MZ','Mozambique'),('NA','Namibia'),('NC','New Caledonia'),('NE','Niger'),('NF','Norfolk Island'),('NG','Nigeria'),('NI','Nicaragua'),('NL','Netherlands'),('NO','Norway'),('NP','Nepal'),('NR','Nauru'),('NU','Niue'),('NZ','New Zealand'),('OM','Oman'),('PA','Panama'),('PE','Peru'),('PF','French Polynesia'),('PG','Papua New Guinea'),('PH','Philippines'),('PK','Pakistan'),('PL','Poland'),('PM','Saint Pierre and Miquelon'),('PN','Pitcairn Islands'),('PR','Puerto Rico'),('PS','Palestine'),('PT','Portugal'),('PW','Palau'),('PY','Paraguay'),('QA','Qatar'),('RE','Réunion'),('RO','Romania'),('RS','Serbia'),('RU','Russia'),('RW','Rwanda'),('SA','Saudi Arabia'),('SB','Solomon Islands'),('SC','Seychelles'),('SD','Sudan'),('SE','Sweden'),('SG','Singapore'),('SH','Saint Helena'),('SI','Slovenia'),('SJ','Svalbard and Jan Mayen'),('SK','Slovakia'),('SL','Sierra Leone'),('SM','San Marino'),('SN','Senegal'),('SO','Somalia'),('SR','Suriname'),('SS','South Sudan'),('ST','São Tomé and Príncipe'),('SV','El Salvador'),('SX','Sint Maarten'),('SY','Syria'),('SZ','Eswatini'),('TC','Turks and Caicos Islands'),('TD','Chad'),('TF','French Southern Territories'),('TG','Togo'),('TH','Thailand'),('TJ','Tajikistan'),('TK','Tokelau'),('TL','Timor-Leste'),('TM','Turkmenistan'),('TN','Tunisia'),('TO','Tonga'),('TR','Turkey'),('TT','Trinidad and Tobago'),('TV','Tuvalu'),('TW','Taiwan'),('TZ','Tanzania'),('UA','Ukraine'),('UG','Uganda'),('UM','U.S. Outlying Islands'),('US','United States'),('UY','Uruguay'),('UZ','Uzbekistan'),('VA','Vatican City'),('VC','St Vincent and Grenadines'),('VE','Venezuela'),('VG','British Virgin Islands'),('VI','U.S. Virgin Islands'),('VN','Vietnam'),('VU','Vanuatu'),('WF','Wallis and Futuna'),('WS','Samoa'),('XK','Kosovo'),('YE','Yemen'),('YT','Mayotte'),('ZA','South Africa'),('ZM','Zambia'),('ZW','Zimbabwe'); + +-- +-- Table for fac_HDD feature managementHDD +-- + +CREATE TABLE `fac_HDD` ( + HDDID int(11) NOT NULL AUTO_INCREMENT, + DeviceID int(11) NOT NULL, + Label varchar(100) DEFAULT NULL, + SerialNo varchar(100) DEFAULT NULL, + Status enum('On','Off','Retired','Pending_destruction','Destroyed_h2','Spare') DEFAULT 'On', + Size int(11) DEFAULT NULL, + TypeMedia enum('HDD','SSD','MVME') DEFAULT NULL, + DateAdd datetime DEFAULT current_timestamp(), + DateWithdrawn datetime DEFAULT NULL, + DateDestruction datetime DEFAULT NULL, + StatusDestruction enum('none','pending','destroyed') DEFAULT NULL, + Note text DEFAULT NULL, + PRIMARY KEY (HDDID) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Table for fac_DeviceTemplateHdd feature managementHDD +-- + + CREATE TABLE `fac_DeviceTemplateHdd` ( + TemplateID int(11) NOT NULL, + EnableHDDFeature tinyint(1) DEFAULT 0, + HDDCount int(11) DEFAULT 0, + PRIMARY KEY (TemplateID) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/db-23.04-to-24.01.sql b/db-23.04-to-24.01.sql index 9c8f6bac0..1a938ba18 100755 --- a/db-23.04-to-24.01.sql +++ b/db-23.04-to-24.01.sql @@ -17,9 +17,9 @@ CREATE TABLE fac_HDD ( DeviceID INT NOT NULL, Label VARCHAR(100), SerialNo VARCHAR(100) UNIQUE, - Status ENUM('on','off','replace','pending_destruction','destroyed_h2') DEFAULT 'on', + Status ENUM('On','Off','Replace','Pending_destruction','Destroyed_h2','Spare') DEFAULT 'On', Size INT, -- en Go - TypeMedia ENUM('SATA', 'SCSI', 'SD'), + TypeMedia ENUM('HDD', 'SSD', 'MVME'), DateAdd DATETIME DEFAULT CURRENT_TIMESTAMP, DateWithdrawn DATETIME, DateDestruction DATETIME, diff --git a/managementhdd.php b/managementhdd.php index 26154ab9a..57e6b9c01 100644 --- a/managementhdd.php +++ b/managementhdd.php @@ -36,8 +36,9 @@ exit; } -$hddList = HDD::GetHDDByDevice($device->DeviceID); -$hddWaitList = HDD::GetRetiredHDDByDevice($device->DeviceID); +$hddList = HDD::GetHDDByDevice($device->DeviceID); +$hddWaitList = HDD::GetPendingByDevice($device->DeviceID); + ?> @@ -113,16 +114,20 @@ function closeAddHDDForm() { $i - + + + @@ -182,57 +187,58 @@ function closeAddHDDForm() { - + + + - - + - diff --git a/savehdd.php b/savehdd.php index 369b57dae..831986d94 100644 --- a/savehdd.php +++ b/savehdd.php @@ -1,4 +1,10 @@ "; + require_once("db.inc.php"); require_once("facilities.inc.php"); require_once("classes/hdd.class.php"); @@ -15,90 +21,126 @@ exit; } -$action = isset($_POST['action']) ? $_POST['action'] : ''; - -switch (true) { - case preg_match('/^update_(\d+)$/', $action, $m): - $hdd = new HDD(); - $hdd->HDDID = $m[1]; - $hdd->Label = $_POST['Label'][$hdd->HDDID]; - $hdd->SerialNo = $_POST['SerialNo'][$hdd->HDDID]; - $hdd->Status = $_POST['Status'][$hdd->HDDID]; - $hdd->TypeMedia = $_POST['TypeMedia'][$hdd->HDDID]; - $hdd->Size = $_POST['Size'][$hdd->HDDID]; - $hdd->Update(); - break; - - case preg_match('/^remove_(\d+)$/', $action, $m): - $hdd = new HDD(); - $hdd->HDDID = $m[1]; - $hdd->Withdraw(); - break; - - case preg_match('/^delete_(\d+)$/', $action, $m): - $hdd = new HDD(); - $hdd->HDDID = $m[1]; - $hdd->Delete(); - break; - - case preg_match('/^duplicate_(\d+)$/', $action, $m): - HDD::DuplicateToEmptySlots($m[1]); - break; - - case preg_match('/^destroy_(\d+)$/', $action, $m): - HDD::MarkDestroyed($m[1]); - break; - - case preg_match('/^reassign_(\d+)$/', $action, $m): - HDD::ReassignToDevice($m[1], $deviceID); - break; - - case preg_match('/^spare_(\d+)$/', $action, $m): - HDD::MarkAsSpare($m[1]); - break; - - case $action === "add_hdd": - // This action is now handled via JS modal, fallback kept for legacy - HDD::CreateEmpty($deviceID); - break; - - case $action === "create_hdd_form": - $hdd = new HDD(); - $hdd->DeviceID = $deviceID; - $hdd->Label = $_POST['Label_new'] ?? ''; - $hdd->SerialNo = $_POST['SerialNo_new'] ?? ''; - $hdd->Status = 'On'; - $hdd->TypeMedia = $_POST['TypeMedia_new'] ?? 'SATA'; - $hdd->Size = intval($_POST['Size_new'] ?? 0); - $hdd->Note = $_POST['Note_new'] ?? ''; - $hdd->Create(); - break; - - case $action === "bulk_remove": - foreach ($_POST['select_active'] ?? [] as $id) { - HDD::WithdrawByID($id); - } - break; - - case $action === "bulk_delete": - foreach ($_POST['select_active'] ?? [] as $id) { - HDD::DeleteByID($id); - } - break; - - case $action === "bulk_destroy": - foreach ($_POST['select_pending'] ?? [] as $id) { - HDD::MarkDestroyed($id); - } - break; - - case $action === "print_list": - header('Content-Type: application/vnd.ms-excel'); - header('Content-Disposition: attachment; filename="HDD_List_Device_' . $deviceID . '.xls"'); - HDD::ExportPendingDestruction($deviceID); +$action = $_POST['action'] ?? ''; +$deviceID = intval($_POST['DeviceID'] ?? 0); + if (!$deviceID) { + echo "Erreur : DeviceID manquant ou invalide."; exit; - break; + } + +try { + switch (true) + { // Création d’un nouveau HDD depuis le modal + case $action === 'create_hdd_form': + // Récupération et sanitation des champs + $label = $_POST['Label'] ?? ''; + $serialNo = $_POST['SerialNo'] ?? ''; + $typeMedia = $_POST['TypeMedia']?? ''; + $size = intval($_POST['Size'] ?? 0); + $note = $_POST['Note'] ?? ''; + + // Création via instance pour inclure le champ Note + $hdd = new HDD(); + $hdd->DeviceID = $deviceID; + $hdd->Label = $label; + $hdd->SerialNo = $serialNo; + $hdd->Status = 'On'; + $hdd->TypeMedia = $typeMedia; + $hdd->Size = $size; + $hdd->StatusDestruction = 'none'; + $hdd->Note = $note; + $hdd->Create(); + break; + + case preg_match('/^update_(\d+)$/', $action, $m) === 1: + $hdd = new HDD(); + $hdd->HDDID = $m[1]; + $hdd->Label = $_POST['Label'][$hdd->HDDID]; + $hdd->SerialNo = $_POST['SerialNo'][$hdd->HDDID]; + $hdd->Status = $_POST['Status'][$hdd->HDDID]; + $hdd->TypeMedia = $_POST['TypeMedia'][$hdd->HDDID]; + $hdd->Size = $_POST['Size'][$hdd->HDDID]; + $hdd->MakeSafe(); + $hdd->Update(); + echo "Je suis dans le case update"; + break; + + case preg_match('/^remove_(\d+)$/', $action, $m) === 1: + $hdd = HDD::GetHDDByID(intval($m[1])); + if ($hdd) { + $hdd->SendForDestruction(); + } + echo "Je suis dans le case remove"; + break; + + case preg_match('/^delete_(\d+)$/', $action, $m) === 1: + $hdd = new HDD(); + $hdd->HDDID = $m[1]; + $hdd->Delete(); + echo "Je suis dans le case delete"; + break; + + case preg_match('/^duplicate_(\d+)$/', $action, $m) === 1: + HDD::DuplicateToEmptySlots($m[1]); + echo "Je suis dans le case duplicate pour HDDID {$m[1]}"; + exit; + break; + + case preg_match('/^destroy_(\d+)$/', $action, $m) === 1: + HDD::MarkDestroyed($m[1]); + echo "destroy"; + break; + + case preg_match('/^reassign_(\d+)$/', $action, $m) === 1: + HDD::ReassignToDevice($m[1], $deviceID); + break; + + case preg_match('/^spare_(\d+)$/', $action, $m) === 1: + HDD::MarkAsSpare($m[1]); + break; + + case $action === "add_hdd": + // This action is now handled via JS modal, fallback kept for legacy + HDD::CreateEmpty($deviceID); + break; + + case $action === "bulk_remove": + foreach ($_POST['select_active'] ?? [] as $id) { + HDD::WithdrawByID($id); + } + break; + + case $action === "bulk_delete": + foreach ($_POST['select_active'] ?? [] as $id) { + HDD::DeleteByID($id); + } + break; + + case $action === "bulk_destroy": + foreach ($_POST['select_pending'] ?? [] as $id) { + HDD::MarkDestroyed((intval)$id); + } + break; + + case $action === "print_list": + header('Content-Type: application/vnd.ms-excel'); + header('Content-Disposition: attachment; filename="HDD_List_Device_' . $deviceID . '.xls"'); + HDD::ExportPendingDestruction($deviceID); + exit; + break; + // Pour d’autres actions (update_, delete_, etc.), ajoutez vos cases ici + default: + throw new Exception("Action inconnue : “{$action}”."); + } + +} catch (\PDOException $e) { + echo "

Erreur base de données :

" . htmlentities($e->getMessage()) . "
"; + exit; +} catch (\Exception $e) { + echo "

Erreur :

" . htmlentities($e->getMessage()) . "
"; + exit; } - -header("Location: managementhdd.php?DeviceID=$deviceID"); + +// Redirect to the HDD management page +header('Location: managementhdd.php?DeviceID=' . urlencode($deviceID)); exit; From ad70bf44a1044b5000a6b5c92521f1a98924f0ab Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Tue, 13 May 2025 21:16:32 +0200 Subject: [PATCH 08/25] feature_managementhdd --- classes/hdd.class.php | 84 ++++++++++++-- managementhdd.php | 259 +++++++++++++++++++++--------------------- savehdd.php | 74 ++++++------ 3 files changed, 236 insertions(+), 181 deletions(-) diff --git a/classes/hdd.class.php b/classes/hdd.class.php index aaba1b75f..e6281290a 100644 --- a/classes/hdd.class.php +++ b/classes/hdd.class.php @@ -2,6 +2,8 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Writer\Xls; class HDD { // Properties @@ -12,10 +14,10 @@ class HDD { public string $Status; public int $Size; public string $TypeMedia; - public DateTimeImmutable $DateAdd; - public DateTimeImmutable $DateWithdrawn; - public DateTimeImmutable $DateDestruction; - public string $StatusDestruction; + public string $DateAdd; + public ?string $DateWithdrawn; + public ?string $DateDestruction; + public string $StatusDestruction; public ?string $Note; private LoggerInterface $logger; @@ -38,12 +40,11 @@ public function MakeSafe(): void { $this->Note = sanitize($this->Note); } - // Format fields for display if needed - public function MakeDisplay(): void { - // e.g. $this->DateAdd = date('Y-m-d H:i:s', strtotime($this->DateAdd)); - } - - // Convert a PDO row into an HDD object + public function MakeDisplay(): void { + // ex. $this->DateAdd = date('d/m/Y', strtotime($this->DateAdd)); + } + + // Convert a PDO row into an HDD object public static function RowToObject(array $row): self { $hdd = new self(); foreach ($row as $prop => $val) { @@ -328,6 +329,69 @@ public static function SearchBySerial(string $SerialNo): array { return $list; } + //Export all HDDs of a device into a 3-sheet XLS + //- sheet "hdd en prod" for StatusDestruction = 'none' + //- sheet "pending" for StatusDestruction = 'pending' + //- sheet "destroyed" for StatusDestruction = 'destroyed' + + + public static function ExportAllToXls(int $deviceID): void { + global $dbh; + // Crée le classeur + $spreadsheet = new Spreadsheet(); + + $statuses = [ + 'none' => 'hdd en prod', + 'pending' => 'pending', + 'destroyed' => 'destroyed', + ]; + + $first = true; + foreach ($statuses as $status => $title) { + // Feuille active ou création + $sheet = $first + ? $spreadsheet->getActiveSheet() + : $spreadsheet->createSheet(); + $first = false; + $sheet->setTitle($title); + + // En-têtes colonnes + $headers = ['HDDID','Label','SerialNo','Status','TypeMedia','Size','DateAdd','DateWithdrawn','DateDestruction','StatusDestruction','Note']; + $sheet->fromArray($headers, null, 'A1'); + + // Récupère les HDDs pour ce statut + $stmt = $dbh->prepare( + "SELECT HDDID, Label, SerialNo, Status, TypeMedia, Size, + DateAdd, DateWithdrawn, DateDestruction, StatusDestruction, Note + FROM fac_HDD + WHERE DeviceID = :DeviceID + AND StatusDestruction = :StatusDestruction + ORDER BY Label ASC" + ); + $stmt->execute([ + ':DeviceID' => $deviceID, + ':StatusDestruction' => $status, + ]); + + // Remplit les lignes + $row = 2; + while ($h = $stmt->fetch(PDO::FETCH_NUM)) { + // $h est un array indexé de 0 à 10, dans le même ordre que les headers + $sheet->fromArray($h, null, "A{$row}"); + $row++; + } + } + + // En-têtes HTTP & envoi du fichier + header('Content-Type: application/vnd.ms-excel'); + header('Content-Disposition: attachment; filename="HDD_List_Device_' . $deviceID . '.xls"'); + + $writer = new Xls($spreadsheet); + $writer->save('php://output'); + exit; + } + + // Generic logging to fac_GenericLog private static function logAction(string $action, int $HDDID): void { global $person, $dbh; diff --git a/managementhdd.php b/managementhdd.php index 57e6b9c01..ff90b5db8 100644 --- a/managementhdd.php +++ b/managementhdd.php @@ -9,21 +9,21 @@ $subheader = __("HDD Management"); if (!$person->ManageHDD) { - header("Location: index.php"); - exit; + header("Location: index.php"); + exit; } $device = new Device(); if (isset($_GET['DeviceID']) && is_numeric($_GET['DeviceID'])) { - $device->DeviceID = intval($_GET['DeviceID']); - if (!$device->GetDevice()) { - echo __("Invalid DeviceID"); - exit; - } + $device->DeviceID = intval($_GET['DeviceID']); + if (!$device->GetDevice()) { + echo __("Invalid DeviceID"); + exit; + } } else { - echo __("DeviceID is required"); - exit; + echo __("DeviceID is required"); + exit; } $template = new DeviceTemplate(); @@ -32,8 +32,8 @@ $template->LoadHDDConfig(); if (!$template->EnableHDDFeature) { - echo '
'.__("This equipment does not support HDD management.").'
'; - exit; + echo '
'.__("This equipment does not support HDD management.").'
'; + exit; } $hddList = HDD::GetHDDByDevice($device->DeviceID); @@ -51,20 +51,20 @@ - +
-
-

Label); ?>

-
- - -

- - - - - - - - - - - - - - +
+

Label, ENT_QUOTES, 'UTF-8'); ?>

+ + + +

+
#
+ + + + + + + + + + + + + - - - - - - - - - "; - $i++; + $id = (int)$hdd->HDDID; + echo '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + '' . + ''; + $i++; } ?> - -
#
$i - - - - - - -
' . $i . '' . + '' . + '' . + '' . + '' . + '
-

- - - -

- -

- - - - - - - - - - - + +
+

+ + + +

+ +

+ + + + + + + + + + + - - - - - - "; + $id = (int)$hdd->HDDID; + echo '' . + '' . + '' . + '' . + '' . + '' . + ''; } ?> - -
" . htmlentities($hdd->Label) . "" . htmlentities($hdd->SerialNo) . "{$hdd->dateWithdrawn} - - - -
' . htmlspecialchars($hdd->Label, ENT_QUOTES, 'UTF-8') . '' . htmlspecialchars($hdd->SerialNo, ENT_QUOTES, 'UTF-8') . '' . htmlspecialchars($hdd->DateWithdrawn, ENT_QUOTES, 'UTF-8') . '' . + '' . + '' . + '' . + '
-

- - -

-
-
- - - -
-
+ + +

+ + +

+ +
+ + + +
+
+ @@ -241,4 +242,4 @@ function closeModal() { - + \ No newline at end of file diff --git a/savehdd.php b/savehdd.php index 831986d94..a4cb26f18 100644 --- a/savehdd.php +++ b/savehdd.php @@ -2,8 +2,6 @@ ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); -// test de validation du parse -echo "Si vous voyez ce message, PHP parse bien le fichier.
"; require_once("db.inc.php"); require_once("facilities.inc.php"); @@ -22,11 +20,6 @@ } $action = $_POST['action'] ?? ''; -$deviceID = intval($_POST['DeviceID'] ?? 0); - if (!$deviceID) { - echo "Erreur : DeviceID manquant ou invalide."; - exit; - } try { switch (true) @@ -53,82 +46,79 @@ break; case preg_match('/^update_(\d+)$/', $action, $m) === 1: - $hdd = new HDD(); - $hdd->HDDID = $m[1]; - $hdd->Label = $_POST['Label'][$hdd->HDDID]; - $hdd->SerialNo = $_POST['SerialNo'][$hdd->HDDID]; - $hdd->Status = $_POST['Status'][$hdd->HDDID]; - $hdd->TypeMedia = $_POST['TypeMedia'][$hdd->HDDID]; - $hdd->Size = $_POST['Size'][$hdd->HDDID]; + $id = intval((int)$m[1]); + // Récupère l’objet complet (avec tous les champs) + $hdd = HDD::GetHDDByID($id); + if (!$hdd) { + throw new Exception("HDDID {$id} introuvable."); + } + // Ne mettez à jour QUE ce qui vient du formulaire + $hdd->Label = $_POST['Label'][$id] ?? $hdd->Label; + $hdd->SerialNo = $_POST['SerialNo'][$id] ?? $hdd->SerialNo; + $hdd->Status = $_POST['Status'][$id] ?? $hdd->Status; + $hdd->TypeMedia = $_POST['TypeMedia'][$id]?? $hdd->TypeMedia; + $hdd->Size = intval($_POST['Size'][$id] ?? $hdd->Size); + // Maintenant vous avez déjà StatusDestruction, Note, DateAdd, etc. $hdd->MakeSafe(); $hdd->Update(); - echo "Je suis dans le case update"; break; case preg_match('/^remove_(\d+)$/', $action, $m) === 1: - $hdd = HDD::GetHDDByID(intval($m[1])); + $hdd = HDD::GetHDDByID(intval((int)$m[1])); if ($hdd) { $hdd->SendForDestruction(); } - echo "Je suis dans le case remove"; break; case preg_match('/^delete_(\d+)$/', $action, $m) === 1: - $hdd = new HDD(); - $hdd->HDDID = $m[1]; - $hdd->Delete(); - echo "Je suis dans le case delete"; + HDD::DeleteByID((int)$m[1]); break; case preg_match('/^duplicate_(\d+)$/', $action, $m) === 1: - HDD::DuplicateToEmptySlots($m[1]); - echo "Je suis dans le case duplicate pour HDDID {$m[1]}"; - exit; + HDD::DuplicateToEmptySlots((int)$m[1]); break; case preg_match('/^destroy_(\d+)$/', $action, $m) === 1: - HDD::MarkDestroyed($m[1]); - echo "destroy"; + HDD::MarkDestroyed((int)$m[1]); break; case preg_match('/^reassign_(\d+)$/', $action, $m) === 1: - HDD::ReassignToDevice($m[1], $deviceID); + HDD::ReassignToDevice((int)$m[1], $deviceID); break; case preg_match('/^spare_(\d+)$/', $action, $m) === 1: - HDD::MarkAsSpare($m[1]); - break; - - case $action === "add_hdd": - // This action is now handled via JS modal, fallback kept for legacy - HDD::CreateEmpty($deviceID); + HDD::MarkAsSpare((int)$m[1]); break; case $action === "bulk_remove": - foreach ($_POST['select_active'] ?? [] as $id) { - HDD::WithdrawByID($id); + // $_POST['select_active'] contient un tableau d’IDs cochés + foreach ($_POST['select_active'] ?? [] as $id) { + $id = intval($id); + // Récupère l’objet complet pour préserver ses autres propriétés + if ($hdd = HDD::GetHDDByID($id)) { + $hdd->SendForDestruction(); + } } break; case $action === "bulk_delete": foreach ($_POST['select_active'] ?? [] as $id) { - HDD::DeleteByID($id); + HDD::DeleteByID(intval($id)); } break; case $action === "bulk_destroy": foreach ($_POST['select_pending'] ?? [] as $id) { - HDD::MarkDestroyed((intval)$id); + HDD::MarkDestroyed(intval($id)); } break; case $action === "print_list": - header('Content-Type: application/vnd.ms-excel'); - header('Content-Disposition: attachment; filename="HDD_List_Device_' . $deviceID . '.xls"'); - HDD::ExportPendingDestruction($deviceID); - exit; + // Export XLS complet en 3 feuilles + HDD::ExportAllToXls($deviceID); + // (la méthode se termine par exit()) break; - // Pour d’autres actions (update_, delete_, etc.), ajoutez vos cases ici + default: throw new Exception("Action inconnue : “{$action}”."); } From f486f232bc3cdce7fd195810612e93556e7da06a Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Fri, 16 May 2025 18:08:06 +0200 Subject: [PATCH 09/25] feature_managementhdd --- configuration.php | 13 +- create.sql | 4 +- db-23.04-to-24.01.sql | 2 + managementhdd.php | 278 +++++++++++++++++++++++++++++------------- savehdd.php | 2 +- 5 files changed, 208 insertions(+), 91 deletions(-) diff --git a/configuration.php b/configuration.php index e96fb2d80..26dea9cae 100644 --- a/configuration.php +++ b/configuration.php @@ -1928,6 +1928,7 @@ function uploadifive() {
+

',__("Features Options"),'

@@ -1938,7 +1939,17 @@ function uploadifive() {
-
+
+
+
+
+
+
+
+
-

- + +
- + @@ -110,37 +119,38 @@ function closeAddHDDForm() { $i = 1; foreach ($hddList as $hdd) { $id = (int)$hdd->HDDID; - echo '' . - '' . - '' . - '' . - '' . - '' . - '' . - '' . - '' . + echo ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. ''; $i++; } ?>
#
' . $i . '' . - '' . - '' . - '' . - '' . - '
'.$i.''. + ''. + ''. + ''. + ''. + '
+

@@ -148,46 +158,117 @@ function closeAddHDDForm() {

- + +
- + + + HDDID; - echo '' . - '' . - '' . - '' . - '' . - '' . - ''; + echo ' + + + + + + + + '; + $i++; } ?>
#
' . htmlspecialchars($hdd->Label, ENT_QUOTES, 'UTF-8') . '' . htmlspecialchars($hdd->SerialNo, ENT_QUOTES, 'UTF-8') . '' . htmlspecialchars($hdd->DateWithdrawn, ENT_QUOTES, 'UTF-8') . '' . - '' . - '' . - '' . - '
'.$i.'
'.htmlspecialchars($hdd->Label, ENT_QUOTES, "UTF-8").'
'.htmlspecialchars($hdd->SerialNo, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->StatusDestruction, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->DateWithdrawn, ENT_QUOTES, "UTF-8").' + + + + +
+

- +

+
 
+

+ + + + + + + + + + + + + + + + HDDID; + echo ' + + + + + + + + + '; + $i++; + } + ?> + +
#
'.$i.'
'.htmlspecialchars($hdd->Label, ENT_QUOTES, "UTF-8").'
'.htmlspecialchars($hdd->SerialNo, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->StatusDestruction, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->DateWithdrawn, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->DateDestroyed, ENT_QUOTES, "UTF-8").' + + + +
+
+
+
+ 0){ + echo ''; + } + ?> + +
+
+ 0){ + print " [ ".__("Return to Parent Device")." ]
\n"; + print " GetDeviceCabinetID()."\">[ ".__("Return to Navigator")." ]"; + }else{ + print "
[ ".__("Return index")." ]
"; + } + + ?> +
+
@@ -222,24 +303,45 @@ function closeAddHDDForm() {
-
- + + + } - + // Message confirm delete + function confirmDelete() { + return confirm(""); + } + + // Select all toggles + function toggleAddHDDForm() { + document.getElementById("addHDDModal").style.display = "block"; + } + function closeAddHDDForm() { + document.getElementById("addHDDModal").style.display = "none"; + } + // Quand on change la case "select all" des actifs + $('#select_all_active').on('change', function() { + // coche ou décoche toutes les cases de classe .select_active + $('.select_active').prop('checked', this.checked); + }); + // Pareil pour les pending + $('#select_all_pending').on('change', function() { + $('.select_pending').prop('checked', this.checked); + }); + \ No newline at end of file diff --git a/savehdd.php b/savehdd.php index a4cb26f18..2da6ad4a0 100644 --- a/savehdd.php +++ b/savehdd.php @@ -113,7 +113,7 @@ } break; - case $action === "print_list": + case $action === "export_list": // Export XLS complet en 3 feuilles HDD::ExportAllToXls($deviceID); // (la méthode se termine par exit()) From e8a341f28c6a433315ea5fa4c022b4cfa41c04d7 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Fri, 16 May 2025 23:48:34 +0200 Subject: [PATCH 10/25] feature_managementhdd --- configuration.php | 58 ++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/configuration.php b/configuration.php index 26dea9cae..b5e1749be 100644 --- a/configuration.php +++ b/configuration.php @@ -1928,37 +1928,33 @@ function uploadifive() {
-

',__("Features Options"),'

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+

',__("Features Options"),'

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

',__("Site Level Security Options"),'

From 17502d4d5f6e06a4dd1e453a8e05e8744c3a59ef Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Tue, 20 May 2025 01:06:36 +0200 Subject: [PATCH 11/25] feature_managementhdd --- assign_hdd.php | 24 +++ classes/hdd.class.php | 60 ++++---- create.sql | 31 ++-- db-23.04-to-24.01.sql | 35 +++-- managementhdd.php | 134 +++++++++++++---- report_hdd.php | 343 ++++++++++++++++++++++++++++++++++++++++++ savehdd.php | 2 +- search_devices.php | 30 ++++ 8 files changed, 569 insertions(+), 90 deletions(-) create mode 100644 assign_hdd.php create mode 100644 report_hdd.php create mode 100644 search_devices.php diff --git a/assign_hdd.php b/assign_hdd.php new file mode 100644 index 000000000..9b09e7a0f --- /dev/null +++ b/assign_hdd.php @@ -0,0 +1,24 @@ +false,'error'=>'']); + exit; +} + +$h = new hdd(); +$h->HDDID = $hddid; +if(!$h->GetHDD()){ + echo json_encode(['success'=>false,'error'=>'']); + exit; +} + +$h->DeviceID = $deviceid; +if($h->Save()){ + echo json_encode(['success'=>true]); +} else { + echo json_encode(['success'=>false,'error'=>'']); +} diff --git a/classes/hdd.class.php b/classes/hdd.class.php index e6281290a..0fc17f9ee 100644 --- a/classes/hdd.class.php +++ b/classes/hdd.class.php @@ -16,8 +16,7 @@ class HDD { public string $TypeMedia; public string $DateAdd; public ?string $DateWithdrawn; - public ?string $DateDestruction; - public string $StatusDestruction; + public ?string $DateDestroyed; public ?string $Note; private LoggerInterface $logger; @@ -36,7 +35,6 @@ public function MakeSafe(): void { $this->Status = sanitize($this->Status); $this->Size = intval($this->Size); $this->TypeMedia = sanitize($this->TypeMedia); - $this->StatusDestruction = sanitize($this->StatusDestruction); $this->Note = sanitize($this->Note); } @@ -72,9 +70,9 @@ public function Create(): void { global $dbh; $this->MakeSafe(); $sql = "INSERT INTO fac_HDD - (DeviceID, Label, SerialNo, Status, Size, TypeMedia, DateAdd, StatusDestruction, Note) + (DeviceID, Label, SerialNo, Status, Size, TypeMedia, DateAdd, Note) VALUES - (:DeviceID, :Label, :SerialNo, :Status, :Size, :TypeMedia, NOW(), :StatusDestruction, :Note)"; + (:DeviceID, :Label, :SerialNo, :Status, :Size, :TypeMedia, NOW(), :Note)"; $stmt = $dbh->prepare($sql); $stmt->execute([ ":DeviceID" => $this->DeviceID, @@ -83,7 +81,6 @@ public function Create(): void { ":Status" => $this->Status, ":Size" => $this->Size, ":TypeMedia" => $this->TypeMedia, - ":StatusDestruction" => $this->StatusDestruction, ":Note" => $this->Note ]); $this->HDDID = intval($dbh->lastInsertId()); @@ -95,9 +92,9 @@ public static function CreateFromForm(int $deviceID, string $label, string $seri global $dbh; $stmt = $dbh->prepare( "INSERT INTO fac_HDD - (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd, StatusDestruction) + (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd) VALUES - (:DeviceID, :Label, :SerialNo, 'On', :TypeMedia, :Size, NOW(), 'none')" + (:DeviceID, :Label, :SerialNo, 'On', :TypeMedia, :Size, NOW())" ); $stmt->execute([ ':DeviceID' => $deviceID, @@ -174,7 +171,7 @@ public static function DuplicateToEmptySlots(int $sourceHDDID): void { if ($max <= 0) { return; }// Combien sont déjà présents ? - $stmt = $dbh->prepare("SELECT COUNT(*) FROM fac_HDD WHERE DeviceID = ?"); + $stmt = $dbh->prepare("SELECT COUNT(*) FROM fac_HDD WHERE DeviceID = ? AND (Status = 'On' OR Status = 'Off')"); $stmt->execute([$deviceID]); $current = intval($stmt->fetchColumn()); @@ -209,7 +206,6 @@ public function SendForDestruction(string $note = ''): bool { $stmt = $dbh->prepare( "UPDATE fac_HDD SET Status = 'Pending_destruction', - StatusDestruction = 'pending', DateWithdrawn = NOW(), Note = CONCAT(Note, ' ', :Note) WHERE HDDID = :HDDID" @@ -225,8 +221,8 @@ public static function MarkDestroyed(int $id): bool { global $dbh; $stmt = $dbh->prepare( "UPDATE fac_HDD SET - StatusDestruction = 'destroyed', - DateDestruction = NOW() + Status = 'Destroyed', + DateDestroyed = NOW() WHERE HDDID = ?" ); $res = $stmt->execute([$id]); @@ -244,8 +240,7 @@ public static function ReassignToDevice(int $id, int $deviceID): bool { DeviceID = ?, Status = 'On', DateWithdrawn = NULL, - DateDestruction = NULL, - StatusDestruction = 'none' + DateDestroyed = NULL WHERE HDDID = ?" ); $res = $stmt->execute([$deviceID, $id]); @@ -273,7 +268,7 @@ public static function MarkAsSpare(int $id): bool { // List active HDDs for a device public static function GetHDDByDevice(int $DeviceID): array { global $dbh; - $stmt = $dbh->prepare("SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID AND StatusDestruction = 'none' ORDER BY Label ASC"); + $stmt = $dbh->prepare("SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID AND (Status = 'On' OR Status = 'Off') ORDER BY Label ASC"); $stmt->execute([":DeviceID" => $DeviceID]); $list = []; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { @@ -288,7 +283,7 @@ public static function GetPendingByDevice(int $DeviceID): array { $stmt = $dbh->prepare( "SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID - AND StatusDestruction = 'pending' + AND Status = 'Pending_destruction' ORDER BY DateWithdrawn DESC" ); $stmt->execute([':DeviceID' => $DeviceID]); @@ -300,14 +295,14 @@ public static function GetPendingByDevice(int $DeviceID): array { return $list; } - // List retired HDDs (destruction completed) - public static function GetRetiredHDDByDevice(int $DeviceID): array { + // List destroyed HDDs (destruction completed) + public static function GetDestroyedHDDByDevice(int $DeviceID): array { global $dbh; $stmt = $dbh->prepare( "SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID - AND StatusDestruction = 'destroyed' - ORDER BY DateDestruction DESC" + AND Status = 'Destroyed' + ORDER BY DateDestroyed DESC" ); $stmt->execute([":DeviceID" => $DeviceID]); $list = []; @@ -330,10 +325,11 @@ public static function SearchBySerial(string $SerialNo): array { } //Export all HDDs of a device into a 3-sheet XLS - //- sheet "hdd en prod" for StatusDestruction = 'none' - //- sheet "pending" for StatusDestruction = 'pending' - //- sheet "destroyed" for StatusDestruction = 'destroyed' - + //- sheet "hdd in prod" for Status = 'On' + //- sheet "hdd out prod" for Status = 'Off' + //- sheet "Pending_destruction" for Status = 'Pending_destruction' + //- sheet "Destroyed" for Status = 'Destroyed' + //- sheet "Spare" for Status = 'Spare' public static function ExportAllToXls(int $deviceID): void { global $dbh; @@ -341,9 +337,11 @@ public static function ExportAllToXls(int $deviceID): void { $spreadsheet = new Spreadsheet(); $statuses = [ - 'none' => 'hdd en prod', - 'pending' => 'pending', - 'destroyed' => 'destroyed', + 'On' => 'hdd in prod', + 'Off' => 'hdd out prod', + 'Pending_destruction' => 'Pending_destruction', + 'Destroyed' => 'Destroyed', + 'Spare' => 'Spare' ]; $first = true; @@ -356,21 +354,21 @@ public static function ExportAllToXls(int $deviceID): void { $sheet->setTitle($title); // En-têtes colonnes - $headers = ['HDDID','Label','SerialNo','Status','TypeMedia','Size','DateAdd','DateWithdrawn','DateDestruction','StatusDestruction','Note']; + $headers = ['HDDID','Label','SerialNo','Status','TypeMedia','Size','DateAdd','DateWithdrawn','DateDestroyed','Note']; $sheet->fromArray($headers, null, 'A1'); // Récupère les HDDs pour ce statut $stmt = $dbh->prepare( "SELECT HDDID, Label, SerialNo, Status, TypeMedia, Size, - DateAdd, DateWithdrawn, DateDestruction, StatusDestruction, Note + DateAdd, DateWithdrawn, DateDestroyed, Note FROM fac_HDD WHERE DeviceID = :DeviceID - AND StatusDestruction = :StatusDestruction + AND Status = :Status ORDER BY Label ASC" ); $stmt->execute([ ':DeviceID' => $deviceID, - ':StatusDestruction' => $status, + ':Status' => $status, ]); // Remplit les lignes diff --git a/create.sql b/create.sql index 9cff0a777..82f9e1bea 100644 --- a/create.sql +++ b/create.sql @@ -1084,28 +1084,27 @@ INSERT INTO `fac_Country` VALUES ('AD','Andorra'),('AE','United Arab Emirates'), -- -- Table for fac_HDD feature managementHDD -- - -CREATE TABLE `fac_HDD` ( - HDDID int(11) NOT NULL AUTO_INCREMENT, - DeviceID int(11) NOT NULL, - Label varchar(100) DEFAULT NULL, - SerialNo varchar(100) DEFAULT NULL, - Status enum('On','Off','Retired','Pending_destruction','Destroyed_h2','Spare') DEFAULT 'On', - Size int(11) DEFAULT NULL, - TypeMedia enum('HDD','SSD','MVME') DEFAULT NULL, - DateAdd datetime DEFAULT current_timestamp(), - DateWithdrawn datetime DEFAULT NULL, - DateDestruction datetime DEFAULT NULL, - StatusDestruction enum('none','pending','destroyed') DEFAULT NULL, - Note text DEFAULT NULL, +CREATE TABLE fac_HDD ( + HDDID INT(11) NOT NULL AUTO_INCREMENT, + DeviceID INT(11) NOT NULL, + Label VARCHAR(100), + SerialNo VARCHAR(100), + Status ENUM('On','Off','Pending_destruction','Destroyed','Spare') + DEFAULT 'On', + Size INT(11) DEFAULT NULL, -- en Go + TypeMedia ENUM('HDD','SSD','MVME'), + DateAdd DATETIME DEFAULT CURRENT_TIMESTAMP, + DateWithdrawn DATETIME DEFAULT NULL, + DateDestroyed DATETIME DEFAULT NULL, + Note TEXT DEFAULT NULL, PRIMARY KEY (HDDID) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table for fac_DeviceTemplateHdd feature managementHDD -- - CREATE TABLE `fac_DeviceTemplateHdd` ( + CREATE TABLE fac_DeviceTemplateHdd ( TemplateID int(11) NOT NULL, EnableHDDFeature tinyint(1) DEFAULT 0, HDDCount int(11) DEFAULT 0, diff --git a/db-23.04-to-24.01.sql b/db-23.04-to-24.01.sql index e7fe92544..8ea308670 100755 --- a/db-23.04-to-24.01.sql +++ b/db-23.04-to-24.01.sql @@ -3,29 +3,34 @@ --- UPDATE fac_Config set Value="24.01" WHERE Parameter="Version"; +--- feature hdd +--- -fac_config INSERT INTO fac_Config (Parameter, Value) VALUES ('feature_hdd', 'disabled') ON DUPLICATE KEY UPDATE Value = Value; INSERT INTO fac_Config (Parameter, Value) VALUES ('Log_for_user_hdd', 'disabled') ON DUPLICATE KEY UPDATE Value = Value; +--- -fac_people ALTER TABLE fac_People ADD COLUMN ManageHDD TINYINT(1) DEFAULT 0; +--- -fac_devicetemplatehdd CREATE TABLE fac_DeviceTemplateHdd ( TemplateID INT NOT NULL, EnableHDDFeature TINYINT(1) DEFAULT 0, HDDCount INT DEFAULT 0, PRIMARY KEY (TemplateID) -); +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +--- -fac_hdd CREATE TABLE fac_HDD ( - HDDID INT NOT NULL AUTO_INCREMENT, - DeviceID INT NOT NULL, - Label VARCHAR(100), - SerialNo VARCHAR(100) UNIQUE, - Status ENUM('On','Off','Replace','Pending_destruction','Destroyed_h2','Spare') DEFAULT 'On', - Size INT, -- en Go - TypeMedia ENUM('HDD', 'SSD', 'MVME'), - DateAdd DATETIME DEFAULT CURRENT_TIMESTAMP, - DateWithdrawn DATETIME, - DateDestruction DATETIME, - StatusDestruction ENUM('none', 'pending', 'destroyed'), - Note TEXT, - PRIMARY KEY (HDDID) -); \ No newline at end of file + HDDID INT NOT NULL AUTO_INCREMENT, + DeviceID INT NOT NULL, + Label VARCHAR(100), + SerialNo VARCHAR(100), + Status ENUM('On','Off','Pending_destruction','Destroyed','Spare') + DEFAULT 'On', + Size INT(11) DEFAULT NULL, -- en Go + TypeMedia ENUM('HDD','SSD','MVME') DEFAULT NULL, + DateAdd DATETIME DEFAULT CURRENT_TIMESTAMP, + DateWithdrawn DATETIME DEFAULT NULL, + DateDestroyed DATETIME DEFAULT NULL, + Note TEXT DEFAULT NULL, + PRIMARY KEY (HDDID) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/managementhdd.php b/managementhdd.php index 55b205a9d..d1199f308 100644 --- a/managementhdd.php +++ b/managementhdd.php @@ -1,7 +1,7 @@ DeviceID); $hddWaitList = HDD::GetPendingByDevice($device->DeviceID); +$hdddestroyedList = HDD::GetDestroyedHDDByDevice($device->DeviceID); ?> @@ -111,6 +112,7 @@ + @@ -127,9 +129,8 @@ ''. ''. ''. + ''. ''. ''. ''. @@ -155,6 +157,7 @@ +

@@ -162,7 +165,6 @@ - @@ -177,17 +179,16 @@ foreach ($hddWaitList as $hdd) { $id = (int)$hdd->HDDID; echo ' - - + - - + + '; $i++; @@ -211,9 +212,7 @@ - - @@ -224,16 +223,10 @@ echo ' - + - - - - + + '; $i++; } @@ -251,12 +244,13 @@ if($deviceID >0){ echo ''; } - ?> - + ?> +
+ + +
- - - +
0){ @@ -304,6 +298,33 @@
+ + + + + \ No newline at end of file diff --git a/report_hdd.php b/report_hdd.php new file mode 100644 index 000000000..48bfe338e --- /dev/null +++ b/report_hdd.php @@ -0,0 +1,343 @@ +prepare(" + UPDATE fac_HDD + SET Status = 'Destroyed', + DateDestroyed = NOW(), + ProofDocument = ? + WHERE HDDID = ? + "); + foreach ($selected as $hid) { + $hddid = intval($hid); + $stmt->bind_param('si', $filename, $hddid); + $stmt->execute(); + } + $success = 'Disques marqués « Destroyed » avec preuve enregistrée.'; + } + } +} + +// 2) Traitement de l’export XLS +if (isset($_GET['export']) && $_GET['export'] === 'xls') { + // Récupération des filtres GET + $statusFilter = $_GET['status'] ?? ''; + $DateAddFrom = $_GET['DateAddFrom'] ?? ''; + $DateAddTo = $_GET['DateAddTo'] ?? ''; + $DateWithdrawnFrom = $_GET['DateWithdrawnFrom'] ?? ''; + $DateWithdrawnTo = $_GET['DateWithdrawnTo'] ?? ''; + $DateDestroyedFrom = $_GET['DateDestroyedFrom'] ?? ''; + $DateDestroyedTo = $_GET['DateDestroyedTo'] ?? ''; + + // Construction dynamique du WHERE + $where = []; $types = ''; $params = []; + if ($statusFilter && $statusFilter !== 'All') { + $where[] = 'h.Status = ?'; $types .= 's'; $params[] = $statusFilter; + } + if ($DateAddFrom) { + $where[] = 'h.DateAdd >= ?'; $types .= 's'; $params[] = $DateAddFrom; + } + if ($DateAddTo) { + $where[] = 'h.DateAdd <= ?'; $types .= 's'; $params[] = $DateAddTo; + } + if ($DateWithdrawnFrom) { + $where[] = 'h.DateWithdrawn >= ?'; $types .= 's'; $params[] = $DateWithdrawnFrom; + } + if ($DateWithdrawnTo) { + $where[] = 'h.DateWithdrawn <= ?'; $types .= 's'; $params[] = $DateWithdrawnTo; + } + if ($DateDestroyedFrom) { + $where[] = 'h.DateDestroyed >= ?'; $types .= 's'; $params[] = $DateDestroyedFrom; + } + if ($DateDestroyedTo) { + $where[] = 'h.DateDestroyed <= ?'; $types .= 's'; $params[] = $DateDestroyedTo; + } + + // Requête d’export + $sql = " + SELECT + h.HDDID, + h.DeviceID, + d.Label AS DeviceLabel, + h.Label, + h.SerialNo, + h.Status, + h.Size, + h.TypeMedia, + h.DateAdd, + h.DateWithdrawn, + h.DateDestroyed, + h.Note + FROM fac_HDD h + LEFT JOIN fac_Device d ON h.DeviceID = d.DeviceID + " . ($where ? "WHERE " . implode(' AND ', $where) : "") . " + ORDER BY h.HDDID + "; + $stmt = $db->prepare($sql); + if ($where) { + $stmt->bind_param($types, ...$params); + } + $stmt->execute(); + $res = $stmt->get_result(); + + // Envoi du XLS + header('Content-Type: application/vnd.ms-excel; charset=UTF-8'); + header('Content-Disposition: attachment; filename="report_hdd.xls"'); + echo "HDDID\tDeviceID\tDeviceLabel\tLabel\tSerialNo\tStatus\tSize\tTypeMedia\tDateAdd\tDateWithdrawn\tDateDestroyed\tNote\n"; + while ($r = $res->fetch_assoc()) { + $cols = [ + $r['HDDID'], + $r['DeviceID'], + $r['DeviceLabel'], + $r['Label'], + $r['SerialNo'], + $r['Status'], + $r['Size'], + $r['TypeMedia'], + $r['DateAdd'], + $r['DateWithdrawn'], + $r['DateDestroyed'], + $r['Note'] + ]; + $escaped = array_map(function($v){ + return str_replace(["\t","\r\n","\n"], [' ', ' ', ' '], $v); + }, $cols); + echo implode("\t", $escaped) . "\n"; + } + exit; +} + +// 3) Lecture des filtres GET pour l’affichage +$statusFilter = $_GET['status'] ?? ''; +$DateAddFrom = $_GET['DateAddFrom'] ?? ''; +$DateAddTo = $_GET['DateAddTo'] ?? ''; +$DateWithdrawnFrom = $_GET['DateWithdrawnFrom'] ?? ''; +$DateWithdrawnTo = $_GET['DateWithdrawnTo'] ?? ''; +$DateDestroyedFrom = $_GET['DateDestroyedFrom'] ?? ''; +$DateDestroyedTo = $_GET['DateDestroyedTo'] ?? ''; + +// 4) Construction dynamique du WHERE pour l’affichage +$where = []; $types = ''; $params = []; +if ($statusFilter && $statusFilter !== 'All') { + $where[] = 'h.Status = ?'; $types .= 's'; $params[] = $statusFilter; +} +if ($DateAddFrom) { + $where[] = 'h.DateAdd >= ?'; $types .= 's'; $params[] = $DateAddFrom; +} +if ($DateAddTo) { + $where[] = 'h.DateAdd <= ?'; $types .= 's'; $params[] = $DateAddTo; +} +if ($DateWithdrawnFrom) { + $where[] = 'h.DateWithdrawn >= ?'; $types .= 's'; $params[] = $DateWithdrawnFrom; +} +if ($DateWithdrawnTo) { + $where[] = 'h.DateWithdrawn <= ?'; $types .= 's'; $params[] = $DateWithdrawnTo; +} +if ($DateDestroyedFrom) { + $where[] = 'h.DateDestroyed >= ?'; $types .= 's'; $params[] = $DateDestroyedFrom; +} +if ($DateDestroyedTo) { + $where[] = 'h.DateDestroyed <= ?'; $types .= 's'; $params[] = $DateDestroyedTo; +} + +// 5) Requête principale +$sql = " + SELECT + h.*, + d.Label AS DeviceLabel + FROM fac_HDD h + LEFT JOIN fac_Device d ON h.DeviceID = d.DeviceID + " . ($where ? "WHERE " . implode(' AND ', $where) : "") . " + ORDER BY h.HDDID +"; +$stmt = $db->prepare($sql); +if ($where) { + $stmt->bind_param($types, ...$params); +} +$stmt->execute(); +$res = $stmt->get_result(); +$hdds = $res->fetch_all(MYSQLI_ASSOC); +?> + + + + + Report HDD + + + + +
+

Report des HDD

+ + +
+ +
+ + + +
+
+
+ + +
+
+ + Export XLS +
+
+ +
#
'.$i.'
'.htmlspecialchars($hdd->Label, ENT_QUOTES, "UTF-8").'
'.htmlspecialchars($hdd->Label, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->SerialNo, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->StatusDestruction, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->DateWithdrawn, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->Status, ENT_QUOTES, "UTF-8").' '.$hdd->DateWithdrawn.' - +
'.$i.'
'.htmlspecialchars($hdd->Label, ENT_QUOTES, "UTF-8").'
'.htmlspecialchars($hdd->Label, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->SerialNo, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->StatusDestruction, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->DateWithdrawn, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->DateDestroyed, ENT_QUOTES, "UTF-8").' - - - - '.htmlspecialchars($hdd->Status, ENT_QUOTES, "UTF-8").' '.$hdd->DateDestroyed.'
+ + + + + + + + + + + + + + + + + + + + + + '; ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#HDDIDDeviceIDDevice LabelLabelSerialNoStatusSizeTypeDateAddDateWithdrawnDateDestroyedNotePreuveSélection
+ + + + + + + + +
+ + Voir + + + + + +
+ + + +
+
+ + +
+ +
+
+ + + + + + diff --git a/savehdd.php b/savehdd.php index 2da6ad4a0..561ef5f85 100644 --- a/savehdd.php +++ b/savehdd.php @@ -40,7 +40,6 @@ $hdd->Status = 'On'; $hdd->TypeMedia = $typeMedia; $hdd->Size = $size; - $hdd->StatusDestruction = 'none'; $hdd->Note = $note; $hdd->Create(); break; @@ -58,6 +57,7 @@ $hdd->Status = $_POST['Status'][$id] ?? $hdd->Status; $hdd->TypeMedia = $_POST['TypeMedia'][$id]?? $hdd->TypeMedia; $hdd->Size = intval($_POST['Size'][$id] ?? $hdd->Size); + $hdd->Note = $_POST['Note'][$id] ?? $hdd->Note; // Maintenant vous avez déjà StatusDestruction, Note, DateAdd, etc. $hdd->MakeSafe(); $hdd->Update(); diff --git a/search_devices.php b/search_devices.php new file mode 100644 index 000000000..a0aef75a7 --- /dev/null +++ b/search_devices.php @@ -0,0 +1,30 @@ += 2) { + $sql = " + SELECT + d.DeviceID, + d.Label AS Name + FROM fac_Device d + JOIN fac_DeviceTemplate t ON d.TemplateID = t.TemplateID + JOIN fac_DeviceTemplateHdd dth ON t.TemplateID = dth.TemplateID + WHERE dth.EnableHDDFeature = 1 -- flag HDD activé + AND d.Label LIKE ? -- on cherche sur d.Label, pas e.Label + ORDER BY d.Label -- idem pour ORDER BY + LIMIT 20 + "; + $stmt = $db->prepare($sql); + $like = "%{$q}%"; + $stmt->bind_param('s', $like); + $stmt->execute(); + $res = $stmt->get_result(); + while ($r = $res->fetch_assoc()) { + $out[] = $r; + } +} + +header('Content-Type: application/json'); +echo json_encode($out); From 8b61e32ec00aad6b489918a98e65c21486546029 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Tue, 20 May 2025 20:43:03 +0200 Subject: [PATCH 12/25] feature_managementhdd --- classes/hdd.class.php | 16 +++++++ managementhdd.php | 98 +++++++++++++++++++++++++++++++++---------- reports.php | 1 + 3 files changed, 94 insertions(+), 21 deletions(-) diff --git a/classes/hdd.class.php b/classes/hdd.class.php index 0fc17f9ee..ede26a4cb 100644 --- a/classes/hdd.class.php +++ b/classes/hdd.class.php @@ -311,7 +311,23 @@ public static function GetDestroyedHDDByDevice(int $DeviceID): array { } return $list; } +// List HDDs Spare destruction + public static function GetSpareHDDByDevice(int $DeviceID): array { + global $dbh; + $stmt = $dbh->prepare( + "SELECT * FROM fac_HDD + WHERE DeviceID = :DeviceID + AND Status = 'Spare' + ORDER BY DateWithdrawn DESC" + ); + $stmt->execute([':DeviceID' => $DeviceID]); + $list = []; + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $list[] = self::RowToObject($row); + } + return $list; + } // Search HDDs by serial number public static function SearchBySerial(string $SerialNo): array { global $dbh; diff --git a/managementhdd.php b/managementhdd.php index d1199f308..f1cd6e9fe 100644 --- a/managementhdd.php +++ b/managementhdd.php @@ -35,6 +35,7 @@ $hddList = HDD::GetHDDByDevice($device->DeviceID); $hddWaitList = HDD::GetPendingByDevice($device->DeviceID); $hdddestroyedList = HDD::GetDestroyedHDDByDevice($device->DeviceID); +$hddSpareList = HDD::GetSpareHDDByDevice($device->DeviceID); ?> @@ -85,9 +86,29 @@ text-align: center; } } -input[type="text"]{ +#assignModal { + display: none; + position: fixed; + z-index: 1000; + left: 0; top: 0; + width: 100%; height: 100%; + background-color: rgba(0,0,0,0.5); +} +#assignModal .modal-dialog { + background-color: #fff; + margin: 10% auto; + padding: 20px; + width: 400px; + border-radius: 8px; + position: relative; +} +#assignModal .btn-close { + position: absolute; + top: 10px; right: 10px; + cursor: pointer; + font-size: 1.2em; +} - } @@ -157,14 +178,14 @@ -

-

+

+ @@ -179,6 +200,7 @@ foreach ($hddWaitList as $hdd) { $id = (int)$hdd->HDDID; echo ' + @@ -188,7 +210,7 @@ - + '; $i++; @@ -207,12 +229,12 @@
#
'.$i.' '.htmlspecialchars($hdd->Label, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->SerialNo, ENT_QUOTES, "UTF-8").'
- + @@ -221,7 +243,6 @@ foreach ($hdddestroyedList as $hdd) { $id = (int)$hdd->HDDID; echo ' - @@ -243,30 +264,27 @@ 0){ echo ''; + echo ''; } ?> -
- - -
0){ print " [ ".__("Return to Parent Device")." ]
\n"; - print " GetDeviceCabinetID()."\">[ ".__("Return to Navigator")." ]"; + print " GetDeviceCabinetID()."\">[ ".__("Return to Navigator")." ]"; + print " GetDeviceDCID()."\">[ ".__("Return to Navigator")." ]"; }else{ print "
[ ".__("Return index")." ]
"; } - - ?> + ?>
-
#
'.$i.' '.htmlspecialchars($hdd->Label, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->SerialNo, ENT_QUOTES, "UTF-8").'
-
- - -
-
+ 0){ - echo ''; - echo ''; + echo '
'; + echo '
'; + echo '
'; } - ?>
+ ?>
From 560eb05fb6a0c37e34114c54aea14754211bd598 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Thu, 22 May 2025 11:34:24 +0200 Subject: [PATCH 14/25] feature_managementhdd --- classes/hdd.class.php | 42 +- create.sql | 2 - db-23.04-to-24.01.sql | 2 - inventory.php | 1517 +++++++++++++++++++++++++++++++++++++++++ managementhdd.php | 362 +++------- report_hdd.php | 10 +- savehdd.php | 12 +- 7 files changed, 1645 insertions(+), 302 deletions(-) create mode 100644 inventory.php diff --git a/classes/hdd.class.php b/classes/hdd.class.php index ede26a4cb..44f8d9252 100644 --- a/classes/hdd.class.php +++ b/classes/hdd.class.php @@ -9,7 +9,6 @@ class HDD { // Properties public int $HDDID = 0; public int $DeviceID = 0; - public string $Label; public string $SerialNo; public string $Status; public int $Size; @@ -17,7 +16,6 @@ class HDD { public string $DateAdd; public ?string $DateWithdrawn; public ?string $DateDestroyed; - public ?string $Note; private LoggerInterface $logger; @@ -30,12 +28,10 @@ public function __construct(LoggerInterface $logger = null) { public function MakeSafe(): void { //$this->HDDID = intval($this->HDDID); $this->DeviceID = intval($this->DeviceID); - $this->Label = sanitize($this->Label); $this->SerialNo = sanitize($this->SerialNo); $this->Status = sanitize($this->Status); $this->Size = intval($this->Size); $this->TypeMedia = sanitize($this->TypeMedia); - $this->Note = sanitize($this->Note); } public function MakeDisplay(): void { @@ -70,35 +66,32 @@ public function Create(): void { global $dbh; $this->MakeSafe(); $sql = "INSERT INTO fac_HDD - (DeviceID, Label, SerialNo, Status, Size, TypeMedia, DateAdd, Note) + (DeviceID, SerialNo, Status, Size, TypeMedia, DateAdd) VALUES - (:DeviceID, :Label, :SerialNo, :Status, :Size, :TypeMedia, NOW(), :Note)"; + (:DeviceID, :SerialNo, :Status, :Size, :TypeMedia, NOW())"; $stmt = $dbh->prepare($sql); $stmt->execute([ ":DeviceID" => $this->DeviceID, - ":Label" => $this->Label, ":SerialNo" => $this->SerialNo, ":Status" => $this->Status, ":Size" => $this->Size, ":TypeMedia" => $this->TypeMedia, - ":Note" => $this->Note ]); $this->HDDID = intval($dbh->lastInsertId()); self::logAction("Created", $this->HDDID); } // Quick creation from form data - public static function CreateFromForm(int $deviceID, string $label, string $serialNo, string $typeMedia, int $size): int { + public static function CreateFromForm(int $deviceID, string $serialNo, string $typeMedia, int $size): int { global $dbh; $stmt = $dbh->prepare( "INSERT INTO fac_HDD - (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd) + (DeviceID, SerialNo, Status, TypeMedia, Size, DateAdd) VALUES - (:DeviceID, :Label, :SerialNo, 'On', :TypeMedia, :Size, NOW())" + (:DeviceID, :SerialNo, 'On', :TypeMedia, :Size, NOW())" ); $stmt->execute([ ':DeviceID' => $deviceID, - ':Label' => sanitize($label), ':SerialNo' => sanitize($serialNo), ':TypeMedia' => sanitize($typeMedia), ':Size' => $size @@ -115,22 +108,18 @@ public function Update(): bool { $stmt = $dbh->prepare( "UPDATE fac_HDD SET DeviceID = :DeviceID, - Label = :Label, SerialNo = :SerialNo, Status = :Status, Size = :Size, - TypeMedia = :TypeMedia, - Note = :Note + TypeMedia = :TypeMedia WHERE HDDID = :HDDID" ); $res = $stmt->execute([ ":DeviceID" => $this->DeviceID, - ":Label" => $this->Label, ":SerialNo" => $this->SerialNo, ":Status" => $this->Status, ":Size" => $this->Size, ":TypeMedia" => $this->TypeMedia, - ":Note" => $this->Note, ":HDDID" => $this->HDDID ]); if ($res) self::logAction("Updated", $this->HDDID); @@ -181,14 +170,13 @@ public static function DuplicateToEmptySlots(int $sourceHDDID): void { } // Insère les duplicata $stmt = $dbh->prepare( "INSERT INTO fac_HDD - (DeviceID, Label, SerialNo, Status, TypeMedia, Size, DateAdd) + (DeviceID, SerialNo, Status, TypeMedia, Size, DateAdd) VALUES - (?, ?, ?, ?, ?, ?, NOW())" + (?, ?, ?, ?, ?, NOW())" ); for ($i = 0; $i < $remaining; $i++) { $stmt->execute([ $deviceID, - $hdd['Label'], uniqid('HDD_', true), $hdd['Status'], $hdd['TypeMedia'], @@ -206,13 +194,11 @@ public function SendForDestruction(string $note = ''): bool { $stmt = $dbh->prepare( "UPDATE fac_HDD SET Status = 'Pending_destruction', - DateWithdrawn = NOW(), - Note = CONCAT(Note, ' ', :Note) + DateWithdrawn = NOW() WHERE HDDID = :HDDID" ); return $stmt->execute([ ":HDDID" => $this->HDDID, - ":Note" => sanitize($note) ]); } @@ -268,7 +254,7 @@ public static function MarkAsSpare(int $id): bool { // List active HDDs for a device public static function GetHDDByDevice(int $DeviceID): array { global $dbh; - $stmt = $dbh->prepare("SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID AND (Status = 'On' OR Status = 'Off') ORDER BY Label ASC"); + $stmt = $dbh->prepare("SELECT * FROM fac_HDD WHERE DeviceID = :DeviceID AND (Status = 'On' OR Status = 'Off') ORDER BY SerialNo ASC"); $stmt->execute([":DeviceID" => $DeviceID]); $list = []; while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { @@ -370,17 +356,17 @@ public static function ExportAllToXls(int $deviceID): void { $sheet->setTitle($title); // En-têtes colonnes - $headers = ['HDDID','Label','SerialNo','Status','TypeMedia','Size','DateAdd','DateWithdrawn','DateDestroyed','Note']; + $headers = ['HDDID','SerialNo','Status','TypeMedia','Size','DateAdd','DateWithdrawn','DateDestroyed']; $sheet->fromArray($headers, null, 'A1'); // Récupère les HDDs pour ce statut $stmt = $dbh->prepare( - "SELECT HDDID, Label, SerialNo, Status, TypeMedia, Size, - DateAdd, DateWithdrawn, DateDestroyed, Note + "SELECT HDDID, SerialNo, Status, TypeMedia, Size, + DateAdd, DateWithdrawn, DateDestroyed FROM fac_HDD WHERE DeviceID = :DeviceID AND Status = :Status - ORDER BY Label ASC" + ORDER BY SerialNo ASC" ); $stmt->execute([ ':DeviceID' => $deviceID, diff --git a/create.sql b/create.sql index 82f9e1bea..dfd4b2246 100644 --- a/create.sql +++ b/create.sql @@ -1087,7 +1087,6 @@ INSERT INTO `fac_Country` VALUES ('AD','Andorra'),('AE','United Arab Emirates'), CREATE TABLE fac_HDD ( HDDID INT(11) NOT NULL AUTO_INCREMENT, DeviceID INT(11) NOT NULL, - Label VARCHAR(100), SerialNo VARCHAR(100), Status ENUM('On','Off','Pending_destruction','Destroyed','Spare') DEFAULT 'On', @@ -1096,7 +1095,6 @@ CREATE TABLE fac_HDD ( DateAdd DATETIME DEFAULT CURRENT_TIMESTAMP, DateWithdrawn DATETIME DEFAULT NULL, DateDestroyed DATETIME DEFAULT NULL, - Note TEXT DEFAULT NULL, PRIMARY KEY (HDDID) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/db-23.04-to-24.01.sql b/db-23.04-to-24.01.sql index 8ea308670..023b9afde 100755 --- a/db-23.04-to-24.01.sql +++ b/db-23.04-to-24.01.sql @@ -22,7 +22,6 @@ CREATE TABLE fac_DeviceTemplateHdd ( CREATE TABLE fac_HDD ( HDDID INT NOT NULL AUTO_INCREMENT, DeviceID INT NOT NULL, - Label VARCHAR(100), SerialNo VARCHAR(100), Status ENUM('On','Off','Pending_destruction','Destroyed','Spare') DEFAULT 'On', @@ -31,6 +30,5 @@ CREATE TABLE fac_HDD ( DateAdd DATETIME DEFAULT CURRENT_TIMESTAMP, DateWithdrawn DATETIME DEFAULT NULL, DateDestroyed DATETIME DEFAULT NULL, - Note TEXT DEFAULT NULL, PRIMARY KEY (HDDID) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/inventory.php b/inventory.php new file mode 100644 index 000000000..614ab0fc6 --- /dev/null +++ b/inventory.php @@ -0,0 +1,1517 @@ + + + +/* Reset All Broswers to Nothing */ +@import url('reset.css'); + +html { + font-family: helvetica,arial; + font-size: .833em; + padding-left: .9em; + padding-right: .9em; +} +select {padding: .05em;} +fieldset table, table {border: 1px solid grey;} +textarea {white-space: pre;word-wrap: break-word;} +.hide {display: none !important;} +.show {display: block;} +.greybg {background-color: lightGrey;} +.warning {text-align: center; color: red; text-transform: uppercase;} +.right {text-align: right;} +.left {text-align: left;} +.custom-combobox {position: relative;display: inline-block;} +.monospace {font-family: monospace !important;} +.noborder {border: 0px !important;} + +.floatleft { float: left; margin-right: 5px; } +.floatright { float: right; margin-left: 5px; } + +[readonly],[disabled] { + background-color: #dcdcdc; + border: 1px dotted grey; + padding: 1px; + color: #000000; + cursor: default; +} + +.arrow_left { position: relative; background: #ffffff; border: 1px solid #000000; } +.arrow_left:after, .arrow_left:before { right: 100%; border: solid transparent; content: " "; height: 0; width: 0; position: absolute; pointer-events: none; } +.arrow_left:after { border-color: rgba(255, 255, 255, 0); border-right-color: #ffffff; border-width: 15px; top: 15px; margin-top: -15px; } +.arrow_left:before { border-color: rgba(0, 0, 0, 0); border-right-color: #000000; border-width: 16px; top: 15px; margin-top: -16px; } + +.no-close .ui-dialog-titlebar-close {display: none;} + +@keyframes loading{ + from { + -webkit-transform:rotate(0deg); + -moz-transform:rotate(0deg); + -o-transform:rotate(0deg); + } + to { + -webkit-transform:rotate(360deg); + -moz-transform:rotate(360deg); + -o-transform:rotate(360deg); + } +} + +@-webkit-keyframes loading{ + from { + -webkit-transform:rotate(0deg); + -moz-transform:rotate(0deg); + -o-transform:rotate(0deg); + } + to { + -webkit-transform:rotate(360deg); + -moz-transform:rotate(360deg); + -o-transform:rotate(360deg); + } +} + + +.rotate{ + animation: loading 0.8s; + -webkit-animation: loading 0.8s; + + animation-iteration-count: infinite; + -webkit-animation-iteration-count: infinite; /*Safari and Chrome*/ + + overflow:hidden; +} + + +/* css for timepicker */ +.ui-timepicker-div .ui-widget-header {margin-bottom: 8px;} +.ui-timepicker-div dl {text-align: left;} +.ui-timepicker-div dl dt {height: 25px; margin-bottom: -25px;} +.ui-timepicker-div dl dd {margin: 0 10px 10px 65px;} +.ui-timepicker-div td {font-size: 90%;} +.ui-tpicker-grid-label {background: none; border: none; margin: 0; padding: 0;} + +/* Header/logo */ +#header{ + padding:5px 0; + background:ParameterArray['HeaderColor']; ?> url("../ParameterArray['PDFLogoFile']; ?>") no-repeat left center; + height:66px; + position: relative; +} +#header > span {color: white;display: block;margin-top: 5px;text-align: center; + text-shadow: 1px 1px 0 #063, 1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000; +} +#header1 {font-size: xx-large;} +#header2 {font-size: x-large;} +#header > #version {bottom: 2px;position: absolute;right: 4px;font-size:small; + text-shadow: 1px 1px 0 #063, 1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000; +} + +/* Configuration Page */ +div.cp { position: relative;} +.miniColors-trigger { position: absolute; top: 0; right: 0;} +.config .center input { width: 95%; } +#configtabs { min-width: 670px; } +#configtabs button { margin-left: 0.5em; margin-right: 0.5em;} +#configtabs span { font-style: italic; font-size: -1;} +#configtabs label:after {content:":"; margin-right: 0.5em;} +#configtabs #general div > input {width: 20em; } +#configtabs #general #rackusage input {width: 2em; } +#configtabs #general #rackusage > div > div:nth-child(5) { width: 6em; } +#configtabs #style .cp > input {width: 7em; } +#configtabs #email div > input {width: 20em; } + +#configtabs #reporting div:first-child + div > input {width: 20em; } + +div#directoryselection { display: none;} +#directoryselection #filelist { position: absolute; top: 30px; left: 1em; height: 380px; width: 245px; overflow-y: scroll; overflow-x: hidden; white-space: nowrap;} +#directoryselection #filelist a { line-height: 1.5em; } +#directoryselection #filelist a::before, +#imageselection #filelist a.dir::before { + display: inline-block; + background-image: url(../images/folder.gif); + content: ''; + background-size: 1.5em; + height: 1.75em; + width: 1.5em; + padding-right: 5px; + margin-bottom: -5px; + background-repeat: no-repeat; +} + +div#imageselection { display: none;} +#imageselection span { display: block; padding: 0.25em 0 0.5em 0.5em; cursor: pointer; text-decoration: underline; border: 1px solid white;} +#imageselection a.dir span { display: inherit; } +#imageselection #preview { position: absolute; top: 30px; right: 0; height: 340px; width: 340px; margin: 0.1em 0 0 0; padding: 0; border: 0px solid black;} +#imageselection #filelist { position: absolute; top: 30px; left: 1em; height: 380px; width: 245px; overflow-y: scroll; overflow-x: hidden; white-space: nowrap;} + +#configtabs .ui-menu-item ul { max-height: 200px; /* overflow: auto; */ } +#tzmenu {display: none;} +#tzmenu li > a {display: block; width: 100%; line-height: 1.25; color: #aaaaaa !important;} +#tzmenu li > a.ui-state-active {color: black !important; line-height: 1.5 !important; background: url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x !important;} +#tzmenu li > ul { overflow-x: hidden; } +#tzmenu li > ul { -ms-overflow-style: none; scrollbar-width: none; } +#tzmenu li > ul::-webkit-scrollbar { display: none; } + +#tooltip, #cdutooltip { min-height: 300px; min-width: 550px; } + +#tt .available.connected-list { + -ms-overflow-style: none; + scrollbar-width: none; +} +#tt .available.connected-list::-webkit-scrollbar { + display: none; +} + +.customattrsheader { padding-right: 10px; } +#customattrs input, #customattrs select { background-color: transparent; border-style: ridge; } + +/* index */ +.index .table, .index .table .title {background-color: white;} +.index .table .title {font-weight: bold; font-size: 1.25em;} +.index .table div {padding: 3px;} +.rackrequest div:first-child div {background-color: gray;text-align: center;color: white;font-weight: bold;} +.overdue {background-color:#FFE6F4;} +.soon {background-color:#FFFFAA;} +.clear {background-color:white;} + +/* Rack Request Page */ +.request fieldset { + background-color: white; + border: 1px solid grey; + padding: 10px; + margin-bottom: 8px; +} +.request legend {border: 1px ParameterArray['HeaderColor']; ?> solid;background-color: white; padding: .15em;} +.errmsg {display:block;font-style:italic;margin-left:2em;} +.hlight {color: red;} + + + +/* Data Center Stats */ +.dcstats .heading > div{width: 89%;display: inline-block;vertical-align: middle;} +.dcstats .heading > div + div {width: 10%;} +.dcstats .heading > div + div button {display: block;width: 100%;} +.dcstats .table, .dcstats .table .title { background-color: white; } +.dcstats .table .title { font-weight: bold; font-size: 1.25em; } +.dcstats .table .title span { font-size: 0.6em; vertical-align: top;} +.dcstats .table .title span:before { content:" [ "; } +.dcstats .table .title span:after { content:" ]";} +.dcstats .table div {padding: 3px;} +div#dcstats { display: table;} +div#dcstats > div{ width: 100%;} +div#dcstats .table + .table > div > div + div{white-space: pre; text-align: right;} +.canvas {position: relative; background-repeat: no-repeat;} +.canvas img {position: absolute; top: 0; left: 0; z-index: 10;} +.dcstats ~ #tt span {font-size: 1.5em; text-align: center; font-weight: bold;} +.dcstats ~ #tt ul {list-style-type: none;} +.dcstats ~ #tt ul li.red {background: url('../images/rs.png') left center no-repeat; line-height: 20px; padding-left: 20px;} +.dcstats ~ #tt ul li.green {background: url('../images/gs.png') left center no-repeat; line-height: 20px; padding-left: 20px;} +.dcstats ~ #tt ul li.yellow {background: url('../images/ys.png') left center no-repeat; line-height: 20px; padding-left: 20px;} +.dcstats ~ #tt ul li.wtf {background: url('../images/us.png') left center no-repeat; line-height: 20px; padding-left: 20px;} +#maptitle {padding: 8px; font-size: 120%; font-weight: bold;} +#maptitle .nav {float: right; height: 21px;} +#mapCanvas { margin-bottom: 50px; position: relative;} +canvas#background { position: absolute; } + +/* Storage Room */ +.storage .table, .storage .table #title { background: white; } +.storage .table #title {filter: none;} +.storage .table .title { font-weight: bold; font-size: 1.25em; } +.storage .table div {padding: 3px;} +.storage .table {min-width: 400px; max-width: 600px; margin-top: 2em;} + +/* Sidebar Menu */ +#sidebar { + position: relative; + min-width: 200px; + display: inline-block; + vertical-align: top; +} +#sidebar input, #sidebar textarea { + height: 27px; + width: 170px; + margin: 0; + vertical-align: text-bottom; + clear: left; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + padding: 5px; +} +#sidebar textarea { + -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ + -moz-box-sizing: border-box; /* Firefox, other Gecko */ + box-sizing: border-box; /* Opera/IE 8+ */ + border-style: solid none solid solid; + border-width: 1px 0 1px 1px; + border-color: black; + resize: none; +} +#sidebar input + button, #sidebar .text-core + button, button.iebug, #sidebar textarea + button { + height: 27px; + padding: 0px; + margin: 0px; + display: inline-block; + vertical-align: top; + clear: right; + border-left: 0px solid; + -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ + -moz-box-sizing: border-box; /* Firefox, other Gecko */ + box-sizing: border-box; /* Opera/IE 8+ */ + -moz-border-radius: 0px; + -moz-box-shadow: 0px; + -webkit-border-radius: 0px; + border-radius: 0px; +} +#sidebar form { margin-bottom: 4px; } +#sidebar input.search { height: 15px; padding: 5px; width: 141px; border: 1px solid black; border-right: 0; vertical-align: top;} +#sidebar input + button img, #sidebar .text-arrow + button img {height: 27px;} +#sidebar div.text-core {width: 150px; height: 27px;} +#sidebar div.text-core textarea{ width: 151px; height: 27px;} +#sidebar .advsearch { background: white; display: block; height: 4.5em; position: absolute; top: 0px; width: 350px; z-index: 99; } +#searchadv ~ select { padding: 5px; border: 1px solid black; } +#sidebar .advsearch.hide { display: none; } +#advsrch { color: ParameterArray['LinkColor']; ?>; cursor: pointer; } +#advsrch:before {content:"[ ";} +#advsrch:after {content:" ]";} +#searchadv ~ .ui-icon.ui-icon-close { position: absolute; top: 0; right: 0; cursor: pointer;} + +.text-arrow { + -moz-box-sizing: border-box; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAOAQMAAADHWqTrAAAAA3NCSVQICAjb4U/gAAAABlBMVEX///8yXJnt8Ns4AAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1MzmNZGAwAAABpJREFUCJljYEAF/xsY6hkY7BgYZBgYOFBkADkdAmFDagYFAAAAAElFTkSuQmCC") no-repeat scroll 50% 50% transparent; + cursor: pointer; + height: 22px; + position: absolute; + right: 0; + top: 0; + width: 22px; + z-index: 2; +} +.text-core { display: inline-block; } + +.ui-menu {z-index: 100;} +.ui-autocomplete { max-height: 10em; overflow-y: auto; overflow-x: hidden; padding-right: 20px;} +* html .ui-autocomplete {height: 100px;} +.ui-autocomplete li.ui-menu-item {display: block;} +.ui-menu .ui-menu-item a { line-height: 1 !important; white-space: nowrap !important; overflow: hidden;} +#mapadjust .main .ui-menu .ui-menu-item a { line-height: 1.5 !important;} + +#gandalf { + height: 100%; + width: 100%; + z-index: 99; + background-color: white; + position: absolute; + top: 0; +} +#gandalf div { + font-family: monospace; + white-space: pre; + width: 400px; + margin-left: auto; + margin-right: auto; +} + +/* Mapmaker */ +.mapmaker > div{width: 77%;display: inline-block;vertical-align: middle;} +.mapmaker > div + div {width: 19%;} +.mapmaker .table .table {margin-left: auto;} +.mapmaker + .center div{position: relative;width: 100%;} +.mapmaker + .center > div > div.container {position: absolute;top: 0px;left: 0px;} + +/* Zonemaker */ +.zonemaker > div{width: 100%;display: inline-block;vertical-align: middle;} +.zonemaker .table .table {margin-left: auto;} +.zonemaker + .center div{position: relative;width: 100%;} +.zonemaker + .center > div > div.container {position: absolute;top: 0px;left: 0px;} + +/* templatemaker */ +#regulartemplateattributes, #hiddencdudata, #hiddensensordata {display:inline-block;vertical-align:top;} +.templatemaker > div{width: 100%;display: inline-block;vertical-align: middle;} +.templatemaker .table .table {margin-left: auto;} +.templatemaker + .center div{position: relative;width: 100%;} +.templatemaker + .center > div > div.container {position: absolute; top: 0px; left: 0px;} +.templatemaker input + button, #btn_override, #btn_snmptest { line-height: 1em; vertical-align: middle; height: 1.5em; margin-top: -1px; } +.templatemaker #hiddencoords { position: absolute; left: -10000px; top: -10000px;} +.templatemaker #previewimage { width: 400px;} +.templatemaker fieldset label {padding-right: 1em;padding-left:0.25em;} +.templatemaker #atsbox {border: 1px solid black;padding:0.25em;margin-top: 1em;} +.templatemaker .ui-button { margin: 0; } +.table.front #previewimage, .table.rear #previewimage { position: relative; } +.table.front #coordstable, .table.rear #coordstable { width: 320px; padding-left: 10px;} +#coordstable input { width: 40px; } +#coordstable > .table > div:first-child { text-align: center; } +table.coordinates th {background-color: #CCC; text-align: center; vertical-align: middle; padding-left: .5em; padding-right: .5em; padding-top: .2em;padding-bottom: .2em;} +table.coordinates td {text-align: center; padding-left: .5em; padding-right: .5em; padding-top: 0.1em;padding-bottom: 0.1em;} +table.coordinates input {text-align: center; border: 0px;} +table.coordinates select {text-align: center; border: 0px;} +span.cdudisclaimer {color:red;font-weight:bold;} + +#hiddenports,#hiddenpowerports { position: absolute; left: -10000px; top: -10000px;} +.hiddenports .table { border: 1px solid black; } +.hiddenports .table > div:first-child { text-align: center; background-color: lightgray; border: 1px solid black;} +.hiddenports .table > div { background-color: white; } +.hiddenports .table > div > div { padding: 3px; } + +#rightside { vertical-align: top; } +#img_FrontPictureFile, #img_RearPictureFile { max-width: 125px; max-height: 200px; padding-right: 5px;} + +/* Basic Page Layout */ +.page {position: relative;width: 100%;} +.clear {clear: both;} +p, h2, h3, h1 {margin-top: 1em;margin-bottom: 1em;} +h2 {font-size: 1.5em;text-align: center;} +h3 {font-size: 1.16em;text-align: center;} +h3 + h3 {color: red;font-weight: bold;} +h4 {font-size: 1.1em;text-align: center;} +h3 + h5 {margin-bottom: 0.5em;} +a:link, a:hover, a:visited:hover {color:ParameterArray['LinkColor']; ?>;} +a:visited {color: ParameterArray['VisitedLinkColor']; ?>;} + +div.main { + display: inline-block; + vertical-align: top; + min-height: 500px; + padding: 5px; + background-color: ParameterArray['BodyColor']; ?>; + border: 1px dotted #333; + margin-bottom: 2em; +} +.main > div { + margin-bottom: 2em; +} +div.center > div {display: inline-block;text-align: left;} +.center {text-align: center;min-height: 400px;} +.centermargin {margin-left: auto;margin-right: auto;} + +.table {display: table;text-align: left;border-collapse: collapse;} +.caption {caption-side: bottom; text-align: center; display: table-caption !important; white-space: nowrap;} +.title {caption-side: top; text-align: center; display: table-caption !important;} +div.table > div {display: table-row;} +div.table > div > div {display: table-cell;vertical-align: middle; /* padding-bottom: .75em; */} +/* div.table > div > div span {display: block;font-size: 0.75em;} */ +.table label{width:130px;} +.whiteborder, .whiteborder div {border: 1px solid white;} +.border, .border div {border: 1px solid gray;} + +/* Search Results */ +.search .center {text-align: left;} +.search .main ol, .search .main ul{list-style-type: none;margin-left: 1em;} +.search ol {margin-top: .35em;} +.search ol li {margin-bottom: .35em;} +.search ol ul li {margin-left: 1em;margin-bottom: 0em;} +.search ol ul li div, .search ol li.datacenter div {display: inline;} +.search ol ul li div, .search ol li.cabinet div {display: inline;} +.search ol ul li div img, .search ol li.cabinet div img {vertical-align: middle;height: 1em;margin-right: .25em;} +.search .main .bullet { background: url("minus.gif") no-repeat scroll left center transparent; cursor: pointer; padding-left: 15px;} +.search .hidecontents li.cabinet > ol { display: none; } + +/* User Rights */ +.rights > div:nth-last-child(2) div {text-align: center;padding-top: .75em;padding-bottom: .75em;} +div.table > div + div + div + div > div + div label {float: none;} +#primarycontact {cursor:pointer;} +#deptgroup .ui-multiselect ul.available li { overflow-x: hidden; } + +/* Project Catalog */ +#projectgroup .ui-multiselect ul.available li { padding: 0.5em 0.5em 0.5em 20px; height: auto; line-height: inherit;} + +/* Contact Editor */ +#deletedialog {display: none;} +#deletedialog p {font-weight: bold;} +#deletedialog li {margin-left: 1em; list-style: disc outside none;} +#deletedialog div {width: 45%; display: inline-block; vertical-align: top;} +#deletedialog .middle {width: 9%;} + +/* Inventory Reports */ +.reports fieldset {margin-right: 20px;} +#reports > div {display: inline-block;vertical-align: top;} +#reports > div a {display: block;} + +/* PDU Info */ +.pdu .center > div + div > .table > div > div{padding: 3px;} +.pdu #btn_override { height: 1.2em; line-height: 1em; margin: 3px 0 3px 10px; vertical-align: middle; } + +/* Power Panels */ +div.center > div + div {vertical-align: top;padding-left: 1em;} +div.center > div + div div.table {background-color: white;} +div.center div table { + background-color: white; + border-collapse: collapse; + margin-left: auto; + margin-right: auto; +/* max-width: 400px; */ +} +div.center div table table{min-width: 150px;} +div.center div table, div.center div tr, div.center div td {border: 1px solid gray;} +.cabinet tr > td:first-child, .panelmgr .polenumber {padding: 0.25em 0.5em;text-align: center;} +.panelmgr .polelabel { + min-width: 150px; + max-width: 400px; + padding: 0.25em 0.5em 0.25em 1em; + vertical-align: middle; +} +.panelmgr .main form input, .panelmgr .main form select { + padding-right: 0px; + width: 100%; +} +.polelabel a {display: block;margin-bottom: 0.35em;} +.polelabel a span {display: block;margin-left: 1.5em;} +td#oddeven {padding: 0px;text-align: left;width: 150px;} +.caption h3 {margin-bottom: 0px;font-size: 1.25em;} +#powerinfo {margin-top: 0em;} +#powerinfo .table {background-color: white;} +#powerinfo .caption {border: 0px !important;} +div.error {margin-top: 2em;margin-bottom: 2em;border: 1px dotted gray;} +.error legend {color: red;font-weight: bold;} +.error > div > div {width: 200px;vertical-align: top !important;} +.error > div > div + div {font-style: italic;} +.error span {display: block;margin-left: 1.5em;} +#pdutest {display: none;} +.panelmgr .main form, .panelmgr .main form ~ div { display: inline-block; vertical-align: top;} +.panelmgr .main .center > div { margin-right: 200px; } +.pwr_gauge { position: absolute; right: 50px; top: 0px; } +.pwr_gauge + .pwr_gauge { top: 200px; } +.pwr_gauge + .pwr_gauge + .pwr_gauge { top: 400px; } + +/* Department Administration */ +#groupadmin { + overflow: hidden; + min-width: 580px; + min-height: 150px; + display: none; + margin-top: 20px; + border: 1px solid gray; +} +#deptgroup {background-color: ParameterArray['BodyColor']; ?>;} +#deptgroup > div {padding:5px 10px;width:580px;min-height:300px;} +#deptgroup > div h3 {margin-top: 0;margin-bottom: 5px;} +#deptgroup > div h3 button {margin-left:10px;vertical-align:middle;} +#deptgroup h3 + div {margin-left: 42.5px;} +#deptgroup select {width: 440px;} +#displaynone {display: none !important;} +#cnt_cabinets, #cnt_devices, #cnt_users { cursor: pointer; text-decoration: underline; } + +#projectadmin { + overflow: hidden; + min-width: 700px; + min-height: 150px; + display: none; + margin-top: 20px; + border: 1px solid gray; +} +#projectgroup {background-color: ParameterArray['BodyColor']; ?>;} +#projectgroup > div {padding:5px 10px;width:580px;min-height:300px;} +#projectgroup > div h3 {margin-top: 0;margin-bottom: 5px;} +#projectgroup > div h3 button {margin-left:10px;vertical-align:middle;} +#projectgroup h3 + div {margin-left: 42.5px;} +#projectgroup select {width: 600px;} + +/* Rack Content */ +.legenditem {padding: 0.2em;height: 1.1em;line-height: 1.2em;overflow: hidden;padding: 0.2em;white-space: nowrap;width: 210px;} +.colorbox {width: 1.1em; display: inline-block; vertical-align: text-bottom;height: 1.1em; margin: 0px; padding: 0px;} +#infopanel { + position: relative; + display: inline-block; + max-width: 240px; +} +#infopanel fieldset, .reports fieldset { + background-color: white; + border: 1px solid grey; + padding: 10px; + margin-bottom: 8px; +} +#infopanel fieldset button, #infopanel fieldset input[type=submit], #infopanel fieldset input[type=button],.reports fieldset button, .reports fieldset input[type=submit], .reports fieldset input[type=button] {width: 100%;} +#infopanel legend, .device legend, .reports legend {border: 1px ParameterArray['HeaderColor']; ?> solid;background-color: white;} +div.cabinet { + display: inline-block; + vertical-align: top; + min-width: 200px; + max-width: 250px; + margin-right: 20px; +} + +#servercontainer .dept0, #servercontainer-rear .dept0, #servercontainer-side .dept0 {background-color: #fff;} + +.cabinet .pos { text-align: center; } +/* stupid safari layout glitch */ +.cabinet table.cabinet { border-collapse: collapse; } +.cabinet table.cabinet tr:nth-child(n+3) {height: 21px;} +.cabinet #servercontainer, .cabinet #servercontainer-rear, .cabinet #servercontainer-side { background-image: url("../images/racku-background.png"); position: relative; padding: 0px; margin: 0px;} +.genericdevice {display: flex;justify-content: center; align-items: center; height: 100%; border: 2px black solid; background-color: inherit; overflow: hidden; white-space: nowrap;} + +.cabinet td + td {vertical-align: middle;width: 220px; } +.cabinet td.cabpos {text-align: center; vertical-align: middle;padding: 0.25em 0.5em;width: 10%;} +.cabinet th{font-size: 1.5em;padding: 0.25em;text-align: center;} +#zerou a{display: block;} + +.cabnavigator .nav { text-align: center; } +.cabnavigator .nav li { margin-top: 0.1em; border: 1px solid darkGray;} +.cabnavigator .nav a:hover li { border-color: black; } + +.cabnavigator th a { color: black; text-decoration: none; pointer-events: none; } + +.cabnavigator.tooltip { + min-height: 30px; + min-width: 30px; + z-index: 99; + position: absolute; + white-space: nowrap; +} +.cabnavigator.tooltip div { + border: 0 none; + line-height: 1.25em; + margin: 5px; + padding: 3px; +} +.blackout { background-color: black; } +.rowview .noprint span:last-child {display: none;} +.rowview div.cabinet { vertical-align: bottom; } +.cabinet .error { background-color: ParameterArray['CriticalColor']; ?> !important; } + +/* flippingpostits - START */ +.loader { + width: 100px; + height: 100px; + -webkit-perspective: 100px; + perspective: 100px; + position: absolute; + top: 25%; + left: 50%; + margin-top: -50px; + margin-left: -50px; +} + +.loader__tile { + display: block; + float: left; + width: 33.33%; + height: 33.33%; + -webkit-animation-name: flip; + animation-name: flip; + -webkit-transform-style: preserve-3d; + transform-style: preserve-3d; + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; + -webkit-animation-duration: 2s; + animation-duration: 2s; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + -webkit-transform: rotateY(0deg); + transform: rotateY(0deg); + z-index: 0; +} + +.loader__tile__1 {background-color: #943048;-webkit-animation-delay: 0.1s;animation-delay: 0.1s;} +.loader__tile__2 {background-color: #d7532d;-webkit-animation-delay: 0.2s;animation-delay: 0.2s;} +.loader__tile__3 {background-color: #d2cabb;-webkit-animation-delay: 0.3s;animation-delay: 0.3s;} +.loader__tile__4 {background-color: #9faad0;-webkit-animation-delay: 0.4s;animation-delay: 0.4s;} +.loader__tile__5 {background-color: #b39a3b;-webkit-animation-delay: 0.5s;animation-delay: 0.5s;} +.loader__tile__6 {background-color: #dc2c34;-webkit-animation-delay: 0.6s;animation-delay: 0.6s;} +.loader__tile__7 {background-color: #ece5be;-webkit-animation-delay: 0.7s;animation-delay: 0.7s;} +.loader__tile__8 {background-color: #d07500;-webkit-animation-delay: 0.8s;animation-delay: 0.8s;} +.loader__tile__9 {background-color: #7983a9;-webkit-animation-delay: 0.9s;animation-delay: 0.9s;} + +@-webkit-keyframes flip { + 0% {-webkit-transform: rotateY(0deg); transform: rotateY(0deg); } + 11% {-webkit-transform: rotateY(180deg);transform: rotateY(180deg);} +} + +@keyframes flip { + 0% {-webkit-transform: rotateY(0deg); transform: rotateY(0deg); } + 11% {-webkit-transform: rotateY(180deg);transform: rotateY(180deg);} +} + +/* flippingpostits - END */ +/* spinningsquares - START */ + +.dizzy-gillespie { + -webkit-filter: saturate(3); + filter: saturate(3); + width: 0.1px; + height: 0.1px; + border: 40px solid transparent; + border-radius: 5px; + -webkit-animation: loader 3s ease-in infinite, spin 1s linear infinite; + animation: loader 3s ease-in infinite, spin 1s linear infinite; + position: absolute; + top: 25%; + left: 50%; + margin-top: -50px; + margin-left: -50px; +} + +.dizzy-gillespie::before { + -webkit-filter: saturate(0.3); + filter: saturate(0.3); + display: block; + position: absolute; + z-index: -1; + margin-left: -40px; + margin-top: -40px; + content: ''; + height: 0.1; + width: 0.1; + border: 40px solid transparent; + border-radius: 5px; + animation: loader 2s ease-in infinite reverse, spin 0.8s linear infinite reverse; +} + +.dizzy-gillespie::after { + display: block; + position: absolute; + z-index: 2; + margin-left: -10px; + margin-top: -10px; + content: ''; + height: 20px; + width: 20px; + border-radius: 20px; + background-color: white; +} + +@-webkit-keyframes loader { + 0% {border-bottom-color: transparent;border-top-color: #114357;} + 25% {border-left-color: transparent;border-right-color: #826C75;} + 50% {border-top-color: transparent;border-bottom-color: #F29492;} + 75% {border-right-color: transparent;border-left-color: #826C75;} + 100% {border-bottom-color: transparent;border-top-color: #114357;} +} + +@keyframes loader { + 0% {border-bottom-color: transparent;border-top-color: #114357;} + 25% {border-left-color: transparent;border-right-color: #826C75;} + 50% {border-top-color: transparent;border-bottom-color: #F29492;} + 75% {border-right-color: transparent;border-left-color: #826C75;} + 100% {border-bottom-color: transparent;border-top-color: #114357;} +} +@-webkit-keyframes spin { + 0% {-webkit-transform: rotate(0deg); transform: rotate(0deg);} + 100% {-webkit-transform: rotate(-360deg);transform: rotate(-360deg);} +} +@keyframes spin { + 0% {-webkit-transform: rotate(0deg); transform: rotate(0deg);} + 100% {-webkit-transform: rotate(-360deg);transform: rotate(-360deg);} +} + +/* spinningsquares - END */ +/* multiaxistrainer - START */ + +.preloader { + position: absolute; + margin: -48px 0 0 -48px; + display: block; + position: relative; + width: 90px; + height: 90px; + border: 3px solid #eb1777; + border-radius: 50%; + top: 25%; + left: 50%; + -webkit-animation-delay:0.2s; + animation-delay:0.2s +} + +.preloader:before { + content: ""; + display: block; + position: absolute; + width: 58px; + height: 58px; + border: 3px solid #3bb4e5; + top: 50%; + left: 50%; + margin: -32px 0 0 -32px; + border-radius: 50%; + -webkit-animation-delay:0.4s; + animation-delay:0.4s +} + +.preloader:after { + content: ""; + display: block; + position: absolute; + border: 3px solid #ccdc25; + width: 26px; + height: 26px; + top: 50%; + left: 50%; + margin: -16px 0 0 -16px; + border-radius: 50%; + -webkit-animation-delay:0.6s; + animation-delay:0.6s +} + +.preloader, +.preloader:before, +.preloader:after { + animation-name: Scale; + animation-duration: 3s; + animation-iteration-count: infinite; + animation-timing-function: ease-in-out; + animation-direction: alternate; + -webkit-animation-name: Scale; + -webkit-animation-duration: 3s; + -webkit-animation-iteration-count: infinite; + -webkit-animation-timing-function: ease-in-out; + -webkit-animation-direction: alternate; +} + +@keyframes Scale { + 25% {-webkit-transform: scale(-1.2, 1.2);transform: scale(-1.2, 1.2)} + 50% {-webkit-transform: scale(-1, -1); transform: scale(-1, -1)} + 75% {-webkit-transform: scale(1.2, -1.2);transform: scale(1.2, -1.2)} + 100% {-webkit-transform: scale(1, 1); transform: scale(1, 1)} +} + +@-webkit-keyframes Scale { + 25% {-webkit-transform: scale(-1.2, 1.2)} + 50% {-webkit-transform: scale(-1, -1)} + 75% {-webkit-transform: scale(1.2, -1.2)} +} + +/* multiaxistrainer - END */ +/* rotatingloader - START */ + +@-webkit-keyframes rotate { + from {transform: rotate(0deg);} + to {transform: rotate(360deg);} +} +@-moz-keyframes rotate { + from {transform: rotate(0deg);} + to {transform: rotate(360deg);} +} +@-o-keyframes rotate { + from {transform: rotate(0deg);} + to {transform: rotate(360deg);} +} +@keyframes rotate { + from {transform: rotate(0deg);} + to {transform: rotate(360deg);} +} +@-webkit-keyframes rotateCounter { + from {transform: rotate(0deg);} + to {transform: rotate(-360deg);} +} +@-moz-keyframes rotateCounter { + from {transform: rotate(0deg);} + to {transform: rotate(-360deg);} +} +@-o-keyframes rotateCounter { + from {transform: rotate(0deg);} + to {transform: rotate(-360deg);} +} +@keyframes rotateCounter { + from {transform: rotate(0deg);} + to {transform: rotate(-360deg);} +} + +.rotateloader { + display: flex; + flex-flow: column nowrap; + justify-content: center; + max-width: 200px; + margin: 5em; + animation-duration: 2s; + animation-iteration-count: infinite; + animation-timing-function: linear; + position: absolute; + top: 15%; +} +.rotateloader.one {animation-duration: 3s;animation-name: rotate;} +.rotateloader.one .row .box {animation-duration: 1.5s;animation-name: rotateCounter;} +.rotateloader.two {animation-duration: 3s;animation-name: rotate;} +.rotateloader.two .row {animation-duration: 1.5s;animation-name: rotateCounter;} +.rotateloader.two .row .box {animation-duration: 3s;} +.rotateloader.three .row {animation-duration: 2s;animation-name: rotateCounter;} +.rotateloader.four {animation-name: rotate;} +.rotateloader.four .row {animation-duration: 10s;animation-name: rotate;} +.rotateloader.four .row .box {animation-duration: 4s;animation-name: rotateCounter;transform-origin: 50% 75%;} + +.rotateloader .row {animation-duration: 1s;animation-iteration-count: infinite;animation-timing-function: linear;display: flex;justify-content: center;flex-direction: row;} +.rotateloader .row .box {animation-duration: 2s;animation-iteration-count: infinite;animation-timing-function: linear;animation-name: rotate;} + +.box { + width: 20px; + height: 20px; + line-height: 20px; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.8); + border-radius: 5px; + margin: 0.2em; + text-align: center; +} +.box.white {background-color: white;} +.box.red {background-color: ParameterArray['BodyColor']; ?>;} +.box.blue {background-color: ParameterArray['HeaderColor']; ?>;} + +/* rotatingloader - END */ +/* rollingbox - START */ +.boxLoading { + width: 50px; + height: 50px; + margin: auto; + position: relative; + left: 0; + right: 0; + top: 25%; + bottom: 0; +} +.boxLoading:before { + content: ''; + width: 50px; + height: 5px; + background: #000; + opacity: 0.1; + position: absolute; + top: 59px; + left: 0; + border-radius: 50%; + animation: shadow .5s linear infinite; +} +.boxLoading:after { + content: ''; + width: 50px; + height: 50px; + background: ParameterArray['HeaderColor']; ?>; + animation: animate .5s linear infinite; + position: absolute; + top: 0; + left: 0; + border-radius: 3px; +} +@keyframes animate { + 17% {border-bottom-right-radius: 3px;} + 25% {transform: translateY(9px) rotate(22.5deg);} + 50% {transform: translateY(18px) scale(1, 0.9) rotate(45deg);border-bottom-right-radius: 40px;} + 75% {transform: translateY(9px) rotate(67.5deg);} + 100% {transform: translateY(0) rotate(90deg);} +} +@keyframes shadow { + 0%, + 100% {transform: scale(1, 1);} + 50% {transform: scale(1.2, 1);} +} +/* rollingbox - END */ + + +/* PICTURES */ +.disabled {pointer-events: none;cursor: default;} +.cabnavigator div.picture {position:relative; left:0px; top:0px; z-index: 5;} +.picture div {position:absolute; z-index: 10; padding: 0 !important;} +.picture .label { + z-index: 11; + text-align: center; + vertical-align: middle; + color: white; + font-family: arial; + text-shadow: 1px 1px 0 #063, 1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000; + filter: glow(color=#063,strength=2), alpha(opacity=90); +} +.picture .label { height: 16px; } +.picture .label > div {text-align: center;width: 100%;} +.picture .label > div, +.picture div > a > div > div { top: 10%; height: 80%; padding-left: 0.3em;} +.picture div > a > div > div {overflow: hidden;} +.picture div .label {overflow: hidden;} +.label.noimage { margin: -2px; border: 2px solid black; } +.cabnavigator .picture div img:hover, .cabnavigator .picture a > div:hover { border: 2px solid red; margin: -2px;} +.cabnavigator .picture div img.rlt:hover { margin: -2px 0 0 2px;} + +.picture { + left: 0; + position: relative; + top: 0; + z-index: 5; + display: inline-block; + padding: 0 !important; +} +.picture div { + position: absolute; +} +.picture img { + height: 100%; + width: 100%; + position: absolute; +} +.picture div > a ~ .label { + pointer-events: none; +} +.picture > .label { + text-align: center; + pointer-events: none; +} +.picture > div > .label { + pointer-events: none; +} +.picture > div .label { + top: 0; +} +.label { + display: block; + z-index: 5; + top: 25%; + width: 90%; + height: 13px; + left: 5%; + overflow: hidden; + word-break: break-all; +} +.rotar_d{ + transform:rotate(90deg); + -webkit-transform:rotate(90deg); + -moz-transform:rotate(90deg); + -ms-transform:rotate(90deg); + -o-transform:rotate(90deg);} +.rlt { + transform-origin: left top; + -webkit-transform-origin: left top; + -moz-transform-origin: left top; + -ms-transform-origin: left top; + -o-transform-origin: left top; +} + +/* Cabinet Properties */ + +#infopanel #cabprop { + margin: 0px 0px 2px 0px; + border-collapse: separate; + border: 0px none; + border-spacing: 3px; + width: 100%; + min-width: 200px; max-width: 350px; +} +#infopanel #cabprop td:first-child{ + padding: 3px 2px 3px 2px; + font-weight: bold; + border: 0px none; + border-bottom: 1px solid gray; + margin: 2px 2px 2px 0px; +} +#infopanel #cabprop td:nth-child(2){ + text-align: left; + padding: 3px 2px 3px 4px; + border-style: none solid solid none; + border-width: 0 2px 1px 0; + margin: 2px 0px 2px 2px; +} +#infopanel #cabprop td:nth-child(2) > span { + -webkit-border-radius: 2px; + border-radius: 2px; + box-sizing: border-box; + border: 1px solid #9daccc; + background: #e2e6f0; + padding: 0px 3px 0px 3px; + margin: 0 2px 2px 0; + font: 11px "lucida grande",tahoma,verdana,arial,sans-serif; + display: inline-block; +} + +/* image_management */ +.imagem div.center > div { width: 350px; } +.imagem div.center > div + div { width: 550px; } + +.imagem div.preview { + background-color: #FFFFFF; + border: 1px solid #808080; + height: 300px; + padding: 5px; + width: 500px; + overflow: scroll; +} + +.imagem .preview > div { + border: 1px solid #000000; + display: inline-block; + margin: 3px; + padding: 5px; + position: relative; +} +.imagem .preview > div > .del { + position: absolute; + top: 0; + right: 0; + height: 20px; + width: 20px; + background-image: url('../images/x.gif'); + opacity: .4; + z-index: 5; +} +.imagem .preview > div > div:first-child { + background-size: contain; + height: 100px; + width: 100px; + background-repeat: no-repeat; + background-position: center center; + margin: -1px auto 5px; + padding: 2px; +} +.preview .filename { max-width: 100px; word-break: break-all; } +.imagem .heading { border-bottom: 1px solid; font-size: 2em; margin-bottom: 5px; text-align: right; } + +.uploadifive-queue-item .close { cursor: pointer; } +.uploadifive-button { padding: 0 8px; } + +/* devices.php Device Detail */ +.device fieldset { + display: block; + vertical-align: top; + margin-bottom: 1.5em; + margin-top: 1em; + background-color: white; + border: 1px dotted gray; + padding: 0.25em; +} +//.device fieldset .custom-combobox{margin: 0;padding: 0 0 0 2px;} +.device fieldset .custom-combobox{margin: 0;padding: 0;} +.device fieldset .custom-combobox input{margin: 0;} +.device fieldset .custom-combobox a {padding: 1px 0;position: absolute;} +.device div.right { max-width: 495px; } +.device div.left, .device div.right { + margin-bottom: 1.5em; + display: inline-block; + vertical-align: top; + text-align: left; +} + +.on { color: green; } +.off { color: red; } + +.device #deviceimages > div { width: 355px; margin-left: auto; margin-right: auto; } +.device #deviceimages > div > img { width: 175px; } + +.device #auditdate { line-height: 2em; } + +.device .table {width: 100%;} +.device .table.style > div:nth-child(2n+1) > div {border-top: 1px solid grey;vertical-align: top;} +.device .table.style > div:nth-child(2n+1) > div:first-child {background-color: lightGray;border-left: 1px solid grey;} +.device .table > div > div {min-width: 100px;} +.device .caption {margin-top: 2em;} +.device .table .table .table, .right .table + .table {background-color: white;width: 100%; height: 100%;} +.device .table .table .table > div > div {padding: 3px;} +.right .table + .table {margin-top: 1em;} + +.table.patchpanel > div:first-child > div > div, +.table.switch > div:first-child > div > div, +.table.power > div:first-child > div > div { position: relative; border: 0px none; margin: -3px; padding-right: 20px; } +.table.patchpanel > div:first-child > div select, +.table.switch > div:first-child > div select, +.table.power > div:first-child > div select { position: absolute; top: -3px; right: 0px; } + +.table.patchpanel > div:first-child, .table.switch > div:first-child { white-space: nowrap; } + +.device div[id^="controls"] { border: 0 none; white-space: nowrap; } + +.device .table.patchpanel div[id^="pp"] { border-left: 2px solid black; min-width: 10px;} +.device .table.patchpanel > div:first-child div[id^="pp"], +.device .table.patchpanel > div:first-child div[id^="mt"] {border-top: 1px solid black; } +.device .table.patchpanel > div:last-child div[id^="pp"], +.device .table.patchpanel > div:last-child div[id^="mt"] {border-bottom: 1px solid black; } +.device .table.patchpanel div[id^="pp"]:NOT([id="pp"]) { cursor: pointer; text-decoration: underline; } +.device .table.patchpanel div[id^="mt"] { border-right: 2px solid black; } +.device .table.patchpanel div[id^="pp"], +.device .table.patchpanel div[id^="mt"] { background-color: rgba(211, 211, 211, 0.5);} +.device .table.patchpanel > div > div {min-width: auto;} +.device .table.patchpanel > div:first-child select, +.device .table.switch > div:first-child select, +.device .table.power > div:first-child select { position: absolute; background-color: transparent; border: 0px none; width: auto;} +.device .table.patchpanel > div:first-child select::-moz-focus-inner, +.device .table.patchpanel > div:first-child select:focus::-moz-focus-inner, +.device .table.switch > div:first-child select::-moz-focus-inner, +.device .table.switch > div:first-child select:focus::-moz-focus-inner {border: none;} + +.device .path div { border: 0px none; } +.device .path > div > div { position: relative; height: 1em; } +.device .path > div > div > div { position: absolute; top: 0.15em; min-width: 550px; padding-left: 25px; white-space: nowrap; font-weight: 100; font-size: 0.85em;} +.device .path span:after{ content: "\2192";} +.device .path span:last-child:after{ content: "";} + +#pandn.table span.custom-combobox { width: 100%;} +#pandn.table .custom-combobox input, #pandn.table .custom-combobox a {border-top: 2px; border-bottom: 2px; border-style: inset; width: auto; height: 18px;} +#pandn.table .custom-combobox input {width: calc(100% - 18px);} +#pandn.table .custom-combobox input {background-image: none; border-left: 2px; border-right: 0px; padding-left: 4px; font-size: inherit;} +#pandn.table .custom-combobox a {margin: 0; vertical-align: top; border-left: 0px; border-right: 2px; position: absolute;} + +#olog > div:first-child { border-bottom: 2px solid black; } +#olog > div > div:first-child { width: 100px; padding-right: 5px; white-space: nowrap; } +#olog > div:first-child > div:first-child { border-right: 0 none; } +#olog > div:first-child > div:first-child ~ div { border-left: 0 none; } + +#olog > div:nth-child(2) > div { padding: 0px; } +#olog > div:nth-child(2) > div > div { max-height: 9em; overflow-y: scroll; overflow-x: hidden; border: 0;} + +#olog > div:last-child > div > button { float: right; line-height: 1em; height: 1.75em;} +#olog > div:last-child > div > button ~ div { overflow: hidden; padding-right: 1em; border: 0 none; } +#olog > div:last-child > div > button ~ div > input { width: 100%; } + +#olog .table > div > div ~ div {white-space: pre-wrap; max-width: 800px; word-wrap: break-word;} + +#devicetype-limiter, #connection-limiter { display: inline-block; margin-top: 10px; margin-bottom: 2px; vertical-align: super; } +#devicetype-limiter .ui-button-text-only .ui-button-text, +#connection-limiter .ui-button-text-only .ui-button-text { padding: 0.2em; } +#devicetype-limiter label, #connection-limiter label { width: auto; } + +.device #tags { width: 95%; min-width: 250px;} + +#firstport.hide { display: none; } + +.device fieldset .table label { white-space: nowrap;} + +.device .delete .ui-icon.status.down {cursor: pointer;} +.switch .delete, .patchpanel .delete { border: 0 none; } +.switch.table > div > div, +.power.table > div > div, +.patchpanel.table > div > div { min-width: 0px; } +.switch.table > div > div:first-child, +.patchpanel.table > div > div:first-child { min-width: 15px; } +/* can't explain where the 2px is coming from */ +.switch.table input, .patchpanel.table input { height: 18px; } +.switch.table input, .switch.table select, +.patchpanel.table input, .patchpanel.table select { padding: 0; background-color: transparent;} +.switch.table div[id^=n] input { width:98%; } + +.switch .status, .power .status, .patchpanel .down { background-image: url("../images/portstatus.png");} +.switch .down, .patchpanel .down { background-position: left; } +.switch .up { background-position: right; } +.power .up { background-position: right; } + +.chassis .table input{text-align:center;} +.chassis .table > div > div{text-align:center;} +.chassis .table + .table > div > div{text-align:left;} +.chassis .table > div:first-child > div, .chassis label{font-weight:bold;padding-bottom:0.5em;} +.chassis .table + .table > div > div{min-width:0px;padding-right:0.75em;padding-bottom:0.25em;} +.chassis .table + .table > div > div:first-child, .chassis .table + .table > div > div:nth-child(2){text-align: center;} + +.positionselector {font-size: .7em; background-color: white;} +.positionselector > div > div > div {width: 1em; height: 1em; padding-left: .5em; padding-right: .5em; text-align: right;} +.positionselector > div > div + div > div {width: 3em; padding-right: 1em; padding-right: 1em;} +.notavail {background-color: black; border-color: black !important;} +/* borders were too thick looking */ +.positionselector > div > div > div{ border-top: 0px; border-left: 0px;} +.positionselector > div > div + div > div{ border-top: 0px; border-right: 0px;} +.positionselector > div { border-width: 1px;} +.positionselector, .positionselector > div > div {border-width: 0px;} +#Positionselector .positionselector > div > div {min-width: 0;} +#Positionselector {padding: 10px; position: absolute; left: -1000px; background-color: white; border: 1px solid black; z-index: 99;} + +#editbtn { display: block; margin-bottom: 5px;} +#preview { width: 340px; min-height: 130px; background-color: white; border: 1px solid grey; padding: 5px;} +#preview img { display: block; border: 0px; max-width: 330px;} + +/* hey I do something function */ +.wade{ + position: relative; + width: 250px; + height: 120px; + padding: 0px; + background: #FFFFFF; + -webkit-border-radius: 17px; + -moz-border-radius: 17px; + border-radius: 17px; + border: #000000 solid 1px; +} + +.wade:after{ + content: ''; + position: absolute; + border-style: solid; + border-width: 15px 16px 0; + border-color: #FFFFFF transparent; + display: block; + width: 0; + z-index: 1; + bottom: -15px; + left: 19px; +} + +.wade:before{ + content: ''; + position: absolute; + border-style: solid; + border-width: 15px 16px 0; + border-color: #000000 transparent; + display: block; + width: 0; + z-index: 0; + bottom: -16px; + left: 19px; +} + + + +/* Logging style */ +#logtable { width: 100%; width: calc(100% - 36px); border: 1px solid black; } +#logtable > div:first-child { border-bottom: 1px solid black; font-size: large;} +#logtable > div:nth-child(2n) { background-color: lightgray; border-bottom: 1px dotted black; } +#logtable > div ~ div > div:first-child{ padding: 3px; white-space: nowrap;} +#logtable > div ~ div > div:nth-child(4){ border-left: 2px dotted black; padding-left: 3px; white-space: nowrap;} +#logtable > div ~ div > div:nth-child(5){ text-align: right; } +#logtable > div ~ div > div:nth-child(5):before{ content:"'"; } +#logtable > div ~ div > div:nth-child(5):after{ content:"' => "; } +.logtable > div.ui-dialog-content { overflow-y: auto; overflow-x: hidden; } + +/* Button code primarily from http://somadesign.ca */ +/* Button */ +.button, input[type=button], input[type=submit], button { + text-decoration: none; + border-color:#888; + border-color:rgba(0, 0, 0, 0.56); + cursor: pointer; + outline: none; + color:#111; + display:inline-block; + vertical-align:top; + position:relative; + font-size:12px; + text-align:center; + background-color:#aaa; + background-image:url(gradient.png); + background-image: -moz-linear-gradient(top, rgba(255,255,255,.75), rgba(255,255,255,0)); + background-image: -o-linear-gradient(top, rgba(255,255,255,.75), rgba(255,255,255,0)); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255,255,255,.75)), to(rgba(255,255,255,0))); + background-image: linear-gradient(top, rgba(255,255,255,.75), rgba(255,255,255,0)); + background-repeat:repeat-x; + text-shadow:1px 1px 0 rgba(255,255,255,.67); + line-height:2; + height:2em; + -moz-box-shadow:1px 1px 0 rgba(255,255,255,.5) inset, -1px -1px 0 rgba(255,255,255,.5) inset; + -webkit-box-shadow:1px 1px 0 rgba(255,255,255,.5) inset, -1px -1px 0 rgba(255,255,255,.5) inset; + box-shadow:1px 1px 0 rgba(255,255,255,.5) inset, -1px -1px 0 rgba(255,255,255,.5) inset; + -webkit-transition: background .185s linear; + -moz-transition: all .185s linear; + -o-transition: all .185s linear; + transition: all .185s linear; + /** Make the text unselectable **/ + -moz-user-select: none; + -webkit-user-select: none; +} +.button, .button:after, button, button:after, input[type=submit], input[type=button], ul.nav li { + -moz-border-radius:4px; + -webkit-border-radius:4px; + border-radius:4px; + border-width:1px; + border-style:solid; +} +.button:after, button:after { + display:block; + position:absolute; + width:100%; + height:100%; + border-color: transparent transparent #ccc; + border-color: transparent transparent rgba(255, 255, 255, 0.67); + bottom:-2px; + left:-1px; +} +.button:hover, .button:focus, button:hover, button:focus, input[type=button]:hover, input[type=button]:focus, input[type=submit]:hover, input[type=submit]:focus { + background-color:#a8c0cb; +} +.button:active, button:active, input[type=submit]:active, input[type=button]:active { + line-height:2.2; + -moz-box-shadow:0 .33em 1em rgba(0,0,0,.67) inset,1px 1px 0 rgba(255,255,255,.25) inset,-1px -1px 0 rgba(255,255,255,.25) inset; + -webkit-box-shadow:0 .33em 2em rgba(0,0,0,.67) inset,1px 1px 0 rgba(255,255,255,.25) inset,-1px -1px 0 rgba(255,255,255,.25) inset; + box-shadow:0 .33em 2em rgba(0,0,0,.67) inset,1px 1px 0 rgba(255,255,255,.25) inset,-1px -1px 0 rgba(255,255,255,.25) inset; + -webkit-transition: line-height .1s linear; + -moz-transition: all .1s linear; + -o-transition: all .1s linear; + transition: all .1s linear; +} +.button.bg, .button.bg:hover, .button.bg:focus, ul.nav li { + background-image:url(gradient.png); + background-image: -moz-linear-gradient(top, rgba(255,255,255,.75), rgba(255,255,255,0)); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255,255,255,.75)), to(rgba(255,255,255,0))); +} + +/* Put this inside a @media qualifier so Netscape 4 ignores it */ +@media screen, print { + /* Set printouts to landscape */ + @page {size: landscape} + + /* Turn off list bullets */ + ul.mktree li { list-style: none; } + /* Control how "spaced out" the tree is */ + ul.mktree, ul.mktree ul , ul.mktree li { margin-left:5px; padding:0px; } + /* Provide space for our own "bullet" inside the LI */ + ul.mktree li .bullet { padding-left: 15px; } + /* Show "bullets" in the links, depending on the class of the LI that the link's in */ + ul.mktree li.liOpen .bullet { cursor: pointer; background: url(minus.gif) center left no-repeat; } + ul.mktree li.liClosed .bullet { cursor: pointer; background: url(plus.gif) center left no-repeat; } + ul.mktree li.liBullet .bullet { cursor: default; background: url(bullet.gif) center left no-repeat; } + /* Sublists are visible or not based on class of parent LI */ + ul.mktree li.liOpen ul { display: block; } + ul.mktree li.liClosed ul { display: none; } + /* Format menu items differently depending on what level of the tree they are in */ + ul.mktree li { font-family: arial, helvetica; font-size: 11pt; font-weight: bold; } + ul.mktree a.DC { color: #000088; font-weight: bold; } + ul.mktree a.CONTAINER { color: #005500; } + ul.mktree a.ZONE { color: #330066; } + ul.mktree a.CABROW { color: #AA3300; } + ul.mktree a.RACK { color: #660000; } + ul.mktree a { text-decoration: none; white-space: pre;} + ul.mktree a:hover { color: red; } + ul.mktree li ul li { font-family: arial, helvetica; font-size: 11pt; font-weight: normal;} +} +@media print { + .noprint { display: none; } + .page { + page-break-after: always; + } +} +.meter-wrap{position: relative;background-color: lightgrey;overflow:hidden;} +.meter-wrap, .meter-value, .meter-text {width: 210px; height: 1.1em;} +.meter-text { + position: absolute; + top:0; left:0; + padding-top: 0px; + color: #000; + text-align: center; + width: 100%; +} +fieldset[name=pdu] > div > img { vertical-align: text-bottom; } + +/* Supplies */ +.supply .table > div:first-child > div {padding-bottom:0.5em;font-weight: bold;} +.supply .table > div > div {padding-right: 0.25em;} +.supply .table > div > div:first-child {width: 22px;} +.supply .table .quantity {text-align: center;} +.supply .table { margin-bottom: 2em; width: 100%;} +.supply .table select { width: 100%; } +.supply .table:first-child { margin-left: 25px; width: auto;} +.supply .table:first-child > div > div:first-child {width: auto;} +.supply #location {width: 97%;} + +.supply .table ~ .table { background-color: white; } +.supply .table ~ .table > div > div:first-child { width: auto; } +.supply .table ~ .table > div > div { padding: 3px; } + + +/* Installer */ +.installer ul li, ul.nav li{ + display: block; + padding: 1.5em; + background-color: lightGray; + border: 0px solid lightGray; +} +.installer ul li{border: 1px dashed darkGray;} +.installer #sidebar a, .nav a {text-decoration: none;} +.installer #sidebar a:hover li.active, .nav a:hover li.active {background-color: white;border-color: lightGray;} +.installer .active, .nav .active {background-color: white;border: 1px solid darkGray;} +.installer a.active span:first-child, .nav a.active span:first-child {background-position: -144px 0;} +.installer div.table > div > div + div {width: 300px;} +.installer .rights > div:nth-last-child(2) div {padding-top: 0;padding-bottom: 2em;text-align: left;} +.installer #configtabs div.table > div > div + div {width: auto;} +.installer .center #configtabs ~ div input {width: auto;} +div.page.installer {min-width: 1100px;} +div.page.installer .main{max-width: 850px;} + +.installer .ui-multiselect ul li { padding: 0.5em 0.5em 0.5em 20px; height: auto; line-height: inherit;} + + +/* Menu */ +ul.nav li {padding: .5em;} +.nav a:visited {color: #000000;} +#sidebar .nav li a { display: block;} +#sidebar .nav .ui-state-focus { + background: white; + border-color: black; + border-width: 1px; + border-style: solid; + margin: 0; +} + +.ui-state-active, +.ui-widget-content .ui-state-active, +.ui-widget-header .ui-state-active { + border: 1px solid #aaaaaa !important; + background: url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x !important; + font-weight: normal !important; + color: rgb(51, 51, 51) !important; +} +.ui-state-active a, +.ui-state-active a:link, +.ui-state-active a:visited { + color: #212121 !important; +} +.ui-state-active .ui-icon, .ui-button:active .ui-icon { + background-image: url("images/ui-icons_222222_256x240.png") !important; +} +.active.ui-state-focus, .active.ui-state-active { + margin: 0px !important; +} + +/* Search Export */ +div.center div table#export { margin: auto; max-width: none; } +#export_wrapper a.dt-button { margin-right: 0px; } + + +/* Paths */ +/* Paths form */ +fieldset.crit_busc {border: 1px solid grey; padding:0.5em; background-color: #EEEEEE;} +fieldset.crit_busc legend {background-color: white; padding:0.5em; border: 1px solid grey;} +table#crit_busc {border: 0px; background: transparent; padding:0.5em;} +table#crit_busc tr {border: 0px; background: transparent; padding:0.5em;} +table#crit_busc td {border: 0px; background: transparent; padding:0.5em;} + +table#parcheos {border: 3px outset; text-align: center; text-valign: center; max-width: 800px; margin-left: auto; margin-right: auto;} +table#parcheos tr {border: 0px;} +table#parcheos td {padding: 0px; border: 0px; vertical-align: top;} + +#parcheos .f-right {background: url("../images/a2f.png") no-repeat #FFF; width:25px;} +#parcheos .f-left {background: url("../images/a1f.png") no-repeat #FFF; width:25px;} +#parcheos .r-right {background: url("../images/a2r.png") no-repeat #FFF; width:25px;} +#parcheos .r-left {background: url("../images/a1r.png") no-repeat #FFF; width:25px;} + +#parcheos .base-f, #parcheos .base-r {background: url("../images/b0f.png") no-repeat top left #FFF; height: 5px; padding: 0px; border: 0px;} + +#parcheos .connection-f-1 {background: url("../images/b1f.png") no-repeat #FFF;} +#parcheos .connection-f-2 {background: url("../images/b2f.png") no-repeat #FFF; width:25px;} +#parcheos .connection-f-3 {background: url("../images/b3f.png") no-repeat #FFF; height:30px;} +#parcheos .connection-f-4 {background: url("../images/b4f.png") no-repeat top right #FFF; height:30px;} +#parcheos .connection-r-1 {background: url("../images/b1r.png") no-repeat #FFF;} +#parcheos .connection-r-2 {background: url("../images/b2r.png") no-repeat #FFF; width:25px;} +#parcheos .connection-r-3 {background: url("../images/b3r.png") no-repeat #FFF; height:30px;} +#parcheos .connection-r-4 {background: url("../images/b4r.png") no-repeat top right #FFF; height:30px;} + +table#parcheos table tr + tr > td + td{background-color:yellow;} +table#parcheos table {margin: 0px; border: 0px; border-collapse: collapse; text-align: left; vertical-align: middle; min-width: 50px; white-space: nowrap;} +table#parcheos table tr th {background-color: #DDDDDD; padding: 2px; border: 1px solid grey; text-align: left; border-collapse: collapse;} +table#parcheos table tr td {padding: 2px; border: 1px solid grey; text-align: left; border-collapse: collapse;} +table#parcheos tr td:first-child + td table {margin-left: auto;} + +p.errormsg {padding: 20px; background-color: #DDDDDD; font-size: 120%; font-weight: bold; color: red;} + +/* feature managementHDD */ +#addHDDModal { + display: none; + position: fixed; + z-index: 1000; + left: 0; top: 0; width: 100%; height: 100%; + background-color: rgba(0,0,0,0.5); + } + #addHDDModal .modal-content { + background-color: #fff; + margin: 10% auto; + padding: 20px; + border: 1px solid #888; + width: 400px; + border-radius: 8px; + } + + .responsive-table { +overflow-x: auto; +align: center; +} +.table2{ + margin: 0 auto; + width: 750px; + align: center; + table { + border-collapse: collapse; + border: 1px; + } + th, td { + padding: 1px; + text-align: center; + vertical-align: middle; + } + th { + background-color:rgba(242, 242, 242, 0.78); + } +} diff --git a/managementhdd.php b/managementhdd.php index 9e667784f..398c40571 100644 --- a/managementhdd.php +++ b/managementhdd.php @@ -1,7 +1,5 @@ DeviceID = $deviceID; -if (!$device->GetDevice()) { +$dev = new Device(); $dev->DeviceID = $DeviceID; +if (!$dev->GetDevice()) { echo __('Invalid DeviceID'); exit; } // Load Template $template = new DeviceTemplate(); -$template->TemplateID = $device->TemplateID; +$template->TemplateID = $dev->TemplateID; $template->GetTemplateByID(); $template->LoadHDDConfig(); @@ -32,10 +30,10 @@ exit; } // Get lists -$hddList = HDD::GetHDDByDevice($device->DeviceID); -$hddWaitList = HDD::GetPendingByDevice($device->DeviceID); -$hdddestroyedList = HDD::GetDestroyedHDDByDevice($device->DeviceID); -$hddSpareList = HDD::GetSpareHDDByDevice($device->DeviceID); +$hddList = HDD::GetHDDByDevice($dev->DeviceID); +$hddWaitList = HDD::GetPendingByDevice($dev->DeviceID); +$hdddestroyedList = HDD::GetDestroyedHDDByDevice($dev->DeviceID); +$hddSpareList = HDD::GetSpareHDDByDevice($dev->DeviceID); ?> @@ -49,67 +47,7 @@ @@ -118,22 +56,22 @@
-

Label, ENT_QUOTES, 'UTF-8'); ?>

+

Label, ENT_QUOTES, 'UTF-8'); ?>

- +

- +
- + - + @@ -142,32 +80,32 @@ $i = 1; foreach ($hddList as $hdd) { $id = (int)$hdd->HDDID; - echo ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''; + echo ' + + + + + + + + + '; $i++; } ?> @@ -178,16 +116,16 @@ +

-
#
'.$i.''. - ''. - ''. - ''. - ''. - '
'.$i.' + + + + + +
+
- @@ -202,7 +140,6 @@ echo ' - @@ -221,16 +158,13 @@

-

-
 
+

- -
#
'.$i.' '.htmlspecialchars($hdd->Label, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->SerialNo, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->Status, ENT_QUOTES, "UTF-8").' '.$hdd->DateWithdrawn.'
+
- @@ -244,7 +178,6 @@ $id = (int)$hdd->HDDID; echo ' - @@ -254,24 +187,61 @@ ?>
#
'.$i.' '.htmlspecialchars($hdd->Label, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->SerialNo, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->Status, ENT_QUOTES, "UTF-8").' '.$hdd->DateDestroyed.'
+ +

+ + + + + + + + + + + + +HDDID; + echo ' + + + + + + + '; + $i++; +} +?> + +
#
'.$i.' '.htmlspecialchars($hdd->SerialNo, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->Status, ENT_QUOTES, "UTF-8").' '.$hdd->DateWithdrawn.' + + + + +
- + 0){ - echo '
'; - echo '
'; - echo '
'; + if($DeviceID >0){ + echo '
+
+
+
'; } ?>
0){ - print " [ ".__("Return to Parent Device")." ]
\n"; - print " GetDeviceCabinetID()."\">[ ".__("Return to Navigator")." ]"; - print " GetDeviceDCID()."\">[ ".__("Return to Navigator")." ]"; + if($DeviceID>0){ + print " [ ".__("Return to Parent Device")." ]
\n"; + print " GetDeviceCabinetID()."\">[ ".__("Return to Navigator")." ]
\n"; + print " GetDeviceDCID()."\">[ ".__("Return to DC")." ]"; }else{ print "
[ ".__("Return index")." ]
"; } @@ -282,18 +252,16 @@ - - - - \ No newline at end of file diff --git a/report_hdd.php b/report_hdd.php index 48bfe338e..09d8ad2d4 100644 --- a/report_hdd.php +++ b/report_hdd.php @@ -5,7 +5,7 @@ date_default_timezone_set('Europe/Paris'); // Répertoire d’uploads -$uploadDir = __DIR__ . '/uploads'; +$uploadDir = __DIR__ . '/assets/uploads'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } @@ -91,7 +91,6 @@ h.HDDID, h.DeviceID, d.Label AS DeviceLabel, - h.Label, h.SerialNo, h.Status, h.Size, @@ -99,7 +98,6 @@ h.DateAdd, h.DateWithdrawn, h.DateDestroyed, - h.Note FROM fac_HDD h LEFT JOIN fac_Device d ON h.DeviceID = d.DeviceID " . ($where ? "WHERE " . implode(' AND ', $where) : "") . " @@ -121,7 +119,6 @@ $r['HDDID'], $r['DeviceID'], $r['DeviceLabel'], - $r['Label'], $r['SerialNo'], $r['Status'], $r['Size'], @@ -129,7 +126,6 @@ $r['DateAdd'], $r['DateWithdrawn'], $r['DateDestroyed'], - $r['Note'] ]; $escaped = array_map(function($v){ return str_replace(["\t","\r\n","\n"], [' ', ' ', ' '], $v); @@ -239,7 +235,6 @@ class="btn btn-success">Export XLS HDDID DeviceID Device Label - Label SerialNo Status Size @@ -247,7 +242,6 @@ class="btn btn-success">Export XLS DateAdd DateWithdrawn DateDestroyed - Note Preuve Sélection @@ -276,7 +270,6 @@ class="btn btn-success">Export XLS - @@ -284,7 +277,6 @@ class="btn btn-success">Export XLS - Voir diff --git a/savehdd.php b/savehdd.php index 561ef5f85..2cbce1a35 100644 --- a/savehdd.php +++ b/savehdd.php @@ -26,21 +26,17 @@ { // Création d’un nouveau HDD depuis le modal case $action === 'create_hdd_form': // Récupération et sanitation des champs - $label = $_POST['Label'] ?? ''; $serialNo = $_POST['SerialNo'] ?? ''; $typeMedia = $_POST['TypeMedia']?? ''; $size = intval($_POST['Size'] ?? 0); - $note = $_POST['Note'] ?? ''; // Création via instance pour inclure le champ Note $hdd = new HDD(); $hdd->DeviceID = $deviceID; - $hdd->Label = $label; $hdd->SerialNo = $serialNo; $hdd->Status = 'On'; $hdd->TypeMedia = $typeMedia; $hdd->Size = $size; - $hdd->Note = $note; $hdd->Create(); break; @@ -52,12 +48,10 @@ throw new Exception("HDDID {$id} introuvable."); } // Ne mettez à jour QUE ce qui vient du formulaire - $hdd->Label = $_POST['Label'][$id] ?? $hdd->Label; $hdd->SerialNo = $_POST['SerialNo'][$id] ?? $hdd->SerialNo; $hdd->Status = $_POST['Status'][$id] ?? $hdd->Status; $hdd->TypeMedia = $_POST['TypeMedia'][$id]?? $hdd->TypeMedia; $hdd->Size = intval($_POST['Size'][$id] ?? $hdd->Size); - $hdd->Note = $_POST['Note'][$id] ?? $hdd->Note; // Maintenant vous avez déjà StatusDestruction, Note, DateAdd, etc. $hdd->MakeSafe(); $hdd->Update(); @@ -112,6 +106,12 @@ HDD::MarkDestroyed(intval($id)); } break; + + case $action === "bulk_destroyFromActive": + foreach ($_POST['select_active'] ?? [] as $id) { + HDD::MarkDestroyed(intval($id)); + } + break; case $action === "export_list": // Export XLS complet en 3 feuilles From 28a6a130a6a0e959fb588cf963396b3da354ef2d Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Thu, 22 May 2025 17:56:53 +0200 Subject: [PATCH 15/25] feature_managementhdd --- assign_hdd.php | 24 --- report_hdd.php | 455 ++++++++++++++++++++------------------------- search_devices.php | 30 --- 3 files changed, 201 insertions(+), 308 deletions(-) delete mode 100644 assign_hdd.php delete mode 100644 search_devices.php diff --git a/assign_hdd.php b/assign_hdd.php deleted file mode 100644 index 9b09e7a0f..000000000 --- a/assign_hdd.php +++ /dev/null @@ -1,24 +0,0 @@ -false,'error'=>'']); - exit; -} - -$h = new hdd(); -$h->HDDID = $hddid; -if(!$h->GetHDD()){ - echo json_encode(['success'=>false,'error'=>'']); - exit; -} - -$h->DeviceID = $deviceid; -if($h->Save()){ - echo json_encode(['success'=>true]); -} else { - echo json_encode(['success'=>false,'error'=>'']); -} diff --git a/report_hdd.php b/report_hdd.php index 09d8ad2d4..c07a35370 100644 --- a/report_hdd.php +++ b/report_hdd.php @@ -1,10 +1,110 @@ = ?'; + $params[] = $DateAddFrom; +} +if ($DateAddTo) { + $where[] = 'h.DateAdd <= ?'; + $params[] = $DateAddTo; +} +if ($DateWithFrom) { + $where[] = 'h.DateWithdrawn >= ?'; + $params[] = $DateWithFrom; +} +if ($DateWithTo) { + $where[] = 'h.DateWithdrawn <= ?'; + $params[] = $DateWithTo; +} +if ($DateDestFrom) { + $where[] = 'h.DateDestroyed >= ?'; + $params[] = $DateDestFrom; +} +if ($DateDestTo) { + $where[] = 'h.DateDestroyed <= ?'; + $params[] = $DateDestTo; +} + +$sql = "SELECT h.*, d.Label AS DeviceLabel + FROM fac_HDD h + LEFT JOIN fac_Device d ON d.DeviceID = h.DeviceID"; +if ($where) { + $sql .= ' WHERE ' . implode(' AND ', $where); +} +$sql .= ' ORDER BY h.DateAdd DESC'; + +// Fetch rows +$stmt = $dbh->prepare($sql); +$stmt->execute($params); +$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + +// Convert to HDD objects +$hddList = array_map([HDD::class, 'RowToObject'], $rows); + +// 3) Handle XLS export +if (isset($_GET['export']) && $_GET['export'] === 'xls') { + header('Content-Type: application/vnd.ms-excel'); + header('Content-Disposition: attachment; filename="hdd_report.xls"'); + echo "" + . "" + . "" + . ""; + echo ""; + foreach ($rows as $i => $r) { + echo '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . ''; + } + echo '
#HDDIDDeviceIDDeviceLabelSerialNoStatusSizeTypeMediaDateAddDateWithdrawnDateDestroyed
' . ($i + 1) . '' . htmlspecialchars($r['HDDID'], ENT_QUOTES) . '' . htmlspecialchars($r['DeviceID'], ENT_QUOTES) . '' . htmlspecialchars($r['DeviceLabel'], ENT_QUOTES) . '' . htmlspecialchars($r['SerialNo'], ENT_QUOTES) . '' . htmlspecialchars($r['Status'], ENT_QUOTES) . '' . htmlspecialchars($r['Size'], ENT_QUOTES) . '' . htmlspecialchars($r['TypeMedia'], ENT_QUOTES) . '' . htmlspecialchars($r['DateAdd'], ENT_QUOTES) . '' . htmlspecialchars($r['DateWithdrawn'], ENT_QUOTES) . '' . htmlspecialchars($r['DateDestroyed'], ENT_QUOTES) . '
'; + exit; +} + +// 4) Prepare upload directory $uploadDir = __DIR__ . '/assets/uploads'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); @@ -14,282 +114,145 @@ $error = ''; $success = ''; -// 1) Traitement du POST “Mark Destroyed” -if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'mark_destroyed') { - $selected = $_POST['selected_hdd'] ?? []; - if (empty($selected)) { - $error = 'Aucun disque sélectionné.'; - } - if (empty($error)) { - if (!isset($_FILES['proof']) || $_FILES['proof']['error'] !== UPLOAD_ERR_OK) { - $error = 'Veuillez joindre la preuve de destruction (PDF).'; - } - } - if (empty($error)) { - $ext = pathinfo($_FILES['proof']['name'], PATHINFO_EXTENSION); - $ext = preg_replace('/[^a-z0-9]/i','',$ext); - $filename = 'proof_' . time() . '.' . $ext; - $dest = $uploadDir . '/' . $filename; - if (!move_uploaded_file($_FILES['proof']['tmp_name'], $dest)) { - $error = 'Échec de l’enregistrement du fichier.'; +// 5) Handle POST actions (mark destruction / attach proof) +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $action = $_POST['action'] ?? ''; + if ($action === 'mark_destroyed') { + $selected = $_POST['selected_hdd'] ?? []; + if (empty($selected)) { + $error = _('No disks selected for destruction.'); } else { - $stmt = $db->prepare(" - UPDATE fac_HDD - SET Status = 'Destroyed', - DateDestroyed = NOW(), - ProofDocument = ? - WHERE HDDID = ? - "); foreach ($selected as $hid) { - $hddid = intval($hid); - $stmt->bind_param('si', $filename, $hddid); - $stmt->execute(); + HDD::MarkDestroyed((int)$hid); } - $success = 'Disques marqués « Destroyed » avec preuve enregistrée.'; + $success = _('Selected disks have been marked as Destroyed.'); } } -} - -// 2) Traitement de l’export XLS -if (isset($_GET['export']) && $_GET['export'] === 'xls') { - // Récupération des filtres GET - $statusFilter = $_GET['status'] ?? ''; - $DateAddFrom = $_GET['DateAddFrom'] ?? ''; - $DateAddTo = $_GET['DateAddTo'] ?? ''; - $DateWithdrawnFrom = $_GET['DateWithdrawnFrom'] ?? ''; - $DateWithdrawnTo = $_GET['DateWithdrawnTo'] ?? ''; - $DateDestroyedFrom = $_GET['DateDestroyedFrom'] ?? ''; - $DateDestroyedTo = $_GET['DateDestroyedTo'] ?? ''; - - // Construction dynamique du WHERE - $where = []; $types = ''; $params = []; - if ($statusFilter && $statusFilter !== 'All') { - $where[] = 'h.Status = ?'; $types .= 's'; $params[] = $statusFilter; - } - if ($DateAddFrom) { - $where[] = 'h.DateAdd >= ?'; $types .= 's'; $params[] = $DateAddFrom; - } - if ($DateAddTo) { - $where[] = 'h.DateAdd <= ?'; $types .= 's'; $params[] = $DateAddTo; - } - if ($DateWithdrawnFrom) { - $where[] = 'h.DateWithdrawn >= ?'; $types .= 's'; $params[] = $DateWithdrawnFrom; - } - if ($DateWithdrawnTo) { - $where[] = 'h.DateWithdrawn <= ?'; $types .= 's'; $params[] = $DateWithdrawnTo; - } - if ($DateDestroyedFrom) { - $where[] = 'h.DateDestroyed >= ?'; $types .= 's'; $params[] = $DateDestroyedFrom; - } - if ($DateDestroyedTo) { - $where[] = 'h.DateDestroyed <= ?'; $types .= 's'; $params[] = $DateDestroyedTo; - } - - // Requête d’export - $sql = " - SELECT - h.HDDID, - h.DeviceID, - d.Label AS DeviceLabel, - h.SerialNo, - h.Status, - h.Size, - h.TypeMedia, - h.DateAdd, - h.DateWithdrawn, - h.DateDestroyed, - FROM fac_HDD h - LEFT JOIN fac_Device d ON h.DeviceID = d.DeviceID - " . ($where ? "WHERE " . implode(' AND ', $where) : "") . " - ORDER BY h.HDDID - "; - $stmt = $db->prepare($sql); - if ($where) { - $stmt->bind_param($types, ...$params); - } - $stmt->execute(); - $res = $stmt->get_result(); - - // Envoi du XLS - header('Content-Type: application/vnd.ms-excel; charset=UTF-8'); - header('Content-Disposition: attachment; filename="report_hdd.xls"'); - echo "HDDID\tDeviceID\tDeviceLabel\tLabel\tSerialNo\tStatus\tSize\tTypeMedia\tDateAdd\tDateWithdrawn\tDateDestroyed\tNote\n"; - while ($r = $res->fetch_assoc()) { - $cols = [ - $r['HDDID'], - $r['DeviceID'], - $r['DeviceLabel'], - $r['SerialNo'], - $r['Status'], - $r['Size'], - $r['TypeMedia'], - $r['DateAdd'], - $r['DateWithdrawn'], - $r['DateDestroyed'], - ]; - $escaped = array_map(function($v){ - return str_replace(["\t","\r\n","\n"], [' ', ' ', ' '], $v); - }, $cols); - echo implode("\t", $escaped) . "\n"; + elseif ($action === 'attach_proof') { + $proofIds = $_POST['selected_hdd_proof'] ?? []; + if (empty($proofIds)) { + $error = _('No disks selected for proof attachment.'); + } else { + // Verify all are already destroyed + $notDestroyed = array_filter($hddList, function($h) use ($proofIds) { + return in_array($h->HDDID, $proofIds, true) && $h->Status !== 'Destroyed'; + }); + if ($notDestroyed) { + $error = _('All selected disks must have status "Destroyed" to attach proof.'); + } elseif (!isset($_FILES['proof']) || $_FILES['proof']['error'] !== UPLOAD_ERR_OK) { + $error = _('Please attach a proof document (PDF).'); + } else { + $ext = pathinfo($_FILES['proof']['name'], PATHINFO_EXTENSION); + $ext = preg_replace('/[^a-z0-9]/i', '', $ext); + $filename = 'proof_' . time() . '.' . $ext; + $dest = $uploadDir . '/' . $filename; + if (!move_uploaded_file($_FILES['proof']['tmp_name'], $dest)) { + $error = _('Failed to save the proof document.'); + } else { + $stmt = $dbh->prepare( + "UPDATE fac_HDD SET ProofDocument = ? WHERE HDDID = ?" + ); + foreach ($proofIds as $hid) { + $stmt->execute([$filename, (int)$hid]); + } + $success = _('Proof document attached to selected destroyed disks.'); + } + } + } } - exit; -} - -// 3) Lecture des filtres GET pour l’affichage -$statusFilter = $_GET['status'] ?? ''; -$DateAddFrom = $_GET['DateAddFrom'] ?? ''; -$DateAddTo = $_GET['DateAddTo'] ?? ''; -$DateWithdrawnFrom = $_GET['DateWithdrawnFrom'] ?? ''; -$DateWithdrawnTo = $_GET['DateWithdrawnTo'] ?? ''; -$DateDestroyedFrom = $_GET['DateDestroyedFrom'] ?? ''; -$DateDestroyedTo = $_GET['DateDestroyedTo'] ?? ''; - -// 4) Construction dynamique du WHERE pour l’affichage -$where = []; $types = ''; $params = []; -if ($statusFilter && $statusFilter !== 'All') { - $where[] = 'h.Status = ?'; $types .= 's'; $params[] = $statusFilter; -} -if ($DateAddFrom) { - $where[] = 'h.DateAdd >= ?'; $types .= 's'; $params[] = $DateAddFrom; -} -if ($DateAddTo) { - $where[] = 'h.DateAdd <= ?'; $types .= 's'; $params[] = $DateAddTo; } -if ($DateWithdrawnFrom) { - $where[] = 'h.DateWithdrawn >= ?'; $types .= 's'; $params[] = $DateWithdrawnFrom; -} -if ($DateWithdrawnTo) { - $where[] = 'h.DateWithdrawn <= ?'; $types .= 's'; $params[] = $DateWithdrawnTo; -} -if ($DateDestroyedFrom) { - $where[] = 'h.DateDestroyed >= ?'; $types .= 's'; $params[] = $DateDestroyedFrom; -} -if ($DateDestroyedTo) { - $where[] = 'h.DateDestroyed <= ?'; $types .= 's'; $params[] = $DateDestroyedTo; -} - -// 5) Requête principale -$sql = " - SELECT - h.*, - d.Label AS DeviceLabel - FROM fac_HDD h - LEFT JOIN fac_Device d ON h.DeviceID = d.DeviceID - " . ($where ? "WHERE " . implode(' AND ', $where) : "") . " - ORDER BY h.HDDID -"; -$stmt = $db->prepare($sql); -if ($where) { - $stmt->bind_param($types, ...$params); -} -$stmt->execute(); -$res = $stmt->get_result(); -$hdds = $res->fetch_all(MYSQLI_ASSOC); ?> - + - Report HDD + HDD Report - +
-

Report des HDD

- +

HDD Report

-
- +
+
+ + +
+
+ + +
- - Export XLS + + Export XLS
- - - - - - - - - - - - - - + + + + - - '; ?> + '; ?> - + - + - - - - - - - - - - + + + + + + + + + + - @@ -298,37 +261,21 @@ class="btn btn-success">Export XLS
#HDDIDDeviceIDDevice LabelSerialNoStatusSizeTypeDateAddDateWithdrawnDateDestroyedPreuveSélection#HDDIDDeviceIDDevice LabelSerialNoStatusSizeTypeDateAddDateWithdrawnDateDestroyedProofAction
- - + + - - + + - - + +
HDDID, ENT_QUOTES) ?>DeviceID, ENT_QUOTES) ?>DeviceLabel, ENT_QUOTES) ?>SerialNo, ENT_QUOTES) ?>Status, ENT_QUOTES) ?>Size, ENT_QUOTES) ?>TypeMedia, ENT_QUOTES) ?>DateAdd, ENT_QUOTES) ?>DateWithdrawn, ENT_QUOTES) ?>DateDestroyed, ENT_QUOTES) ?> - - Voir + ProofDocument)): ?> + View - - + + Status === 'Pending_destruction'): ?> + + Status === 'Destroyed'): ?> +
- -
+ + + +
+ + +
- - + +
- +
-
- +
diff --git a/search_devices.php b/search_devices.php deleted file mode 100644 index a0aef75a7..000000000 --- a/search_devices.php +++ /dev/null @@ -1,30 +0,0 @@ -= 2) { - $sql = " - SELECT - d.DeviceID, - d.Label AS Name - FROM fac_Device d - JOIN fac_DeviceTemplate t ON d.TemplateID = t.TemplateID - JOIN fac_DeviceTemplateHdd dth ON t.TemplateID = dth.TemplateID - WHERE dth.EnableHDDFeature = 1 -- flag HDD activé - AND d.Label LIKE ? -- on cherche sur d.Label, pas e.Label - ORDER BY d.Label -- idem pour ORDER BY - LIMIT 20 - "; - $stmt = $db->prepare($sql); - $like = "%{$q}%"; - $stmt->bind_param('s', $like); - $stmt->execute(); - $res = $stmt->get_result(); - while ($r = $res->fetch_assoc()) { - $out[] = $r; - } -} - -header('Content-Type: application/json'); -echo json_encode($out); From 1b584ababe7a250949cda6d8fa2beed92871ea82 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Thu, 22 May 2025 20:15:38 +0200 Subject: [PATCH 16/25] feature_managementhdd --- report_hdd.php | 215 ++++++++++++++++++++++++++++--------------------- 1 file changed, 124 insertions(+), 91 deletions(-) diff --git a/report_hdd.php b/report_hdd.php index c07a35370..956cde0ee 100644 --- a/report_hdd.php +++ b/report_hdd.php @@ -7,59 +7,68 @@ require_once __DIR__ . '/db.inc.php'; // $dbh as PDO require_once __DIR__ . '/facilities.inc.php'; require_once __DIR__ . '/classes/hdd.class.php'; -use Classes\HDD; +// Access control +if (! $person->ManageHDD) { + header("Location: index.php"); + exit; +} + +// Subheader for template +$subheader = __("HDD Management Report"); + // 1) Get filters from GET $statusFilter = $_GET['status'] ?? 'All'; $serialSearch = $_GET['serial'] ?? ''; $deviceID = (int)($_GET['deviceID'] ?? 0); -$DateAddFrom = $_GET['DateAddFrom'] ?? ''; -$DateAddTo = $_GET['DateAddTo'] ?? ''; -$DateWithFrom = $_GET['DateWithdrawnFrom'] ?? ''; -$DateWithTo = $_GET['DateWithdrawnTo'] ?? ''; -$DateDestFrom = $_GET['DateDestroyedFrom'] ?? ''; -$DateDestTo = $_GET['DateDestroyedTo'] ?? ''; +$DateAddFrom = $_GET['DateAddFrom'] ?? ''; +$DateAddTo = $_GET['DateAddTo'] ?? ''; +$DateWithFrom = $_GET['DateWithdrawnFrom'] ?? ''; +$DateWithTo = $_GET['DateWithdrawnTo'] ?? ''; +$DateDestFrom = $_GET['DateDestroyedFrom'] ?? ''; +$DateDestTo = $_GET['DateDestroyedTo'] ?? ''; // 2) Build SQL filters $where = []; $params = []; if ($statusFilter !== 'All') { - $where[] = 'h.Status = ?'; - $params[] = $statusFilter; + $where[] = 'h.Status = ?'; + $params[] = $statusFilter; } if ($serialSearch !== '') { - $where[] = 'h.SerialNo LIKE ?'; - $params[] = "%{$serialSearch}%"; + $where[] = 'h.SerialNo LIKE ?'; + $params[] = "%{$serialSearch}%"; } if ($deviceID) { - $where[] = 'h.DeviceID = ?'; - $params[] = $deviceID; + $where[] = 'h.DeviceID = ?'; + $params[] = $deviceID; } if ($DateAddFrom) { - $where[] = 'h.DateAdd >= ?'; - $params[] = $DateAddFrom; + $where[] = 'h.DateAdd >= ?'; + $params[] = $DateAddFrom; } if ($DateAddTo) { - $where[] = 'h.DateAdd <= ?'; - $params[] = $DateAddTo; + $where[] = 'h.DateAdd <= ?'; + $params[] = $DateAddTo; } if ($DateWithFrom) { - $where[] = 'h.DateWithdrawn >= ?'; - $params[] = $DateWithFrom; + $where[] = 'h.DateWithdrawn >= ?'; + $params[] = $DateWithFrom; } if ($DateWithTo) { - $where[] = 'h.DateWithdrawn <= ?'; - $params[] = $DateWithTo; + $where[] = 'h.DateWithdrawn <= ?'; + $params[] = $DateWithTo; } if ($DateDestFrom) { - $where[] = 'h.DateDestroyed >= ?'; - $params[] = $DateDestFrom; + $where[] = 'h.DateDestroyed >= ?'; + $params[] = $DateDestFrom; } if ($DateDestTo) { - $where[] = 'h.DateDestroyed <= ?'; - $params[] = $DateDestTo; + $where[] = 'h.DateDestroyed <= ?'; + $params[] = $DateDestTo; } +// 3) Query database $sql = "SELECT h.*, d.Label AS DeviceLabel FROM fac_HDD h LEFT JOIN fac_Device d ON d.DeviceID = h.DeviceID"; @@ -68,94 +77,89 @@ } $sql .= ' ORDER BY h.DateAdd DESC'; -// Fetch rows $stmt = $dbh->prepare($sql); $stmt->execute($params); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); +$hddList = array_map(function(array $r) { + $h = HDD::RowToObject($r); + // Injecte ici le label de l’appareil parent + $h->DeviceLabel = $r['DeviceLabel'] ?? ''; + return $h; +}, $rows); -// Convert to HDD objects -$hddList = array_map([HDD::class, 'RowToObject'], $rows); - -// 3) Handle XLS export +// 4) Handle XLS export if (isset($_GET['export']) && $_GET['export'] === 'xls') { header('Content-Type: application/vnd.ms-excel'); header('Content-Disposition: attachment; filename="hdd_report.xls"'); echo "" . "" . "" - . ""; - echo ""; + . ""; foreach ($rows as $i => $r) { echo '' . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' + . '' . ''; } echo '
#HDDIDDeviceIDDeviceLabelSerialNoStatusSizeTypeMediaDateAddDateWithdrawnDateDestroyed
DateAddDateWithdrawnDateDestroyed
' . ($i + 1) . '' . htmlspecialchars($r['HDDID'], ENT_QUOTES) . '' . htmlspecialchars($r['DeviceID'], ENT_QUOTES) . '' . htmlspecialchars($r['DeviceLabel'], ENT_QUOTES) . '' . htmlspecialchars($r['SerialNo'], ENT_QUOTES) . '' . htmlspecialchars($r['Status'], ENT_QUOTES) . '' . htmlspecialchars($r['Size'], ENT_QUOTES) . '' . htmlspecialchars($r['TypeMedia'], ENT_QUOTES) . '' . htmlspecialchars($r['DateAdd'], ENT_QUOTES) . '' . htmlspecialchars($r['DateWithdrawn'], ENT_QUOTES) . '' . htmlspecialchars($r['DateDestroyed'], ENT_QUOTES) . '' . htmlspecialchars($r['HDDID'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['DeviceID'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['DeviceLabel'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['SerialNo'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['Status'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['Size'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['TypeMedia'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['DateAdd'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['DateWithdrawn'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['DateDestroyed'] ?? '', ENT_QUOTES) . '
'; exit; } -// 4) Prepare upload directory +// 5) Prepare upload directory $uploadDir = __DIR__ . '/assets/uploads'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } -// Messages -$error = ''; +// 6) Handle POST actions +$error = ''; $success = ''; - -// 5) Handle POST actions (mark destruction / attach proof) if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? ''; if ($action === 'mark_destroyed') { $selected = $_POST['selected_hdd'] ?? []; if (empty($selected)) { - $error = _('No disks selected for destruction.'); + $error = __('No disks selected for destruction.'); } else { foreach ($selected as $hid) { HDD::MarkDestroyed((int)$hid); } - $success = _('Selected disks have been marked as Destroyed.'); + $success = __('Selected disks have been marked as Destroyed.'); } - } - elseif ($action === 'attach_proof') { + } elseif ($action === 'attach_proof') { $proofIds = $_POST['selected_hdd_proof'] ?? []; if (empty($proofIds)) { - $error = _('No disks selected for proof attachment.'); + $error = __('No disks selected for proof attachment.'); } else { - // Verify all are already destroyed - $notDestroyed = array_filter($hddList, function($h) use ($proofIds) { - return in_array($h->HDDID, $proofIds, true) && $h->Status !== 'Destroyed'; + $notDestroyed = array_filter($rows, function($r) use ($proofIds) { + return in_array($r['HDDID'], $proofIds, true) && $r['Status'] !== 'Destroyed'; }); if ($notDestroyed) { - $error = _('All selected disks must have status "Destroyed" to attach proof.'); + $error = __('All selected disks must have status "Destroyed" to attach proof.'); } elseif (!isset($_FILES['proof']) || $_FILES['proof']['error'] !== UPLOAD_ERR_OK) { - $error = _('Please attach a proof document (PDF).'); + $error = __('Please attach a proof document (PDF).'); } else { $ext = pathinfo($_FILES['proof']['name'], PATHINFO_EXTENSION); $ext = preg_replace('/[^a-z0-9]/i', '', $ext); $filename = 'proof_' . time() . '.' . $ext; $dest = $uploadDir . '/' . $filename; if (!move_uploaded_file($_FILES['proof']['tmp_name'], $dest)) { - $error = _('Failed to save the proof document.'); + $error = __('Failed to save the proof document.'); } else { - $stmt = $dbh->prepare( - "UPDATE fac_HDD SET ProofDocument = ? WHERE HDDID = ?" - ); + $stmt = $dbh->prepare("UPDATE fac_HDD SET ProofDocument = ? WHERE HDDID = ?"); foreach ($proofIds as $hid) { $stmt->execute([$filename, (int)$hid]); } - $success = _('Proof document attached to selected destroyed disks.'); + $success = __('Proof document attached to selected destroyed disks.'); } } } @@ -166,24 +170,30 @@ - HDD Report - - + <?php echo htmlspecialchars(__('Manage HDDs report'), ENT_QUOTES); ?> + + + + - -
-

HDD Report

+ + +
+ +
+
+

-
+
-
+
- - @@ -191,16 +201,16 @@
- +
- +
- - Export XLS + +
@@ -213,7 +223,7 @@ ProofAction - '; ?> + '; ?> @@ -230,19 +240,19 @@ - + - HDDID, ENT_QUOTES) ?> - DeviceID, ENT_QUOTES) ?> - DeviceLabel, ENT_QUOTES) ?> - SerialNo, ENT_QUOTES) ?> - Status, ENT_QUOTES) ?> - Size, ENT_QUOTES) ?> - TypeMedia, ENT_QUOTES) ?> - DateAdd, ENT_QUOTES) ?> - DateWithdrawn, ENT_QUOTES) ?> - DateDestroyed, ENT_QUOTES) ?> + HDDID ?? '', ENT_QUOTES) ?> + DeviceID ?? '', ENT_QUOTES) ?> + DeviceLabel ?? '', ENT_QUOTES) ?> + SerialNo ?? '', ENT_QUOTES) ?> + Status ?? '', ENT_QUOTES) ?> + Size ?? '', ENT_QUOTES) ?> + TypeMedia ?? '', ENT_QUOTES) ?> + DateAdd ?? '', ENT_QUOTES) ?> + DateWithdrawn ?? '', ENT_QUOTES) ?> + DateDestroyed ?? '', ENT_QUOTES) ?> ProofDocument)): ?> View @@ -261,18 +271,41 @@
+ $statusFilter, + 'serial' => $serialSearch, + 'deviceID' => $deviceID, + 'DateAddFrom' => $DateAddFrom, + 'DateAddTo' => $DateAddTo, + 'DateWithdrawnFrom' => $DateWithFrom, + 'DateWithdrawnTo' => $DateWithTo, + 'DateDestroyedFrom' => $DateDestFrom, + 'DateDestroyedTo' => $DateDestTo, + ]; +?>
- + $value): ?> + + +
+ + $value): ?> + +
- +
- +
From 9baaadc7975f44da0c7fab72a4c3f40236328956 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Fri, 23 May 2025 12:27:22 +0200 Subject: [PATCH 17/25] feature_managementhdd --- report_hdd.php | 406 +++++++++++++++---------------------------------- 1 file changed, 119 insertions(+), 287 deletions(-) diff --git a/report_hdd.php b/report_hdd.php index 956cde0ee..44904c230 100644 --- a/report_hdd.php +++ b/report_hdd.php @@ -1,315 +1,147 @@ ManageHDD) { - header("Location: index.php"); +if (!($person->ManageHDD || $person->SiteAdmin || $person->ReadAccess)) { + echo __('This report requires global read access.'); + header('Refresh: 3; url=' . redirect()); exit; } // Subheader for template -$subheader = __("HDD Management Report"); - -// 1) Get filters from GET -$statusFilter = $_GET['status'] ?? 'All'; -$serialSearch = $_GET['serial'] ?? ''; -$deviceID = (int)($_GET['deviceID'] ?? 0); -$DateAddFrom = $_GET['DateAddFrom'] ?? ''; -$DateAddTo = $_GET['DateAddTo'] ?? ''; -$DateWithFrom = $_GET['DateWithdrawnFrom'] ?? ''; -$DateWithTo = $_GET['DateWithdrawnTo'] ?? ''; -$DateDestFrom = $_GET['DateDestroyedFrom'] ?? ''; -$DateDestTo = $_GET['DateDestroyedTo'] ?? ''; +$subheader = __('HDD Management Report'); -// 2) Build SQL filters -$where = []; -$params = []; -if ($statusFilter !== 'All') { - $where[] = 'h.Status = ?'; - $params[] = $statusFilter; -} -if ($serialSearch !== '') { - $where[] = 'h.SerialNo LIKE ?'; - $params[] = "%{$serialSearch}%"; -} -if ($deviceID) { - $where[] = 'h.DeviceID = ?'; - $params[] = $deviceID; -} -if ($DateAddFrom) { - $where[] = 'h.DateAdd >= ?'; - $params[] = $DateAddFrom; -} -if ($DateAddTo) { - $where[] = 'h.DateAdd <= ?'; - $params[] = $DateAddTo; -} -if ($DateWithFrom) { - $where[] = 'h.DateWithdrawn >= ?'; - $params[] = $DateWithFrom; -} -if ($DateWithTo) { - $where[] = 'h.DateWithdrawn <= ?'; - $params[] = $DateWithTo; -} -if ($DateDestFrom) { - $where[] = 'h.DateDestroyed >= ?'; - $params[] = $DateDestFrom; -} -if ($DateDestTo) { - $where[] = 'h.DateDestroyed <= ?'; - $params[] = $DateDestTo; -} - -// 3) Query database -$sql = "SELECT h.*, d.Label AS DeviceLabel - FROM fac_HDD h - LEFT JOIN fac_Device d ON d.DeviceID = h.DeviceID"; -if ($where) { - $sql .= ' WHERE ' . implode(' AND ', $where); -} -$sql .= ' ORDER BY h.DateAdd DESC'; +// 1) Build SQL: include Device Label via JOIN +$sql = " + SELECT + h.HDDID, + h.DeviceID, + d.Label AS DeviceLabel, + h.SerialNo, + h.Status, + h.Size, + h.TypeMedia, + h.DateAdd, + h.DateWithdrawn, + h.DateDestroyed, + h.ProofDocument + FROM fac_HDD h + LEFT JOIN fac_Device d ON d.DeviceID = h.DeviceID +"; +// Execute query $stmt = $dbh->prepare($sql); -$stmt->execute($params); -$rows = $stmt->fetchAll(PDO::FETCH_ASSOC); -$hddList = array_map(function(array $r) { - $h = HDD::RowToObject($r); - // Injecte ici le label de l’appareil parent - $h->DeviceLabel = $r['DeviceLabel'] ?? ''; - return $h; -}, $rows); - -// 4) Handle XLS export -if (isset($_GET['export']) && $_GET['export'] === 'xls') { - header('Content-Type: application/vnd.ms-excel'); - header('Content-Disposition: attachment; filename="hdd_report.xls"'); - echo "" - . "" - . "" - . ""; - foreach ($rows as $i => $r) { - echo '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . '' - . ''; - } - echo '
#HDDIDDeviceIDDeviceLabelSerialNoStatusSizeTypeMediaDateAddDateWithdrawnDateDestroyed
' . ($i + 1) . '' . htmlspecialchars($r['HDDID'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['DeviceID'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['DeviceLabel'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['SerialNo'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['Status'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['Size'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['TypeMedia'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['DateAdd'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['DateWithdrawn'] ?? '', ENT_QUOTES) . '' . htmlspecialchars($r['DateDestroyed'] ?? '', ENT_QUOTES) . '
'; - exit; -} - -// 5) Prepare upload directory -$uploadDir = __DIR__ . '/assets/uploads'; -if (!is_dir($uploadDir)) { - mkdir($uploadDir, 0755, true); -} - -// 6) Handle POST actions -$error = ''; -$success = ''; -if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $action = $_POST['action'] ?? ''; - if ($action === 'mark_destroyed') { - $selected = $_POST['selected_hdd'] ?? []; - if (empty($selected)) { - $error = __('No disks selected for destruction.'); - } else { - foreach ($selected as $hid) { - HDD::MarkDestroyed((int)$hid); - } - $success = __('Selected disks have been marked as Destroyed.'); - } - } elseif ($action === 'attach_proof') { - $proofIds = $_POST['selected_hdd_proof'] ?? []; - if (empty($proofIds)) { - $error = __('No disks selected for proof attachment.'); - } else { - $notDestroyed = array_filter($rows, function($r) use ($proofIds) { - return in_array($r['HDDID'], $proofIds, true) && $r['Status'] !== 'Destroyed'; - }); - if ($notDestroyed) { - $error = __('All selected disks must have status "Destroyed" to attach proof.'); - } elseif (!isset($_FILES['proof']) || $_FILES['proof']['error'] !== UPLOAD_ERR_OK) { - $error = __('Please attach a proof document (PDF).'); - } else { - $ext = pathinfo($_FILES['proof']['name'], PATHINFO_EXTENSION); - $ext = preg_replace('/[^a-z0-9]/i', '', $ext); - $filename = 'proof_' . time() . '.' . $ext; - $dest = $uploadDir . '/' . $filename; - if (!move_uploaded_file($_FILES['proof']['tmp_name'], $dest)) { - $error = __('Failed to save the proof document.'); - } else { - $stmt = $dbh->prepare("UPDATE fac_HDD SET ProofDocument = ? WHERE HDDID = ?"); - foreach ($proofIds as $hid) { - $stmt->execute([$filename, (int)$hid]); - } - $success = __('Proof document attached to selected destroyed disks.'); - } - } - } - } -} +$stmt->execute(); +$hddList = $stmt->fetchAll(PDO::FETCH_OBJ); ?> - + - <?php echo htmlspecialchars(__('Manage HDDs report'), ENT_QUOTES); ?> - - - - + <?php echo htmlspecialchars($subheader, ENT_QUOTES); ?> + + + + + + + + + + + + + - -
- -
-
-

- -
- -
- +
+ +
+
+

-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
+ + - - - - - - - - - - '; ?> - - - - - - - - - - - - - - - - - - - - - - - - - -
#HDDIDDeviceIDDevice LabelSerialNoStatusSizeTypeDateAddDateWithdrawnDateDestroyedProofAction
- - - - - - - - -
HDDID ?? '', ENT_QUOTES) ?>DeviceID ?? '', ENT_QUOTES) ?>DeviceLabel ?? '', ENT_QUOTES) ?>SerialNo ?? '', ENT_QUOTES) ?>Status ?? '', ENT_QUOTES) ?>Size ?? '', ENT_QUOTES) ?>TypeMedia ?? '', ENT_QUOTES) ?>DateAdd ?? '', ENT_QUOTES) ?>DateWithdrawn ?? '', ENT_QUOTES) ?>DateDestroyed ?? '', ENT_QUOTES) ?> - ProofDocument)): ?> - View - - - Status === 'Pending_destruction'): ?> - - Status === 'Destroyed'): ?> - - -
-
- - $statusFilter, - 'serial' => $serialSearch, - 'deviceID' => $deviceID, - 'DateAddFrom' => $DateAddFrom, - 'DateAddTo' => $DateAddTo, - 'DateWithdrawnFrom' => $DateWithFrom, - 'DateWithdrawnTo' => $DateWithTo, - 'DateDestroyedFrom' => $DateDestFrom, - 'DateDestroyedTo' => $DateDestTo, - ]; -?> - -
- $value): ?> - - - -
- - -
- - $value): ?> - - -
- -
- -
- +
- - + + - + \ No newline at end of file From c6dad3cdc73b7aea1af366d9e5d1c608131e7fa8 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Fri, 19 Sep 2025 12:02:48 +0200 Subject: [PATCH 18/25] =?UTF-8?q?feat(report=5Fhdd):=20sauvegarde=20des=20?= =?UTF-8?q?modifs=20avant=20cr=C3=A9ation=20de=20la=20nouvelle=20branche?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- report_hdd.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/report_hdd.php b/report_hdd.php index 44904c230..c3c56655c 100644 --- a/report_hdd.php +++ b/report_hdd.php @@ -97,10 +97,10 @@ function redraw(){ - - + + @@ -113,19 +113,16 @@ function redraw(){ - - + + - - + + - - + - + - - - + + diff --git a/import_hdd_csv.php b/import_hdd_csv.php new file mode 100644 index 000000000..f8ac78260 --- /dev/null +++ b/import_hdd_csv.php @@ -0,0 +1,256 @@ +ManageHDD) { + http_response_code(403); + echo json_encode(['success' => false, 'error' => __('Permission denied')]); + exit; +} + +$sendJson = function(array $payload, int $status = 200) { + http_response_code($status); + header('Content-Type: application/json'); + echo json_encode($payload); + exit; +}; + +$detectDelimiter = function(string $line): string { + $candidates = [',', ';', "\t", '|']; + $best = ','; + $max = 0; + foreach ($candidates as $delim) { + $count = substr_count($line, $delim); + if ($count > $max) { + $max = $count; + $best = $delim; + } + } + return $best; +}; + +try { + $force = !empty($_POST['force']); + $columnName = trim($_POST['csv_column'] ?? ''); + if ($columnName === '') { + throw new Exception(__('Please select the CSV column containing serial numbers')); + } + + if (!isset($_FILES['batch_csv'])) { + throw new Exception(__('CSV file is required')); + } + + if ($_FILES['batch_csv']['error'] !== UPLOAD_ERR_OK) { + throw new Exception(__('Error while uploading the CSV file')); + } + + if ($_FILES['batch_csv']['size'] > 2 * 1024 * 1024) { + throw new Exception(__('CSV file is too large (max 2 MB)')); + } + + $csvTmp = $_FILES['batch_csv']['tmp_name']; + $lines = file($csvTmp, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if ($lines === false || count($lines) === 0) { + throw new Exception(__('Unable to read CSV file or file is empty')); + } + + $headerLine = $lines[0]; + $delimiter = $detectDelimiter($headerLine); + $headers = str_getcsv($headerLine, $delimiter); + $headersNormalized = array_map(function($h) { + return strtolower(trim($h)); + }, $headers); + + $targetIndex = array_search(strtolower($columnName), $headersNormalized, true); + if ($targetIndex === false) { + throw new Exception(__('Selected column was not found in the CSV header')); + } + + $serials = []; + for ($i = 1; $i < count($lines); $i++) { + $row = str_getcsv($lines[$i], $delimiter); + if (!isset($row[$targetIndex])) { + continue; + } + $sn = trim($row[$targetIndex]); + if ($sn !== '') { + $serials[] = $sn; + } + } + $serials = array_values(array_unique($serials)); + if (empty($serials)) { + throw new Exception(__('No serial numbers were found in the selected column')); + } + + $placeholders = implode(',', array_fill(0, count($serials), '?')); + $stmt = $dbh->prepare("SELECT HDDID, SerialNo, Status, DateDestroyed FROM fac_HDD WHERE SerialNo IN ($placeholders)"); + $stmt->execute($serials); + $foundActive = []; + $alreadyProcessed = []; + while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $serial = $row['SerialNo']; + $status = strtolower($row['Status']); + $isDestroyed = ($status === 'destroyed') || (!empty($row['DateDestroyed'])); + if ($isDestroyed) { + $alreadyProcessed[$serial] = (int)$row['HDDID']; + } else { + $foundActive[$serial] = (int)$row['HDDID']; + } + } + + $missing = array_values(array_diff($serials, array_keys($foundActive), array_keys($alreadyProcessed))); + $recognizedIds = array_values($foundActive); + $recognizedSerials = array_keys($foundActive); + $alreadyProcessedSerials = array_keys($alreadyProcessed); + + if (!empty($missing) && !$force) { + $sendJson([ + 'require_confirm' => true, + 'missing' => $missing, + 'message' => sprintf(__('Found %d serial numbers. %d were not recognized. Continue processing the recognized entries?'), count($recognizedIds), count($missing)) + ]); + } + + if (empty($recognizedIds)) { + throw new Exception(__('No matching HDD serial numbers were available for processing')); + } + + $applyDestroyStatus = !empty($_POST['apply_destroy_status']); + $destroyDateInput = trim($_POST['destroy_date'] ?? ''); + $notes = trim($_POST['notes'] ?? ''); + if ($applyDestroyStatus) { + if ($destroyDateInput === '') { + throw new Exception(__('Please provide a destruction date when applying the destroyed status')); + } + $dt = DateTime::createFromFormat('Y-m-d', $destroyDateInput); + if (!$dt) { + throw new Exception(__('Invalid destruction date format (expected YYYY-MM-DD)')); + } + $destroyDateValue = $dt->format('Y-m-d 00:00:00'); + } else { + $destroyDateValue = null; + } + + $proofFileName = null; + if (isset($_FILES['batch_proof']) && $_FILES['batch_proof']['error'] !== UPLOAD_ERR_NO_FILE) { + if ($_FILES['batch_proof']['error'] !== UPLOAD_ERR_OK) { + throw new Exception(__('Error while uploading the proof file')); + } + if ($_FILES['batch_proof']['size'] > 5 * 1024 * 1024) { + throw new Exception(__('Proof file is too large (max 5 MB)')); + } + $allowedProofExtensions = [ + 'pdf' => ['application/pdf'], + 'xls' => ['application/vnd.ms-excel', 'application/octet-stream'], + 'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip', 'application/octet-stream'], + 'ods' => ['application/vnd.oasis.opendocument.spreadsheet', 'application/zip', 'application/octet-stream'], + ]; + $proofExtension = strtolower(pathinfo($_FILES['batch_proof']['name'], PATHINFO_EXTENSION)); + if (!array_key_exists($proofExtension, $allowedProofExtensions)) { + throw new Exception(__('Proof file type not allowed (PDF, XLS, XLSX or ODS only)')); + } + if (!class_exists('finfo')) { + throw new Exception(__('The fileinfo PHP extension is required to validate the proof file')); + } + $finfo = new finfo(FILEINFO_MIME_TYPE); + $proofMime = $finfo->file($_FILES['batch_proof']['tmp_name']); + if (!in_array($proofMime, $allowedProofExtensions[$proofExtension], true)) { + throw new Exception(__('Proof file type not allowed (PDF, XLS, XLSX or ODS only)')); + } + $datePart = date('Ymd-His'); + $randPart = substr(bin2hex(random_bytes(4)), 0, 8); + $proofFileName = "proof_batch_{$datePart}_{$randPart}.{$proofExtension}"; + + $pathSetting = $config->ParameterArray['hdd_proof_path'] ?? 'assets/files/hdd/'; + $storageRoot = $pathSetting; + if (preg_match('#^(?:[A-Za-z]:\\\\|/)#', $storageRoot) === 1) { + $baseDir = rtrim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $storageRoot), DIRECTORY_SEPARATOR); + } else { + $baseDir = rtrim(__DIR__ . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, trim($storageRoot, '/\\')), DIRECTORY_SEPARATOR); + } + if (!is_dir($baseDir)) { + if (!@mkdir($baseDir, 0750, true)) { + throw new Exception(__('Unable to create the storage directory') . ' : ' . $baseDir); + } + } + if (!is_writable($baseDir)) { + throw new Exception(__('Storage directory is not writable: ') . $baseDir); + } + $destPath = $baseDir . DIRECTORY_SEPARATOR . $proofFileName; + if (!@move_uploaded_file($_FILES['batch_proof']['tmp_name'], $destPath)) { + throw new Exception(__('Error while saving the proof file')); + } + @chmod($destPath, 0644); + } + + $idPlaceholders = implode(',', array_fill(0, count($recognizedIds), '?')); + if ($proofFileName !== null) { + $proofStmt = $dbh->prepare("UPDATE fac_HDD SET ProofFile = ? WHERE HDDID IN ($idPlaceholders)"); + $proofParams = array_merge([$proofFileName], $recognizedIds); + $proofStmt->execute($proofParams); + } + + if ($applyDestroyStatus) { + $statusStmt = $dbh->prepare("UPDATE fac_HDD SET Status='Destroyed', DateDestroyed = ? WHERE HDDID IN ($idPlaceholders)"); + $statusParams = array_merge([$destroyDateValue], $recognizedIds); + $statusStmt->execute($statusParams); + } + + $timestamp = date('c'); + $logLines = []; + $logLines[] = 'Timestamp: '.$timestamp; + $logLines[] = 'User: '.$person->UserID; + $logLines[] = 'Notes: '.($notes !== '' ? $notes : __('None')); + $logLines[] = sprintf('Processed serials (%d): %s', count($recognizedSerials), $recognizedSerials ? implode(', ', $recognizedSerials) : __('None')); + $logLines[] = sprintf('Already processed serials (%d): %s', count($alreadyProcessedSerials), $alreadyProcessedSerials ? implode(', ', $alreadyProcessedSerials) : __('None')); + $logLines[] = sprintf('Unknown serials (%d): %s', count($missing), $missing ? implode(', ', $missing) : __('None')); + $logLines[] = 'Proof file: '.($proofFileName !== null ? $proofFileName : __('None')); + if ($applyDestroyStatus) { + $logLines[] = 'Destruction date: '.$destroyDateValue; + } + $logText = implode("\n", $logLines); + + $logSummary = json_encode([ + 'timestamp' => $timestamp, + 'user' => $person->UserID, + 'notes' => $notes, + 'processed' => $recognizedSerials, + 'already_processed' => $alreadyProcessedSerials, + 'missing' => $missing + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + HDD::RecordGenericLog(null, $person->UserID, 'HDD_CSV_BATCH', $logSummary); + + $messageParts = []; + $messageParts[] = sprintf(__('Processed %d serial numbers.'), count($recognizedIds)); + if (!empty($alreadyProcessedSerials)) { + $messageParts[] = __('Serial numbers already processed! Only valid serials were handled.'); + } + if (!empty($missing)) { + $messageParts[] = sprintf(__('Skipped %d unknown serial numbers.'), count($missing)); + } + if ($proofFileName !== null) { + $messageParts[] = __('Proof file assigned to matching HDDs.'); + } + + $response = [ + 'success' => true, + 'message' => implode(' ', $messageParts), + 'missing' => $missing, + 'log_text' => $logText, + 'reload' => true + ]; + if (!empty($alreadyProcessedSerials)) { + $response['already_message'] = __('Serial numbers already processed! Only valid serials were handled.'); + } + $sendJson($response); +} catch (Exception $ex) { + $sendJson(['success' => false, 'error' => $ex->getMessage()], 400); +} diff --git a/locale/fr_FR/LC_MESSAGES/openDCIM.po b/locale/fr_FR/LC_MESSAGES/openDCIM.po index 9263f60e8..e9742366b 100644 --- a/locale/fr_FR/LC_MESSAGES/openDCIM.po +++ b/locale/fr_FR/LC_MESSAGES/openDCIM.po @@ -6183,6 +6183,97 @@ msgstr "" #~ msgid "Outlet Status" #~ msgstr "Statut de la prise électrique" +# New strings for HDD CSV automation and logging +msgid "Process CSV Batch" +msgstr "Traiter un lot CSV" + +msgid "Import CSV for Automated Destruction" +msgstr "Importer un CSV pour l'automatisation de la destruction" + +msgid "CSV file (UTF-8, max 2 MB)" +msgstr "Fichier CSV (UTF-8, max 2 Mo)" + +msgid "Destruction proof file (PDF / Excel / ODS, max 5 MB)" +msgstr "Fichier de preuve de destruction (PDF / Excel / ODS, max 5 Mo)" + +msgid "Apply destroyed status and date to matching HDDs" +msgstr "Appliquer le statut détruit et la date aux HDD correspondants" + +msgid "Destruction date (applied to all matching HDDs)" +msgstr "Date de destruction (appliquée à tous les HDD correspondants)" + +msgid "Optional note / reference" +msgstr "Note / référence optionnelle" + +msgid "Reference or ticket number" +msgstr "Référence ou numéro de ticket" + +msgid "Download log" +msgstr "Télécharger le journal" + +msgid "Please select the CSV column containing serial numbers" +msgstr "Veuillez sélectionner la colonne CSV contenant les numéros de série" + +msgid "Some serial numbers were not recognized. Continue processing the others?" +msgstr "Certains numéros de série n'ont pas été reconnus. Continuer avec les autres ?" + +msgid "An error occurred while processing the CSV." +msgstr "Une erreur est survenue lors du traitement du CSV." + +msgid "CSV file is required" +msgstr "Le fichier CSV est requis" + +msgid "Error while uploading the CSV file" +msgstr "Erreur lors du transfert du fichier CSV" + +msgid "CSV file is too large (max 2 MB)" +msgstr "Le fichier CSV est trop volumineux (max 2 Mo)" + +msgid "Unable to read CSV file or file is empty" +msgstr "Impossible de lire le fichier CSV ou le fichier est vide" + +msgid "Selected column was not found in the CSV header" +msgstr "La colonne sélectionnée est introuvable dans l'en-tête CSV" + +msgid "No serial numbers were found in the selected column" +msgstr "Aucun numéro de série n'a été trouvé dans la colonne sélectionnée" + +msgid "Please select a destruction date when applying the destroyed status" +msgstr "Veuillez sélectionner une date de destruction lors de l'application du statut détruit" + +msgid "Invalid destruction date format (expected YYYY-MM-DD)" +msgstr "Format de date de destruction invalide (format attendu AAAA-MM-JJ)" + +msgid "Please provide a destruction date when applying the destroyed status" +msgstr "Veuillez fournir une date de destruction lors de l'application du statut détruit" + +msgid "Serial numbers already processed! Only valid serials were handled." +msgstr "Numéros de série déjà traités ! Seuls les numéros valides seront pris en compte." + +msgid "No matching HDD serial numbers were available for processing" +msgstr "Aucun numéro de série HDD correspondant n'est disponible pour le traitement" + +msgid "Processed %d serial numbers." +msgstr "Traitement de %d numéros de série." + +msgid "Skipped %d unknown serial numbers." +msgstr "Ignoré %d numéros de série inconnus." + +msgid "Proof file assigned to matching HDDs." +msgstr "Fichier de preuve appliqué aux HDD correspondants." + +msgid "CSV batch - Notes: %s | Processed: %s | Already processed: %s | Unknown: %s" +msgstr "Lot CSV - Notes : %s | Traités : %s | Déjà traités : %s | Inconnus : %s" + +msgid "Bulk destroy of %d HDD(s). IDs: %s" +msgstr "Destruction en lot de %d HDD. ID : %s" + +msgid "Audit certified for this device." +msgstr "Audit certifié pour cet équipement." + +msgid "Details" +msgstr "Détails" + #~ msgid "Outlet Status On State" #~ msgstr "Statut de la prise électrique en l'état" diff --git a/managementhdd.php b/managementhdd.php index 5aef14356..f83d1ac69 100644 --- a/managementhdd.php +++ b/managementhdd.php @@ -34,6 +34,28 @@ $hddWaitList = HDD::GetPendingByDevice($dev->DeviceID); $hdddestroyedList = HDD::GetDestroyedHDDByDevice($dev->DeviceID); $hddSpareList = HDD::GetSpareHDDByDevice($dev->DeviceID); +$lastAudit = HDD::GetLastAudit($dev->DeviceID); +$proofPathSetting = $config->ParameterArray['hdd_proof_path'] ?? 'assets/files/hdd/'; +$proofWebBase = rtrim($proofPathSetting, '/') . '/'; + +if (!function_exists('build_hdd_proof_url')) { + /** + * Build a public URL for a stored proof file value. + */ + function build_hdd_proof_url($storedValue, $webBase) { + $value = trim((string)$storedValue); + if ($value === '') { + return ''; + } + if (preg_match('#^(?:[a-z]+:)?//#i', $value) === 1 || strpos($value, '/') === 0) { + return $value; + } + if (preg_match('#^[A-Za-z]:\\\\#', $value) === 1 || strpos($value, '/') !== false || strpos($value, '\\') !== false) { + return $value; + } + return $webBase . $value; + } +} ?> @@ -59,7 +81,7 @@

Label, ENT_QUOTES, 'UTF-8'); ?>

'.htmlspecialchars($_SESSION['LastError'], ENT_QUOTES, 'UTF-8').''; unset($_SESSION['LastError']); } ?> '.htmlspecialchars($_SESSION['Message'], ENT_QUOTES, 'UTF-8').''; unset($_SESSION['Message']); } ?> - +

@@ -155,10 +177,12 @@
HDDID, ENT_QUOTES) ?> DeviceID, ENT_QUOTES) ?>DeviceLabel ?? '', -ENT_QUOTES) ?>DeviceLabel ?? '',ENT_QUOTES) ?>HDDID, ENT_QUOTES) ?> SerialNo, ENT_QUOTES) ?> Status, ENT_QUOTES) ?> Size, ENT_QUOTES) ?> TypeMedia, ENT_QUOTES) ?> DateAdd ?? '', ENT_QUOTES) ?>DateWithdrawn ?? '', -ENT_QUOTES) ?>DateDestroyed ?? '', -ENT_QUOTES) ?>DateWithdrawn ?? '',ENT_QUOTES) ?>DateDestroyed ?? '',ENT_QUOTES) ?> ProofDocument)): ?> Date: Mon, 10 Nov 2025 17:23:46 +0100 Subject: [PATCH 20/25] Update db-23.04-to-24.01.sql --- db-23.04-to-24.01.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/db-23.04-to-24.01.sql b/db-23.04-to-24.01.sql index ab5173e93..fa60738bf 100755 --- a/db-23.04-to-24.01.sql +++ b/db-23.04-to-24.01.sql @@ -10,7 +10,7 @@ ON DUPLICATE KEY UPDATE Value = Value; INSERT INTO fac_Config (Parameter, Value, UnitOfMeasure, ValType, DefaultVal) VALUES ('Log_for_user_hdd', 'disabled','Enabled/Disabled','string','Disabled') ON DUPLICATE KEY UPDATE Value = Value; --- -fac_people -ALTER TABLE fac_People ADD COLUMN ManageHDD TINYINT(1) DEFAULT 0; +ALTER TABLE fac_People ADD COLUMN ManageHDD TINYINT(1) DEFAULT 0 AFTER SiteAdmin; --- -fac_devicetemplatehdd CREATE TABLE fac_DeviceTemplateHdd ( TemplateID INT NOT NULL, @@ -33,3 +33,4 @@ CREATE TABLE fac_HDD ( PRIMARY KEY (HDDID) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + From 343fefc86a8eeaee3f6d4c3f85e1b2d825e1f795 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Fri, 28 Nov 2025 00:34:23 +0100 Subject: [PATCH 21/25] Add HDD CSV automation and logging features --- classes/hdd.class.php | 46 ++++ devices.php | 17 +- hdd_log_view.php | 46 ++-- import_hdd_csv.php | 256 +++++++++++++++++++++ locale/fr_FR/LC_MESSAGES/openDCIM.po | 91 ++++++++ managementhdd.php | 178 +++++++++++++-- report_hdd.php | 327 +++++++++++++++++++++++++-- reports.php | 7 +- savehdd.php | 42 +++- upload_hdd_proof.php | 123 +++++++--- 10 files changed, 1033 insertions(+), 100 deletions(-) create mode 100644 import_hdd_csv.php diff --git a/classes/hdd.class.php b/classes/hdd.class.php index 13ff8d96d..ef2ffe9ea 100644 --- a/classes/hdd.class.php +++ b/classes/hdd.class.php @@ -266,6 +266,52 @@ public static function MarkAsSpare(int $id): bool { return $res; } + public static function RecordGenericLog(?int $deviceID, string $userID, string $action, string $details): void { + global $dbh; + $objectId = $deviceID ?? 0; + $stmt = $dbh->prepare("INSERT INTO fac_GenericLog (UserID, Class, ObjectID, ChildID, Action, Property, OldVal, NewVal) + VALUES (:UserID, 'HDD', :ObjectID, NULL, :Action, 'Details', '', :NewVal)"); + $stmt->execute([ + ':UserID' => $userID, + ':ObjectID' => $objectId, + ':Action' => $action, + ':NewVal' => $details + ]); + } + + public static function RecordAudit(int $deviceID, string $userID): bool { + global $dbh; + $stmt = $dbh->prepare( + "INSERT INTO fac_GenericLog + (UserID, Class, ObjectID, ChildID, Action, Property, OldVal, NewVal) + VALUES + (:UserID, 'HDD', :ObjectID, NULL, 'HDD_Audit', 'Audit', '', '')" + ); + return $stmt->execute([ + ':UserID' => $userID, + ':ObjectID' => $deviceID + ]); + } + + public static function GetLastAudit(int $deviceID): ?array { + global $dbh; + $sql = "SELECT g.Time AS AuditTime, g.UserID, + NULLIF(TRIM(CONCAT(COALESCE(p.FirstName,''), ' ', COALESCE(p.LastName,''))), '') AS PersonName + FROM fac_GenericLog g + LEFT JOIN fac_People p ON p.UserID = g.UserID + WHERE g.Class='HDD' AND g.Action='HDD_Audit' AND g.ObjectID = :DeviceID + ORDER BY g.Time DESC LIMIT 1"; + $stmt = $dbh->prepare($sql); + $stmt->execute([':DeviceID' => $deviceID]); + if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + return [ + 'AuditTime' => $row['AuditTime'], + 'DisplayName' => ($row['PersonName'] ?: $row['UserID']) + ]; + } + return null; + } + // List active HDDs for a device public static function GetHDDByDevice(int $DeviceID): array { global $dbh; diff --git a/devices.php b/devices.php index 74915675b..19a41055f 100644 --- a/devices.php +++ b/devices.php @@ -1,6 +1,9 @@ ParameterArray['feature_hdd'] == 'enabled'){ + require_once( 'classes/hdd.class.php' ); + } $subheader=__("Data Center Device Detail"); @@ -2067,11 +2070,17 @@ function setPreferredLayout() {EnableHDDFeature == 1 ){ + $deviceHddAudit = HDD::GetLastAudit($dev->DeviceID); echo ' - '; +
+
+
'; + if($deviceHddAudit){ + echo '
+
',__("Last HDD audit"),'
+
',date('Y-m-d H:i', strtotime($deviceHddAudit['AuditTime'])),'
+
'; + } } } diff --git a/hdd_log_view.php b/hdd_log_view.php index 80e60a318..0d18693f3 100644 --- a/hdd_log_view.php +++ b/hdd_log_view.php @@ -17,16 +17,36 @@ } $sql = " - SELECT g.Time, g.UserID, g.LogText, h.Label, h.SerialNo - FROM fac_GenericLog g - JOIN fac_HDD h ON g.ItemID = h.hddID - WHERE g.ItemType = 'HDD' AND h.DeviceID = ? - ORDER BY g.Time DESC -"; - + SELECT Time, UserID, Action, NewVal + FROM fac_GenericLog + WHERE Class = 'HDD' AND ObjectID = :DeviceID + ORDER BY Time DESC"; $stmt = $dbh->prepare($sql); -$stmt->execute([$deviceID]); +$stmt->execute([':DeviceID' => $deviceID]); $logEntries = $stmt->fetchAll(PDO::FETCH_ASSOC); + +function formatHddLogDetails($action, $payload) { + $data = json_decode($payload, true); + if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) { + return $payload; + } + switch ($action) { + case 'HDD_BULK_DESTROY': + $count = isset($data['count']) ? intval($data['count']) : 0; + $list = isset($data['ids']) && is_array($data['ids']) ? implode(', ', $data['ids']) : ''; + return sprintf(__("Bulk destroy of %d HDD(s). IDs: %s"), $count, $list); + case 'HDD_CSV_BATCH': + $notes = isset($data['notes']) && $data['notes'] !== '' ? $data['notes'] : __('None'); + $processed = isset($data['processed']) && is_array($data['processed']) ? implode(', ', $data['processed']) : __('None'); + $already = isset($data['already_processed']) && is_array($data['already_processed']) ? implode(', ', $data['already_processed']) : __('None'); + $missing = isset($data['missing']) && is_array($data['missing']) ? implode(', ', $data['missing']) : __('None'); + return sprintf(__("CSV batch - Notes: %s | Processed: %s | Already processed: %s | Unknown: %s"), $notes, $processed, $already, $missing); + case 'HDD_Audit': + return __("Audit certified for this device."); + default: + return $payload; + } +} ?> @@ -47,19 +67,17 @@
-

- -

- +

+ +

@@ -177,12 +201,14 @@ $i = 1; foreach ($hdddestroyedList as $hdd) { $id = (int)$hdd->HDDID; + $proofUrl = build_hdd_proof_url($hdd->ProofFile ?? '', $proofWebBase); + $proofLink = $proofUrl !== '' ? ''.__('View proof').'' : ''; echo ' '.$i.' '.htmlspecialchars($hdd->SerialNo, ENT_QUOTES, "UTF-8").' '.htmlspecialchars($hdd->Status, ENT_QUOTES, "UTF-8").' '.$hdd->DateDestroyed.' - '.(!empty($hdd->ProofFile) ? ''.__('Voir la preuve').'' : '').' + '.$proofLink.' '; $i++; } @@ -226,13 +252,21 @@ + + 0){ - echo '
+ echo '
'; + if($lastAudit){ + echo '
'.sprintf(__('Last HDD audit: %s (%s)'), date('Y-m-d H:i', strtotime($lastAudit['AuditTime'])), htmlspecialchars($lastAudit['DisplayName'], ENT_QUOTES, 'UTF-8')).'
'; + } + echo '
-
+
'; } ?> @@ -287,20 +321,20 @@ -
+ diff --git a/report_hdd.php b/report_hdd.php index 3a0677fa0..8c72384eb 100644 --- a/report_hdd.php +++ b/report_hdd.php @@ -37,16 +37,36 @@ LEFT JOIN fac_Device d ON d.DeviceID = h.DeviceID LEFT JOIN fac_Cabinet c ON c.CabinetID = d.Cabinet LEFT JOIN fac_DataCenter dc ON dc.DataCenterID = c.DataCenterID - WHERE h.Status = 'Pending_destruction' "; // Execute query $stmt = $dbh->prepare($sql); $stmt->execute(); $hddList = $stmt->fetchAll(PDO::FETCH_OBJ); +$proofPathSetting = $config->ParameterArray['hdd_proof_path'] ?? 'assets/files/hdd/'; +$proofWebBase = rtrim($proofPathSetting, '/') . '/'; + +if (!function_exists('build_hdd_proof_url')) { + /** + * Build a public URL for a stored proof file value. + */ + function build_hdd_proof_url($storedValue, $webBase) { + $value = trim((string)$storedValue); + if ($value === '') { + return ''; + } + if (preg_match('#^(?:[a-z]+:)?//#i', $value) === 1 || strpos($value, '/') === 0) { + return $value; + } + if (preg_match('#^[A-Za-z]:\\\\#', $value) === 1 || strpos($value, '/') !== false || strpos($value, '\\') !== false) { + return $value; + } + return $webBase . $value; + } +} ?> - + <?php echo htmlspecialchars($subheader, ENT_QUOTES); ?> @@ -80,7 +100,7 @@ ] }); - // Filtres simples + // Simple filters $('#filterStatus').on('change', function(){ table.column(6).search(this.value).draw(); }); @@ -88,7 +108,7 @@ table.column(1).search(this.value).draw(); }); - // Sélection + // Selection helpers $('#select_all').on('change', function(){ var checked = this.checked; $('input.hdd-select').prop('checked', checked); @@ -96,7 +116,7 @@ $('#btnUploadProof').on('click', function(){ var ids = $('input.hdd-select:checked').map(function(){return this.value;}).get(); if(ids.length === 0){ - alert(''); + alert(''); return; } // Build and submit modal @@ -104,9 +124,221 @@ ids.forEach(function(id){ $('').attr({type:'hidden', name:'hdd_ids[]', value:id}).appendTo('#uploadForm'); }); + $('#apply_destroy_status').prop('checked', false); + $('#destroy_date').val(''); + $('#destroy_date_wrapper').hide(); $('#uploadModal').show(); }); $('#closeUploadModal').on('click', function(){ $('#uploadModal').hide(); }); + $('#apply_destroy_status').on('change', function(){ + if(this.checked){ + $('#destroy_date_wrapper').slideDown(150); + }else{ + $('#destroy_date_wrapper').slideUp(150); + $('#destroy_date').val(''); + } + }); + $('#uploadForm').on('submit', function(){ + if($('#apply_destroy_status').is(':checked')){ + var dateVal = $('#destroy_date').val(); + if(!dateVal){ + alert(''); + return false; + } + } + return true; + }); + + function resetBatchCsvModal(){ + $('#batchCsvForm')[0].reset(); + $('#csv_column').prop('disabled', true).empty().append(''); + $('#batchCsvLog').hide().val(''); + $('#downloadBatchLog').hide(); + $('#batch_destroy_date_wrapper').hide(); + } + + function detectDelimiter(line){ + var delimiters = [',',';','\t','|']; + var best = ','; + var max = 0; + delimiters.forEach(function(delim){ + var count = line.split(delim).length - 1; + if(count > max){ + max = count; + best = delim; + } + }); + return best; + } + + function parseCsvLine(line, delimiter){ + var result = []; + var current = ''; + var inQuotes = false; + for (var i = 0; i < line.length; i++){ + var char = line[i]; + if(char === '"'){ + if(inQuotes && line[i+1] === '"'){ + current += '"'; + i++; + }else{ + inQuotes = !inQuotes; + } + }else if(char === delimiter && !inQuotes){ + result.push(current); + current = ''; + }else{ + current += char; + } + } + result.push(current); + return result; + } + + function parseCsvHeader(content){ + var lines = content.split(/\r?\n/).filter(function(line){ return line.trim().length > 0; }); + if(!lines.length){ return []; } + var delimiter = detectDelimiter(lines[0]); + return parseCsvLine(lines[0], delimiter).map(function(item){ return item.trim(); }); + } + + function populateCsvColumns(headers){ + var select = $('#csv_column'); + select.empty(); + if(!headers.length){ + select.append(''); + select.prop('disabled', true); + return; + } + select.prop('disabled', false); + headers.forEach(function(header){ + if(header){ + $('
'; unset($_SESSION['Message']); } ?>
-
- + @@ -179,8 +412,11 @@ function redraw(){ @@ -192,15 +428,72 @@ function redraw(){ + + + diff --git a/savehdd.php b/savehdd.php index 2cbce1a35..dcf47ef48 100644 --- a/savehdd.php +++ b/savehdd.php @@ -102,14 +102,40 @@ break; case $action === "bulk_destroy": - foreach ($_POST['select_pending'] ?? [] as $id) { - HDD::MarkDestroyed(intval($id)); + $pendingSelected = $_POST['select_pending_destroyed'] ?? ($_POST['select_pending'] ?? []); + $destroyedIds = []; + foreach ($pendingSelected as $id) { + $intId = intval($id); + if ($intId > 0 && HDD::MarkDestroyed($intId)) { + $destroyedIds[] = $intId; + } + } + if (!empty($destroyedIds)) { + $details = json_encode([ + 'ids' => $destroyedIds, + 'count' => count($destroyedIds), + 'source' => 'bulk_destroy' + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + HDD::RecordGenericLog($deviceID, $person->UserID, 'HDD_BULK_DESTROY', $details); } break; case $action === "bulk_destroyFromActive": - foreach ($_POST['select_active'] ?? [] as $id) { - HDD::MarkDestroyed(intval($id)); + $activeSelected = $_POST['select_active'] ?? []; + $destroyedActive = []; + foreach ($activeSelected as $id) { + $intId = intval($id); + if ($intId > 0 && HDD::MarkDestroyed($intId)) { + $destroyedActive[] = $intId; + } + } + if (!empty($destroyedActive)) { + $details = json_encode([ + 'ids' => $destroyedActive, + 'count' => count($destroyedActive), + 'source' => 'bulk_destroy_active' + ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + HDD::RecordGenericLog($deviceID, $person->UserID, 'HDD_BULK_DESTROY', $details); } break; @@ -119,6 +145,14 @@ // (la méthode se termine par exit()) break; + case $action === "certify_audit": + if (HDD::RecordAudit($deviceID, $person->UserID)) { + $_SESSION['Message'] = __('HDD audit recorded successfully'); + } else { + $_SESSION['LastError'] = __('Unable to record HDD audit'); + } + break; + default: throw new Exception("Action inconnue : “{$action}”."); } diff --git a/upload_hdd_proof.php b/upload_hdd_proof.php index 80db9df17..ab7911d17 100644 --- a/upload_hdd_proof.php +++ b/upload_hdd_proof.php @@ -1,6 +1,6 @@ __('Le fichier est trop volumineux (max 5 Mo)'), - UPLOAD_ERR_FORM_SIZE => __('Le fichier est trop volumineux (max 5 Mo)'), - UPLOAD_ERR_PARTIAL => __('Erreur lors du transfert du fichier'), - UPLOAD_ERR_NO_FILE => __('Aucun fichier fourni'), - UPLOAD_ERR_NO_TMP_DIR => __('Répertoire temporaire manquant'), - UPLOAD_ERR_CANT_WRITE => __('Impossible d\'écrire le fichier sur le disque'), - UPLOAD_ERR_EXTENSION => __('Téléversement bloqué par une extension'), + UPLOAD_ERR_INI_SIZE => __('File too large (max 5 MB)'), + UPLOAD_ERR_FORM_SIZE => __('File too large (max 5 MB)'), + UPLOAD_ERR_PARTIAL => __('File upload error'), + UPLOAD_ERR_NO_FILE => __('No file provided'), + UPLOAD_ERR_NO_TMP_DIR => __('Temporary directory is missing'), + UPLOAD_ERR_CANT_WRITE => __('Unable to write the file to disk'), + UPLOAD_ERR_EXTENSION => __('Upload blocked by a PHP extension'), ]; - throw new Exception($map[$e] ?? __('Erreur lors du transfert du fichier')); + throw new Exception($map[$e] ?? __('File upload error')); } $file = $_FILES['proof_pdf']; // Size <= 5 MiB if ($file['size'] > 5 * 1024 * 1024) { - throw new Exception(__('Le fichier est trop volumineux (max 5 Mo)')); + throw new Exception(__('File too large (max 5 MB)')); + } + + $allowedExtensions = [ + 'pdf' => ['application/pdf'], + 'xls' => ['application/vnd.ms-excel', 'application/octet-stream'], + 'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip', 'application/octet-stream'], + 'ods' => ['application/vnd.oasis.opendocument.spreadsheet', 'application/zip', 'application/octet-stream'], + ]; + + $originalExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + if (!array_key_exists($originalExtension, $allowedExtensions)) { + throw new Exception(__('File type not allowed (PDF, XLS, XLSX or ODS only)')); } // MIME check if (!class_exists('finfo')) { - throw new Exception(__('L\'extension fileinfo n\'est pas disponible côté serveur')); + throw new Exception(__('The fileinfo PHP extension is not available on this server')); } $finfo = new finfo(FILEINFO_MIME_TYPE); $mime = $finfo->file($file['tmp_name']); - if ($mime !== 'application/pdf') { - throw new Exception(__('Fichier non autorisé (PDF uniquement)')); + if (!in_array($mime, $allowedExtensions[$originalExtension], true)) { + throw new Exception(__('File type not allowed (PDF, XLS, XLSX or ODS only)')); + } + + $applyDestroyStatus = !empty($_POST['apply_destroy_status']); + $destroyDateInput = trim($_POST['destroy_date'] ?? ''); + $destroyDateValue = null; + if ($applyDestroyStatus) { + if ($destroyDateInput === '') { + throw new Exception(__('Please select a destruction date when applying the destroyed status')); + } + $dateObject = DateTime::createFromFormat('Y-m-d', $destroyDateInput); + if (!$dateObject) { + throw new Exception(__('Invalid destruction date format (expected YYYY-MM-DD)')); + } + $destroyDateValue = $dateObject->format('Y-m-d'); } // Normalized file name $datePart = date('Ymd-His'); $randPart = substr(bin2hex(random_bytes(4)), 0, 8); - $targetName = "proof_{$datePart}_{$randPart}.pdf"; - - // Storage: use configured relative path (with trailing slash) - $relBase = $config->ParameterArray['hdd_proof_path'] ?? 'assets/files/hdd/'; - // Ensure trailing slash for URL storage - $relBase = rtrim($relBase, '/') . '/'; - $relPath = $relBase . $targetName; // Path persisted in DB (web path) - - // Build absolute filesystem path - $basePath = $relBase; - // If absolute path provided, use as is; else resolve relative to this script directory - if (preg_match('#^(?:[A-Za-z]:\\\\|/)#', $basePath) === 1) { - $baseDir = rtrim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $basePath), DIRECTORY_SEPARATOR); + $targetName = "proof_{$datePart}_{$randPart}.{$originalExtension}"; + + // Storage: use configured path for filesystem + public URL + $pathSetting = $config->ParameterArray['hdd_proof_path'] ?? 'assets/files/hdd/'; + $publicBase = rtrim($pathSetting, '/') . '/'; + + // Resolve filesystem path from configuration setting + $storageRoot = $pathSetting; + if (preg_match('#^(?:[A-Za-z]:\\\\|/)#', $storageRoot) === 1) { + $baseDir = rtrim(str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $storageRoot), DIRECTORY_SEPARATOR); } else { - $baseDir = rtrim(__DIR__ . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, trim($basePath, '/\\')), DIRECTORY_SEPARATOR); + $baseDir = rtrim(__DIR__ . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, trim($storageRoot, '/\\')), DIRECTORY_SEPARATOR); } if (!is_dir($baseDir)) { if (!@mkdir($baseDir, 0750, true)) { - throw new Exception(__('Impossible de créer le répertoire de stockage') . ' : ' . $baseDir); + throw new Exception(__('Unable to create the storage directory') . ' : ' . $baseDir); } } if (!is_writable($baseDir)) { - throw new Exception(__('Le répertoire de stockage n\'est pas inscriptible: ') . $baseDir); + throw new Exception(__('Storage directory is not writable: ') . $baseDir); } $destPath = $baseDir . DIRECTORY_SEPARATOR . $targetName; if (!@move_uploaded_file($file['tmp_name'], $destPath)) { - throw new Exception(__('Erreur lors du transfert du fichier (move_uploaded_file)')); + throw new Exception(__('File upload error (move_uploaded_file)')); } @chmod($destPath, 0644); - // Update DB for all selected IDs - $updated = HDD::SetProofFileForIds($ids, $relPath); + // Update DB for all selected IDs with the stored filename only + global $dbh; + $placeholders = implode(',', array_fill(0, count($ids), '?')); + $sql = "UPDATE fac_HDD SET ProofFile = ? WHERE HDDID IN ($placeholders)"; + $params = array_merge([$targetName], $ids); + $stmt = $dbh->prepare($sql); + $stmt->execute($params); + $updated = $stmt->rowCount(); if ($updated <= 0) { - throw new Exception(__('Aucune ligne mise à jour en base (vérifier la colonne ProofFile et les IDs)')); + throw new Exception(__('No database rows were updated (check ProofFile column and IDs)')); + } + + if ($applyDestroyStatus) { + $statusPlaceholders = implode(',', array_fill(0, count($ids), '?')); + $statusSql = "UPDATE fac_HDD SET Status = 'Destroyed', DateDestroyed = ? WHERE HDDID IN ($statusPlaceholders)"; + $statusParams = array_merge([$destroyDateValue], $ids); + $statusStmt = $dbh->prepare($statusSql); + $statusStmt->execute($statusParams); } - $_SESSION['Message'] = __('Preuve de destruction enregistrée avec succès'); + $successMessage = __('Destruction proof uploaded successfully'); + $publicPath = $publicBase . $targetName; + if ($isAjax) { + header('Content-Type: application/json'); + echo json_encode(['success' => true, 'message' => $successMessage, 'path' => $publicPath]); + exit; + } + + $_SESSION['Message'] = $successMessage; } catch (Exception $ex) { error_log('[upload_hdd_proof] ' . $ex->getMessage()); + if ($isAjax) { + header('Content-Type: application/json', true, 400); + echo json_encode(['success' => false, 'error' => $ex->getMessage()]); + exit; + } $_SESSION['LastError'] = $ex->getMessage(); } From 3337b5f6dd6e0aec805a3a36a0173e8b7e303660 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Fri, 28 Nov 2025 00:59:53 +0100 Subject: [PATCH 22/25] readme feat Manage HDD --- readme_feat_Hdd.txt | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 readme_feat_Hdd.txt diff --git a/readme_feat_Hdd.txt b/readme_feat_Hdd.txt new file mode 100644 index 000000000..de34812ff --- /dev/null +++ b/readme_feat_Hdd.txt @@ -0,0 +1,47 @@ +# HDD Workflow Automation Guide + +## 1. Activation & Acc�s +- Assurez-vous que la fonctionnalit� HDD est activ�e ( eature_hdd = enabled). +- Le rapport est disponible depuis **Reports > Asset Reports** si l�option est active. +- Les pages managementhdd.php et report_hdd.php n�cessitent l�autorisation ManageHDD dans droit utilisateur. +- Activé la fonctionnalité par modél d'équipement dans devicetemplate. + +## 2. Vue Gestion HDD (managementhdd.php) +- Bouton **Certify Audit HDD** : enregistre une entr�e d�audit (visible dans View Log, Devices, Reports). +- Bouton **View HDD Activity Log** : affiche toutes les actions HDD_BULK_DESTROY, HDD_CSV_BATCH, HDD_Audit. +- Boutons **Destroy Selected** / **Export all to Excel** fonctionnent via le formulaire principal manageHddForm. + +## 3. Rapport �HDD Management Report� (report_hdd.php) +### 3.1 Destruction classique +1. S�lectionner des HDD. +2. Cliquer sur **Add destruction proof** -> uploader un PDF/Excel/ODS et, si besoin, cocher Apply destroyed status... + date. +3. Soumettre : chaque HDD re�oit le fichier de preuve; si l�option est coch�e, statut/dates sont mis � jour. +4. Les actions sont journalis�es (HDD_BULK_DESTROY) avec heure/utilisateur. + +### 3.2 Traitement CSV automatis� +1. Cliquer sur **Process CSV Batch**. +2. Choisir un fichier CSV (UTF-8, max 2 Mo). +3. Une fois le fichier charg�, s�lectionner la **colonne contenant les serial numbers**. +4. (Optionnel) Fournir un fichier de preuve commun + cocher l�application du statut/dates. +5. (Optionnel) Remplir le champ **Note/Reference** (appara�t dans le log). +6. Soumettre : + - Les SN d�j� d�truits sont ignor�s et signal�s (message + log). + - Les SN inconnus provoquent une demande de confirmation avant de continuer. + - Seuls les SN valides re�oivent la preuve / statut. + - Un fichier .txt est t�l�charg� automatiquement, r�capitulant la note, les SN trait�s/ignor�s et l�horodatage. + - Une entr�e HDD_CSV_BATCH est ajout�e dans ac_GenericLog (trace utilisateur + JSON r�capitulatif). + +## 4. Consultation des journaux (hdd_log_view.php) +- Accessible via le bouton **View HDD Activity Log** (ou directement hdd_log_view.php?DeviceID=...). +- Colonne **Action** : HDD_BULK_DESTROY, HDD_CSV_BATCH, HDD_Audit (ou futurs types). +- Colonne **Details** : + - Bulk destroy : nombre de HDD et IDs. + - CSV batch : note, liste des SN trait�s / d�j� trait�s / inconnus. + - Audit : simple confirmation. + +## 5. Messages & Traductions +- Tous les textes des modales/boutons/messages ont une traduction fran�aise dans locale/fr_FR/LC_MESSAGES/openDCIM.po (section �New strings for HDD CSV automation and logging�). + +## 6. Points de vigilance / �volutions pr�vues +- Les CSV doivent contenir au moins une colonne SN ; chaque colonne est analys�e apr�s upload (d�limiteurs auto : , ; tab |). +- Les futures �volutions pr�vues incluent l�import natif d�OCS Inventory pour afficher l��tat des disques (On/Off), faciliter maintenance/destruction et int�grer un flux 100% automatis�. From 257d9b01fe9abe7cc29d68a10a01785d7e2ef85f Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:36:50 +0100 Subject: [PATCH 23/25] feat: enhance HDD management UI and logging --- classes/hdd.class.php | 112 ++++++++++++++---- css/inventory.php | 28 +++++ hdd_log_view.php | 3 + managementhdd.php | 266 ++++++++++++++++++++++++++++++++++++++++-- savehdd.php | 188 ++++++++++++++++++++++++----- 5 files changed, 531 insertions(+), 66 deletions(-) diff --git a/classes/hdd.class.php b/classes/hdd.class.php index ef2ffe9ea..fcec6030c 100644 --- a/classes/hdd.class.php +++ b/classes/hdd.class.php @@ -153,14 +153,15 @@ public static function DeleteByID(int $id): bool { } // Duplicate this HDD - public static function DuplicateToEmptySlots(int $sourceHDDID): void { + public static function DuplicateToEmptySlots(int $sourceHDDID): array { global $dbh; + $created = []; // Récupère le disque source $stmt = $dbh->prepare("SELECT * FROM fac_HDD WHERE HDDID = ?"); $stmt->execute([$sourceHDDID]); $hdd = $stmt->fetch(PDO::FETCH_ASSOC); if (!$hdd) { - return; + return $created; }// Nombre max de HDD permis par template $deviceID = intval($hdd['DeviceID']); $stmt = $dbh->prepare( @@ -173,7 +174,7 @@ public static function DuplicateToEmptySlots(int $sourceHDDID): void { $max = intval($stmt->fetchColumn()); if ($max <= 0) { - return; + return $created; }// Combien sont déjà présents ? $stmt = $dbh->prepare("SELECT COUNT(*) FROM fac_HDD WHERE DeviceID = ? AND (Status = 'On' OR Status = 'Off')"); $stmt->execute([$deviceID]); @@ -181,7 +182,7 @@ public static function DuplicateToEmptySlots(int $sourceHDDID): void { $remaining = $max - $current; if ($remaining <= 0) { - return; + return $created; } // Insère les duplicata $stmt = $dbh->prepare( "INSERT INTO fac_HDD @@ -198,8 +199,10 @@ public static function DuplicateToEmptySlots(int $sourceHDDID): void { $hdd['Size'] ]); $newId = intval($dbh->lastInsertId()); + $created[] = $newId; self::logAction("Duplicated from HDDID $sourceHDDID", $newId); } + return $created; } // Send HDD for destruction @@ -218,15 +221,43 @@ public function SendForDestruction(string $note = ''): bool { } // Mark HDD as destroyed - public static function MarkDestroyed(int $id): bool { + public static function MarkDestroyed(int $id, ?string $destroyDate = null): bool { global $dbh; - $stmt = $dbh->prepare( - "UPDATE fac_HDD SET - Status = 'Destroyed', - DateDestroyed = NOW() - WHERE HDDID = ?" - ); - $res = $stmt->execute([$id]); + $dateValue = null; + if ($destroyDate !== null && trim($destroyDate) !== '') { + $destroyDate = trim($destroyDate); + $formats = ['Y-m-d H:i:s', 'Y-m-d H:i', 'Y-m-d']; + foreach ($formats as $format) { + $dt = DateTime::createFromFormat($format, $destroyDate); + if ($dt instanceof DateTime) { + $dateValue = $dt->format('Y-m-d H:i:s'); + break; + } + } + } + + if ($dateValue) { + $stmt = $dbh->prepare( + "UPDATE fac_HDD SET + Status = 'Destroyed', + DateDestroyed = :DateDestroyed + WHERE HDDID = :HDDID" + ); + $params = [ + ':DateDestroyed' => $dateValue, + ':HDDID' => $id + ]; + } else { + $stmt = $dbh->prepare( + "UPDATE fac_HDD SET + Status = 'Destroyed', + DateDestroyed = NOW() + WHERE HDDID = :HDDID" + ); + $params = [':HDDID' => $id]; + } + + $res = $stmt->execute($params); if ($res) { self::logAction("Marked as destroyed", $id); } @@ -236,6 +267,11 @@ public static function MarkDestroyed(int $id): bool { //Reassign a HDD to another device public static function ReassignToDevice(int $id, int $deviceID): bool { global $dbh; + $current = self::GetHDDByID($id); + $currentDevice = ($current instanceof HDD) ? intval($current->DeviceID) : 0; + if ($currentDevice !== $deviceID && self::GetRemainingSlotCount($deviceID) <= 0) { + return false; + } $stmt = $dbh->prepare( "UPDATE fac_HDD SET DeviceID = ?, @@ -266,17 +302,32 @@ public static function MarkAsSpare(int $id): bool { return $res; } - public static function RecordGenericLog(?int $deviceID, string $userID, string $action, string $details): void { + public static function GetRemainingSlotCount(int $deviceID): int { global $dbh; - $objectId = $deviceID ?? 0; - $stmt = $dbh->prepare("INSERT INTO fac_GenericLog (UserID, Class, ObjectID, ChildID, Action, Property, OldVal, NewVal) - VALUES (:UserID, 'HDD', :ObjectID, NULL, :Action, 'Details', '', :NewVal)"); - $stmt->execute([ - ':UserID' => $userID, - ':ObjectID' => $objectId, - ':Action' => $action, - ':NewVal' => $details - ]); + $stmt = $dbh->prepare( + "SELECT COALESCE(cfg.HDDCount, 0) AS Capacity, + COALESCE(u.ActiveCount, 0) AS Used + FROM fac_Device d + LEFT JOIN fac_DeviceTemplateHdd cfg ON cfg.TemplateID = d.TemplateID AND cfg.EnableHDDFeature = 1 + LEFT JOIN ( + SELECT DeviceID, COUNT(*) AS ActiveCount + FROM fac_HDD + WHERE Status IN ('On','Off') + GROUP BY DeviceID + ) u ON u.DeviceID = d.DeviceID + WHERE d.DeviceID = :DeviceID" + ); + $stmt->execute([':DeviceID' => $deviceID]); + if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { + $capacity = intval($row['Capacity']); + $used = intval($row['Used']); + if ($capacity <= 0) { + return 0; + } + $remaining = $capacity - $used; + return ($remaining > 0) ? $remaining : 0; + } + return 0; } public static function RecordAudit(int $deviceID, string $userID): bool { @@ -293,6 +344,23 @@ public static function RecordAudit(int $deviceID, string $userID): bool { ]); } + public static function RecordGenericLog(?int $deviceID, string $userID, string $action, string $details = ''): bool { + global $dbh; + $objectId = $deviceID ?? 0; + $stmt = $dbh->prepare( + "INSERT INTO fac_GenericLog + (UserID, Class, ObjectID, ChildID, Action, Property, OldVal, NewVal) + VALUES + (:UserID, 'HDD', :ObjectID, NULL, :Action, 'Details', '', :NewVal)" + ); + return $stmt->execute([ + ':UserID' => $userID, + ':ObjectID' => $objectId, + ':Action' => $action, + ':NewVal' => $details + ]); + } + public static function GetLastAudit(int $deviceID): ?array { global $dbh; $sql = "SELECT g.Time AS AuditTime, g.UserID, diff --git a/css/inventory.php b/css/inventory.php index 0faa03b06..2f9465197 100644 --- a/css/inventory.php +++ b/css/inventory.php @@ -394,6 +394,34 @@ .whiteborder, .whiteborder div {border: 1px solid white;} .border, .border div {border: 1px solid gray;} +.assign-device-table { + text-align: center; +} +#assignDeviceList { + margin: 0 auto; + border-collapse: separate !important; + border-spacing: 10px 6px; +} +#assignDeviceList th, +#assignDeviceList td { + padding: 8px 18px; +} +#assignDeviceList .assign-device-row:hover { + background-color: #eef6ff; + cursor: pointer; +} + +.hdd-status-select { + transition: background-color .2s ease-in-out; + color: #000; +} +.hdd-status-select.status-on { + background-color: #dff5d8; +} +.hdd-status-select.status-off { + background-color: #ffd6d6; +} + /* Search Results */ .search .center {text-align: left;} .search .main ol, .search .main ul{list-style-type: none;margin-left: 1em;} diff --git a/hdd_log_view.php b/hdd_log_view.php index 0d18693f3..e4c868bf6 100644 --- a/hdd_log_view.php +++ b/hdd_log_view.php @@ -55,6 +55,9 @@ function formatHddLogDetails($action, $payload) { <?php echo $subheader; ?> + + + diff --git a/managementhdd.php b/managementhdd.php index f83d1ac69..947cec27f 100644 --- a/managementhdd.php +++ b/managementhdd.php @@ -38,6 +38,36 @@ $proofPathSetting = $config->ParameterArray['hdd_proof_path'] ?? 'assets/files/hdd/'; $proofWebBase = rtrim($proofPathSetting, '/') . '/'; +$assignableDevices = []; +$assignSql = " + SELECT d.DeviceID, d.Label, d.SerialNo, cfg.HDDCount, + COALESCE(active.ActiveCount, 0) AS ActiveCount + FROM fac_Device d + INNER JOIN fac_DeviceTemplateHdd cfg ON cfg.TemplateID = d.TemplateID AND cfg.EnableHDDFeature = 1 + LEFT JOIN ( + SELECT DeviceID, COUNT(*) AS ActiveCount + FROM fac_HDD + WHERE Status IN ('On','Off') + GROUP BY DeviceID + ) active ON active.DeviceID = d.DeviceID + WHERE d.DeviceID <> :DeviceID + ORDER BY d.Label ASC"; +$assignStmt = $dbh->prepare($assignSql); +$assignStmt->execute([':DeviceID' => $dev->DeviceID]); +while ($row = $assignStmt->fetch(PDO::FETCH_ASSOC)) { + $capacity = intval($row['HDDCount']); + $used = intval($row['ActiveCount']); + $freeSlots = $capacity - $used; + if ($capacity > 0 && $freeSlots > 0) { + $assignableDevices[] = [ + 'DeviceID' => intval($row['DeviceID']), + 'Label' => $row['Label'], + 'SerialNo' => $row['SerialNo'] ?? '', + 'FreeSlots' => $freeSlots + ]; + } +} + if (!function_exists('build_hdd_proof_url')) { /** * Build a public URL for a stored proof file value. @@ -83,6 +113,7 @@ function build_hdd_proof_url($storedValue, $webBase) { '.htmlspecialchars($_SESSION['Message'], ENT_QUOTES, 'UTF-8').''; unset($_SESSION['Message']); } ?> +

DateWithdrawn ?? '',ENT_QUOTES) ?> DateDestroyed ?? '',ENT_QUOTES) ?> - ProofFile)): ?> - + ProofFile ?? '', $proofWebBase); + if ($proofUrl !== ''): + ?> +
@@ -108,7 +139,7 @@ function build_hdd_proof_url($storedValue, $webBase) { -
'.$i.' + + + + @@ -328,6 +364,11 @@ function build_hdd_proof_url($storedValue, $webBase) { +
+ + + +
@@ -335,8 +376,45 @@ function build_hdd_proof_url($storedValue, $webBase) {
- - + diff --git a/savehdd.php b/savehdd.php index dcf47ef48..ba6932554 100644 --- a/savehdd.php +++ b/savehdd.php @@ -20,11 +20,28 @@ } $action = $_POST['action'] ?? ''; +$customDestroyDate = trim($_POST['custom_destroy_date'] ?? ''); +$customDestroyDate = ($customDestroyDate === '') ? null : $customDestroyDate; +$targetDeviceID = isset($_POST['target_device_id']) ? intval($_POST['target_device_id']) : 0; + +if (!function_exists('logHddManagementAction')) { + function logHddManagementAction(string $actionName, array $details = []): void { + global $deviceID, $person; + $payload = ''; + if (!empty($details)) { + $payload = json_encode($details, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + if ($payload === false) { + $payload = ''; + } + } + HDD::RecordGenericLog($deviceID, $person->UserID, $actionName, $payload); + } +} try { switch (true) { // Création d’un nouveau HDD depuis le modal - case $action === 'create_hdd_form': + case $action === 'create_hdd_form': // Récupération et sanitation des champs $serialNo = $_POST['SerialNo'] ?? ''; $typeMedia = $_POST['TypeMedia']?? ''; @@ -38,6 +55,12 @@ $hdd->TypeMedia = $typeMedia; $hdd->Size = $size; $hdd->Create(); + logHddManagementAction('HDD_CREATE', [ + 'hdd_id' => $hdd->HDDID, + 'serial' => $hdd->SerialNo, + 'type' => $hdd->TypeMedia, + 'size' => $hdd->Size + ]); break; case preg_match('/^update_(\d+)$/', $action, $m) === 1: @@ -54,50 +77,141 @@ $hdd->Size = intval($_POST['Size'][$id] ?? $hdd->Size); // Maintenant vous avez déjà StatusDestruction, Note, DateAdd, etc. $hdd->MakeSafe(); - $hdd->Update(); + if ($hdd->Update()) { + logHddManagementAction('HDD_UPDATE', [ + 'hdd_id' => $hdd->HDDID, + 'serial' => $hdd->SerialNo, + 'status' => $hdd->Status, + 'type' => $hdd->TypeMedia, + 'size' => $hdd->Size + ]); + } break; case preg_match('/^remove_(\d+)$/', $action, $m) === 1: - $hdd = HDD::GetHDDByID(intval((int)$m[1])); - if ($hdd) { - $hdd->SendForDestruction(); + $removeId = intval((int)$m[1]); + $hdd = HDD::GetHDDByID($removeId); + if ($hdd && $hdd->SendForDestruction()) { + logHddManagementAction('HDD_SEND_FOR_DESTRUCTION', [ + 'ids' => [$removeId], + 'count' => 1, + 'source' => 'single' + ]); } break; case preg_match('/^delete_(\d+)$/', $action, $m) === 1: - HDD::DeleteByID((int)$m[1]); + $deleteId = intval((int)$m[1]); + if (HDD::DeleteByID($deleteId)) { + logHddManagementAction('HDD_DELETE', [ + 'ids' => [$deleteId], + 'count' => 1 + ]); + } break; case preg_match('/^duplicate_(\d+)$/', $action, $m) === 1: - HDD::DuplicateToEmptySlots((int)$m[1]); + $sourceId = intval((int)$m[1]); + $newIds = HDD::DuplicateToEmptySlots($sourceId); + if (!empty($newIds)) { + logHddManagementAction('HDD_DUPLICATE', [ + 'source_id' => $sourceId, + 'count' => count($newIds), + 'new_ids' => $newIds + ]); + } break; case preg_match('/^destroy_(\d+)$/', $action, $m) === 1: - HDD::MarkDestroyed((int)$m[1]); + $destroyId = intval((int)$m[1]); + if (HDD::MarkDestroyed($destroyId, $customDestroyDate)) { + $details = [ + 'ids' => [$destroyId], + 'count' => 1, + 'source' => 'single' + ]; + if ($customDestroyDate) { + $details['destroy_date'] = $customDestroyDate; + } + logHddManagementAction('HDD_DESTROY', $details); + } break; case preg_match('/^reassign_(\d+)$/', $action, $m) === 1: - HDD::ReassignToDevice((int)$m[1], $deviceID); + $reassignId = intval((int)$m[1]); + $targetId = ($targetDeviceID > 0) ? $targetDeviceID : $deviceID; + if ($targetId <= 0) { + throw new Exception(__('Invalid target device for reassignment.')); + } + if ($targetDeviceID > 0 && HDD::GetRemainingSlotCount($targetId) <= 0) { + $_SESSION['LastError'] = __('slot hdd is full'); + break; + } + if (HDD::ReassignToDevice($reassignId, $targetId)) { + logHddManagementAction('HDD_REASSIGN', [ + 'hdd_id' => $reassignId, + 'target_device' => $targetId + ]); + $targetLabel = ''; + $targetDeviceObj = new Device(); + $targetDeviceObj->DeviceID = $targetId; + if ($targetDeviceObj->GetDevice()) { + $targetLabel = $targetDeviceObj->Label; + } + if ($targetDeviceID > 0 && $targetLabel !== '') { + $_SESSION['Message'] = sprintf(__('HDD transfered in (%s)'), $targetLabel); + } elseif ($targetDeviceID > 0) { + $_SESSION['Message'] = __('HDD reassigned successfully'); + } + } else { + if ($targetDeviceID > 0 && HDD::GetRemainingSlotCount($targetId) <= 0) { + $_SESSION['LastError'] = __('slot hdd is full'); + } else { + $_SESSION['LastError'] = __('Unable to reassign HDD'); + } + } break; case preg_match('/^spare_(\d+)$/', $action, $m) === 1: - HDD::MarkAsSpare((int)$m[1]); + $spareId = intval((int)$m[1]); + if (HDD::MarkAsSpare($spareId)) { + logHddManagementAction('HDD_MARK_SPARE', [ + 'hdd_id' => $spareId + ]); + } break; case $action === "bulk_remove": - // $_POST['select_active'] contient un tableau d’IDs cochés - foreach ($_POST['select_active'] ?? [] as $id) { - $id = intval($id); - // Récupère l’objet complet pour préserver ses autres propriétés - if ($hdd = HDD::GetHDDByID($id)) { - $hdd->SendForDestruction(); + $removedIds = []; + // $_POST['select_active'] contient un tableau d'IDs cochés + foreach ($_POST['select_active'] ?? [] as $id) { + $intId = intval($id); + // Récupère l'objet complet pour préserver ses autres propriétés + if ($intId > 0 && ($hdd = HDD::GetHDDByID($intId)) && $hdd->SendForDestruction()) { + $removedIds[] = $intId; } } + if (!empty($removedIds)) { + logHddManagementAction('HDD_BULK_REMOVE', [ + 'ids' => $removedIds, + 'count' => count($removedIds) + ]); + } break; case $action === "bulk_delete": + $deletedIds = []; foreach ($_POST['select_active'] ?? [] as $id) { - HDD::DeleteByID(intval($id)); + $intId = intval($id); + if ($intId > 0 && HDD::DeleteByID($intId)) { + $deletedIds[] = $intId; + } + } + if (!empty($deletedIds)) { + logHddManagementAction('HDD_BULK_DELETE', [ + 'ids' => $deletedIds, + 'count' => count($deletedIds) + ]); } break; @@ -106,17 +220,20 @@ $destroyedIds = []; foreach ($pendingSelected as $id) { $intId = intval($id); - if ($intId > 0 && HDD::MarkDestroyed($intId)) { + if ($intId > 0 && HDD::MarkDestroyed($intId, $customDestroyDate)) { $destroyedIds[] = $intId; } } if (!empty($destroyedIds)) { - $details = json_encode([ - 'ids' => $destroyedIds, - 'count' => count($destroyedIds), + $details = [ + 'ids' => $destroyedIds, + 'count' => count($destroyedIds), 'source' => 'bulk_destroy' - ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - HDD::RecordGenericLog($deviceID, $person->UserID, 'HDD_BULK_DESTROY', $details); + ]; + if ($customDestroyDate) { + $details['destroy_date'] = $customDestroyDate; + } + logHddManagementAction('HDD_BULK_DESTROY', $details); } break; @@ -125,24 +242,31 @@ $destroyedActive = []; foreach ($activeSelected as $id) { $intId = intval($id); - if ($intId > 0 && HDD::MarkDestroyed($intId)) { + if ($intId > 0 && HDD::MarkDestroyed($intId, $customDestroyDate)) { $destroyedActive[] = $intId; } } if (!empty($destroyedActive)) { - $details = json_encode([ - 'ids' => $destroyedActive, - 'count' => count($destroyedActive), + $details = [ + 'ids' => $destroyedActive, + 'count' => count($destroyedActive), 'source' => 'bulk_destroy_active' - ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - HDD::RecordGenericLog($deviceID, $person->UserID, 'HDD_BULK_DESTROY', $details); + ]; + if ($customDestroyDate) { + $details['destroy_date'] = $customDestroyDate; + } + logHddManagementAction('HDD_BULK_DESTROY', $details); } break; case $action === "export_list": - // Export XLS complet en 3 feuilles - HDD::ExportAllToXls($deviceID); - // (la méthode se termine par exit()) + // Export XLS complet en 3 feuilles + logHddManagementAction('HDD_EXPORT', [ + 'mode' => 'full_device', + 'format' => 'xls' + ]); + HDD::ExportAllToXls($deviceID); + // (la méthode se termine par exit()) break; case $action === "certify_audit": From 71289dd89a436c9be33f119d3bfc9d80ec0d2ff4 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:03:06 +0100 Subject: [PATCH 24/25] feat: expose HDD REST endpoints and doc updates --- api/docs/swagger.yaml | 303 ++++++++++++++++++++++++++----- api/v1/getRoutes.php | 212 +++++++++++++++++++++ api/v1/index.php | 3 +- api/v1/postRoutes.php | 107 +++++++++++ api/v1/putRoutes.php | 68 +++++++ classes/DeviceTemplate.class.php | 9 +- 6 files changed, 656 insertions(+), 46 deletions(-) diff --git a/api/docs/swagger.yaml b/api/docs/swagger.yaml index 8567c28cf..3ea1d799a 100644 --- a/api/docs/swagger.yaml +++ b/api/docs/swagger.yaml @@ -15,18 +15,20 @@ info: license: name: "GPLv3" basePath: "/api/v1" -tags: -- name: "openDCIM" - description: "Everything about your Data Center Inventory." - externalDocs: - description: "Find out more" - url: "http://wiki.opendcim.org" +tags: +- name: "openDCIM" + description: "Everything about your Data Center Inventory." + externalDocs: + description: "Find out more" + url: "http://wiki.opendcim.org" +- name: "HDD" + description: "HDD inventory endpoints." paths: - /audit: - get: - summary: Audit history for the requested device - tags: - - "AuditLogs" + /audit: + get: + summary: Audit history for the requested device + tags: + - "AuditLogs" description: '' produces: - "application/json" @@ -86,12 +88,166 @@ paths: type: "array" items: $ref: "#/definitions/Audit" - security: - - api_key: [] - - user_id: [] - /cabinet: - get: - summary: Information about one or more cabinets + security: + - api_key: [] + - user_id: [] + /hdd: + get: + summary: "List HDD entries" + tags: + - "HDD" + description: "Requires ManageHDD or SiteAdmin." + produces: + - "application/json" + operationId: "GetHdd" + parameters: + - name: DeviceID + in: query + required: false + type: integer + format: "DeviceID filter" + - name: HDDID + in: query + required: false + type: string + description: "Single HDDID or comma separated list." + - name: Status + in: query + required: false + type: string + description: "Status filter (single value or comma separated list)." + - name: SerialNo + in: query + required: false + type: string + description: "Partial serial number match." + responses: + "401": + description: "Access denied." + "200": + description: "successful operation" + schema: + type: "array" + items: + $ref: "#/definitions/HDD" + security: + - api_key: [] + - user_id: [] + put: + summary: "Create HDD entry" + tags: + - "HDD" + consumes: + - "application/json" + - "application/x-www-form-urlencoded" + produces: + - "application/json" + description: "Creates an HDD entry. DeviceID and SerialNo are required; ProofFile is read-only." + operationId: "PutHdd" + parameters: + - in: body + name: body + description: "New HDD payload." + required: true + schema: + $ref: "#/definitions/HDD" + responses: + "401": + description: "Access denied." + "200": + description: "successful operation" + schema: + $ref: "#/definitions/HDD" + security: + - api_key: [] + - user_id: [] + /hdd/{hddid}: + get: + summary: "Get HDD entry" + tags: + - "HDD" + produces: + - "application/json" + operationId: "GetHddById" + parameters: + - name: hddid + in: path + required: true + type: integer + format: "HDDID" + responses: + "401": + description: "Access denied." + "404": + description: "HDD not found." + "200": + description: "successful operation" + schema: + $ref: "#/definitions/HDD" + security: + - api_key: [] + - user_id: [] + post: + summary: "Update HDD entry" + tags: + - "HDD" + consumes: + - "application/json" + - "application/x-www-form-urlencoded" + produces: + - "application/json" + description: "Updates SerialNo, Size, TypeMedia, Status, or DeviceID for the specified HDD. ProofFile cannot be modified via the API." + operationId: "PostHdd" + parameters: + - name: hddid + in: path + required: true + type: integer + - in: body + name: body + required: true + schema: + $ref: "#/definitions/HDD" + responses: + "401": + description: "Access denied." + "404": + description: "HDD not found." + "200": + description: "successful operation" + schema: + $ref: "#/definitions/HDD" + security: + - api_key: [] + - user_id: [] + /hdd/{hddid}/proof: + get: + summary: "Retrieve destruction proof metadata" + tags: + - "HDD" + produces: + - "application/json" + operationId: "GetHddProof" + parameters: + - name: hddid + in: path + required: true + type: integer + responses: + "401": + description: "Access denied." + "404": + description: "Proof not available." + "200": + description: "successful operation" + schema: + $ref: "#/definitions/HDDProof" + security: + - api_key: [] + - user_id: [] + /cabinet: + get: + summary: Information about one or more cabinets tags: - "Cabinet" description: 'If no parameters are specified, all record information that you are authorized to view is returned.' @@ -1412,12 +1568,18 @@ definitions: PSCount: type: "integer" format: "Number of power inputs" - NumPorts: - type: "integer" - format: "Number of data ports" - Notes: - type: "string" - format: "Freeform text" + NumPorts: + type: "integer" + format: "Number of data ports" + EnableHDDFeature: + type: "integer" + format: "1 when the HDD management feature is enabled" + HDDCount: + type: "integer" + format: "Maximum HDD slots supported by this template" + Notes: + type: "string" + format: "Freeform text" FrontPictureFile: type: "string" format: "Relative path to the image file for the front" @@ -1605,12 +1767,15 @@ definitions: RackAdmin: type: boolean format: "User has rights to complete rack requests" - BulkOperations: - type: boolean - format: "User has rights to perform Bulk Operations" - SiteAdmin: - type: boolean - format: "User is a site administrator" + BulkOperations: + type: boolean + format: "User has rights to perform Bulk Operations" + ManageHDD: + type: boolean + format: "User has rights to manage HDD inventory" + SiteAdmin: + type: boolean + format: "User is a site administrator" APIKey: type: string format: "API Token" @@ -1659,24 +1824,80 @@ definitions: Notes: type: "string" format: "Freeform text" - SensorReading: - type: "object" - properties: - DeviceID: - type: "integer" - format: "keyValue" + SensorReading: + type: "object" + properties: + DeviceID: + type: "integer" + format: "keyValue" Temperature: type: "number" format: "Temperature in localized units" Humidity: type: "integer" format: "Humidity" - LastRead: - type: "string" - format: "Timestamp of last reading" -securityDefinitions: - api_key: - type: "apiKey" + LastRead: + type: "string" + format: "Timestamp of last reading" + HDD: + type: "object" + properties: + HDDID: + type: "integer" + format: "keyValue" + DeviceID: + type: "integer" + format: "keyValue" + SerialNo: + type: "string" + format: "Serial number" + Status: + type: "string" + enum: + - "On" + - "Off" + - "Pending_destruction" + - "Destroyed" + - "Spare" + TypeMedia: + type: "string" + format: "Media type" + Size: + type: "integer" + format: "Capacity in GB" + DateAdd: + type: "string" + format: "Timestamp" + DateWithdrawn: + type: "string" + format: "Timestamp" + DateDestroyed: + type: "string" + format: "Timestamp" + ProofFile: + type: "string" + format: "Stored proof reference" + HDDProof: + type: "object" + properties: + HDDID: + type: "integer" + SerialNo: + type: "string" + ProofFile: + type: "string" + public_url: + type: "string" + format: "URL" + filesystem_path: + type: "string" + format: "Filesystem path when accessible" + file_exists: + type: "boolean" + format: "Indicates if the file is present on disk" +securityDefinitions: + api_key: + type: "apiKey" name: "APIKey" in: "header" user_id: diff --git a/api/v1/getRoutes.php b/api/v1/getRoutes.php index 66045d287..55519487f 100644 --- a/api/v1/getRoutes.php +++ b/api/v1/getRoutes.php @@ -100,6 +100,218 @@ return $response->withJson($r, $r['errorcode']); }); +// +// URL: /api/v1/hdd +// Method: GET +// Params: Optional DeviceID, Status (single value, comma separated list, or array), SerialNo (partial match), HDDID (single value or list) +// Returns: List of HDD objects matching the filter +// +$app->get('/hdd', function(Request $request, Response $response) use ($person) { + global $dbh; + + $r = array(); + + if (!($person->ManageHDD || $person->SiteAdmin)) { + $r['error'] = true; + $r['errorcode'] = 401; + $r['message'] = __("Access Denied"); + return $response->withJson($r, $r['errorcode']); + } + + $filters = $request->getQueryParams() ?: $request->getParsedBody(); + $sql = "SELECT * FROM fac_HDD WHERE 1=1"; + $params = array(); + + if (isset($filters['DeviceID']) && intval($filters['DeviceID']) > 0) { + $sql .= " AND DeviceID = :DeviceID"; + $params[':DeviceID'] = intval($filters['DeviceID']); + } + + if (isset($filters['HDDID'])) { + $ids = $filters['HDDID']; + if (!is_array($ids)) { + $ids = array_map('trim', explode(',', $ids)); + } + $ids = array_values(array_filter(array_map('intval', $ids), function($v){ return $v > 0; })); + if (!empty($ids)) { + $placeholders = array(); + foreach ($ids as $idx => $id) { + $ph = ":hddid{$idx}"; + $placeholders[] = $ph; + $params[$ph] = $id; + } + $sql .= " AND HDDID IN (" . implode(',', $placeholders) . ")"; + } + } + + if (isset($filters['SerialNo']) && strlen(trim($filters['SerialNo'])) > 0) { + $sql .= " AND SerialNo LIKE :SerialNo"; + $params[':SerialNo'] = '%' . trim($filters['SerialNo']) . '%'; + } + + if (isset($filters['Status'])) { + $rawStatus = $filters['Status']; + if (!is_array($rawStatus)) { + $rawStatus = array_map('trim', explode(',', $rawStatus)); + } + $allowedStatus = array('On','Off','Pending_destruction','Destroyed','Spare'); + $statusPlaceholders = array(); + $statusIndex = 0; + foreach ($rawStatus as $statusValue) { + if ($statusValue === '') { + continue; + } + if (!in_array($statusValue, $allowedStatus, true)) { + continue; + } + $ph = ":status{$statusIndex}"; + $statusIndex++; + $statusPlaceholders[] = $ph; + $params[$ph] = $statusValue; + } + if (!empty($statusPlaceholders)) { + $sql .= " AND Status IN (" . implode(',', $statusPlaceholders) . ")"; + } + } + + $sql .= " ORDER BY DeviceID, HDDID"; + + $stmt = $dbh->prepare($sql); + $stmt->execute($params); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $hddList = array(); + foreach ($rows as $row) { + $hddList[] = HDD::RowToObject($row); + } + + $r['error'] = false; + $r['errorcode'] = 200; + $r['hdd'] = $hddList; + $r['filters'] = $filters; + + return $response->withJson($r, $r['errorcode']); +}); + +// +// URL: /api/v1/hdd/:hddid +// Method: GET +// Params: hddid (path) +// Returns: HDD detail for the requested identifier +// +$app->get('/hdd/{hddid}', function(Request $request, Response $response, $args) use ($person) { + if (!($person->ManageHDD || $person->SiteAdmin)) { + $r['error'] = true; + $r['errorcode'] = 401; + $r['message'] = __("Access Denied"); + return $response->withJson($r, $r['errorcode']); + } + + $hddid = intval($args['hddid']); + $hdd = HDD::GetHDDByID($hddid); + + if (!$hdd) { + $r['error'] = true; + $r['errorcode'] = 404; + $r['message'] = __("HDD not found"); + } else { + $r['error'] = false; + $r['errorcode'] = 200; + $r['hdd'] = $hdd; + } + + return $response->withJson($r, $r['errorcode']); +}); + +// +// URL: /api/v1/hdd/:hddid/proof +// Method: GET +// Params: hddid (path) +// Returns: Metadata about destruction proof for the requested HDD +// +$app->get('/hdd/{hddid}/proof', function(Request $request, Response $response, $args) use ($person, $config) { + if (!($person->ManageHDD || $person->SiteAdmin)) { + $r['error'] = true; + $r['errorcode'] = 401; + $r['message'] = __("Access Denied"); + return $response->withJson($r, $r['errorcode']); + } + + $hddid = intval($args['hddid']); + $hdd = HDD::GetHDDByID($hddid); + + if (!$hdd) { + $r['error'] = true; + $r['errorcode'] = 404; + $r['message'] = __("HDD not found"); + return $response->withJson($r, $r['errorcode']); + } + + $proofValue = trim((string)$hdd->ProofFile); + + if ($proofValue === '') { + $r['error'] = true; + $r['errorcode'] = 404; + $r['message'] = __("No destruction proof available for this HDD"); + return $response->withJson($r, $r['errorcode']); + } + + $pathSetting = $config->ParameterArray['hdd_proof_path'] ?? 'assets/files/hdd/'; + $cleanBase = rtrim($pathSetting, '/\\'); + $publicUrl = $proofValue; + + $isAbsoluteUrl = (preg_match('#^(?:[a-z]+:)?//#i', $proofValue) === 1); + $isAbsolutePath = (!$isAbsoluteUrl && (strpos($proofValue, '/') === 0 || preg_match('#^[A-Za-z]:\\\\#', $proofValue) === 1)); + + if (!$isAbsoluteUrl && !$isAbsolutePath) { + if ($cleanBase !== '') { + $publicUrl = $cleanBase . '/' . ltrim($proofValue, '/\\'); + } else { + $publicUrl = ltrim($proofValue, '/\\'); + } + } + + $projectRoot = realpath(__DIR__ . "/../.."); + if ($projectRoot === false) { + $projectRoot = dirname(dirname(__DIR__)); + } + + $filesystemPath = ''; + $fileExists = false; + + if ($isAbsoluteUrl) { + $filesystemPath = ''; + } elseif ($isAbsolutePath) { + $filesystemPath = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $proofValue); + $fileExists = is_file($filesystemPath); + } else { + $relativeBase = trim($cleanBase, '/\\'); + $relativeStored = ltrim($proofValue, '/\\'); + + if ($relativeBase !== '' && stripos($relativeStored, $relativeBase) === 0) { + $relativePath = $relativeStored; + } else { + $relativePath = ($relativeBase !== '' ? $relativeBase . '/' : '') . $relativeStored; + } + + $filesystemPath = rtrim($projectRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $relativePath); + $fileExists = is_file($filesystemPath); + } + + $r['error'] = false; + $r['errorcode'] = 200; + $r['proof'] = array( + 'HDDID' => $hddid, + 'SerialNo' => $hdd->SerialNo, + 'ProofFile' => $proofValue, + 'public_url' => $publicUrl, + 'filesystem_path' => $filesystemPath, + 'file_exists' => $fileExists + ); + + return $response->withJson($r, $r['errorcode']); +}); + // // URL: /api/v1/department // Method: GET diff --git a/api/v1/index.php b/api/v1/index.php index 2e20e07e4..26077c5f7 100644 --- a/api/v1/index.php +++ b/api/v1/index.php @@ -9,7 +9,8 @@ $loginPage = true; } - require_once( "../../facilities.inc.php" ); + require_once( "../../facilities.inc.php" ); + require_once( __DIR__ . "/../../classes/hdd.class.php" ); use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; diff --git a/api/v1/postRoutes.php b/api/v1/postRoutes.php index 333483aa1..98172882d 100644 --- a/api/v1/postRoutes.php +++ b/api/v1/postRoutes.php @@ -128,6 +128,103 @@ return $response->withJson($r, $r['errorcode']); }); +// +// URL: /api/v1/hdd/:hddid +// Method: POST +// Params: hddid (path), optional SerialNo, Status, TypeMedia, Size, DeviceID +// Returns: Updated HDD record +// +$app->post('/hdd/{hddid}', function(Request $request, Response $response, $args) use ($person) { + if (!($person->ManageHDD || $person->SiteAdmin)) { + $r['error'] = true; + $r['errorcode'] = 401; + $r['message'] = __("Access Denied"); + return $response->withJson($r, $r['errorcode']); + } + + $hddid = intval($args['hddid']); + $hdd = HDD::GetHDDByID($hddid); + + if (!$hdd) { + $r['error'] = true; + $r['errorcode'] = 404; + $r['message'] = __("HDD not found"); + return $response->withJson($r, $r['errorcode']); + } + + $payload = $request->getQueryParams() ?: $request->getParsedBody(); + $allowedStatus = array('On','Off','Pending_destruction','Destroyed','Spare'); + $fieldsUpdated = array(); + + if (isset($payload['SerialNo'])) { + $hdd->SerialNo = $payload['SerialNo']; + $fieldsUpdated['SerialNo'] = $payload['SerialNo']; + } + + if (isset($payload['Status']) && in_array($payload['Status'], $allowedStatus, true)) { + $hdd->Status = $payload['Status']; + $fieldsUpdated['Status'] = $payload['Status']; + } + + if (isset($payload['TypeMedia'])) { + $hdd->TypeMedia = $payload['TypeMedia']; + $fieldsUpdated['TypeMedia'] = $payload['TypeMedia']; + } + + if (isset($payload['Size'])) { + $hdd->Size = intval($payload['Size']); + $fieldsUpdated['Size'] = intval($payload['Size']); + } + + $reassigned = false; + if (isset($payload['DeviceID'])) { + $targetDevice = intval($payload['DeviceID']); + if ($targetDevice > 0 && $targetDevice != intval($hdd->DeviceID)) { + if (HDD::GetRemainingSlotCount($targetDevice) <= 0) { + $r['error'] = true; + $r['errorcode'] = 409; + $r['message'] = __("slot hdd is full"); + return $response->withJson($r, $r['errorcode']); + } + if (!HDD::ReassignToDevice($hddid, $targetDevice)) { + $r['error'] = true; + $r['errorcode'] = 500; + $r['message'] = __("Unable to reassign HDD to the requested device"); + return $response->withJson($r, $r['errorcode']); + } + $hdd->DeviceID = $targetDevice; + $fieldsUpdated['DeviceID'] = $targetDevice; + $reassigned = true; + } + } + + $updated = false; + if (!empty($fieldsUpdated)) { + if (!$hdd->Update()) { + $r['error'] = true; + $r['errorcode'] = 500; + $r['message'] = __("HDD update failed"); + return $response->withJson($r, $r['errorcode']); + } + $updated = true; + } + + if (!$updated && !$reassigned) { + $r['error'] = true; + $r['errorcode'] = 400; + $r['message'] = __("No valid parameters were supplied for update"); + return $response->withJson($r, $r['errorcode']); + } + + HDD::RecordGenericLog($hdd->DeviceID, $person->UserID, 'HDD_API_UPDATE', json_encode($fieldsUpdated, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + + $r['error'] = false; + $r['errorcode'] = 200; + $r['hdd'] = HDD::GetHDDByID($hddid); + + return $response->withJson($r, $r['errorcode']); +}); + // // URL: /api/v1/powerport/:deviceid // Method: POST @@ -395,13 +492,23 @@ $dt->$prop=$val; } } + $hddPayload = array(); + foreach (array('EnableHDDFeature','HDDCount') as $field) { + if (isset($vars[$field])) { + $hddPayload[$field] = $vars[$field]; + } + } if(!$dt->UpdateTemplate()){ $r['error']=true; $r['errorcode']=400; $r['message']=__("Device template update failed"); }else{ + if (!empty($hddPayload)) { + $dt->UpdateTemplateHDD($hddPayload); + } $r['error']=false; $r['errorcode']=200; + $r['devicetemplate']=$dt; } } } diff --git a/api/v1/putRoutes.php b/api/v1/putRoutes.php index 5a772d033..572be0882 100644 --- a/api/v1/putRoutes.php +++ b/api/v1/putRoutes.php @@ -130,6 +130,65 @@ }); +// +// URL: /api/v1/hdd +// Method: PUT +// Params: DeviceID (required), SerialNo (required), optional Status, TypeMedia, Size +// Returns: record as created +// +$app->put('/hdd', function(Request $request, Response $response) use ($person) { + if (!($person->ManageHDD || $person->SiteAdmin)) { + $r['error'] = true; + $r['errorcode'] = 401; + $r['message'] = __("Access Denied"); + return $response->withJson($r, $r['errorcode']); + } + + $vars = $request->getQueryParams() ?: $request->getParsedBody(); + $deviceId = isset($vars['DeviceID']) ? intval($vars['DeviceID']) : 0; + $serial = isset($vars['SerialNo']) ? trim($vars['SerialNo']) : ''; + + if ($deviceId <= 0 || $serial === '') { + $r['error'] = true; + $r['errorcode'] = 400; + $r['message'] = __("DeviceID and SerialNo are required"); + return $response->withJson($r, $r['errorcode']); + } + + if (HDD::GetRemainingSlotCount($deviceId) <= 0) { + $r['error'] = true; + $r['errorcode'] = 409; + $r['message'] = __("slot hdd is full"); + return $response->withJson($r, $r['errorcode']); + } + + $allowedStatus = array('On','Off','Pending_destruction','Destroyed','Spare'); + $status = (isset($vars['Status']) && in_array($vars['Status'], $allowedStatus, true)) ? $vars['Status'] : 'On'; + + $hdd = new HDD(); + $hdd->DeviceID = $deviceId; + $hdd->SerialNo = $serial; + $hdd->Status = $status; + $hdd->TypeMedia = isset($vars['TypeMedia']) ? $vars['TypeMedia'] : 'HDD'; + $hdd->Size = isset($vars['Size']) ? intval($vars['Size']) : 0; + + $hdd->Create(); + + $details = array( + 'HDDID' => $hdd->HDDID, + 'DeviceID' => $deviceId, + 'SerialNo' => $serial, + 'Status' => $status + ); + HDD::RecordGenericLog($deviceId, $person->UserID, 'HDD_API_CREATE', json_encode($details, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + + $r['error'] = false; + $r['errorcode'] = 200; + $r['hdd'] = $hdd; + + return $response->withJson($r, $r['errorcode']); +}); + // // URL: /api/v1/cabinet // Method: PUT @@ -520,6 +579,15 @@ $r['errorcode']=400; $r['message']=__("Device template creation failed"); }else{ + $hddPayload = array(); + foreach (array('EnableHDDFeature','HDDCount') as $field) { + if (isset($vars[$field])) { + $hddPayload[$field] = $vars[$field]; + } + } + if (!empty($hddPayload)) { + $dt->UpdateTemplateHDD($hddPayload); + } // refresh the model in case we extended it elsewhere $d=new DeviceTemplate($dt->TemplateID); $d->GetTemplateByID(); diff --git a/classes/DeviceTemplate.class.php b/classes/DeviceTemplate.class.php index d195f972f..2b8f5961c 100644 --- a/classes/DeviceTemplate.class.php +++ b/classes/DeviceTemplate.class.php @@ -757,11 +757,12 @@ static function getAvailableImages(){ return $array; } //feature manager for HDD - public function UpdateTemplateHDD() { + public function UpdateTemplateHDD(array $payload = null) { global $dbh; - $EnableHDDFeature = isset($_POST['EnableHDDFeature']) ? intval($_POST['EnableHDDFeature']) : 0; - $HDDCount = isset($_POST['HDDCount']) ? intval($_POST['HDDCount']) : 0; + $source = ($payload === null) ? $_POST : $payload; + $EnableHDDFeature = isset($source['EnableHDDFeature']) ? intval($source['EnableHDDFeature']) : 0; + $HDDCount = isset($source['HDDCount']) ? intval($source['HDDCount']) : 0; $check = $dbh->prepare("SELECT COUNT(*) FROM fac_DeviceTemplateHdd WHERE TemplateID = ?"); $check->execute([$this->TemplateID]); @@ -822,4 +823,4 @@ public function ExportTemplateHDD($asJSON = false) { } } -?> \ No newline at end of file +?> From 675a23e8c90cd3f10cafbaefd193dd00ace88c22 Mon Sep 17 00:00:00 2001 From: Alexandre <146840804+alex001x@users.noreply.github.com> Date: Mon, 1 Dec 2025 19:07:52 +0100 Subject: [PATCH 25/25] docs: add HDD API section --- readme_feat_Hdd.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/readme_feat_Hdd.txt b/readme_feat_Hdd.txt index de34812ff..02d35d802 100644 --- a/readme_feat_Hdd.txt +++ b/readme_feat_Hdd.txt @@ -45,3 +45,28 @@ ## 6. Points de vigilance / �volutions pr�vues - Les CSV doivent contenir au moins une colonne SN ; chaque colonne est analys�e apr�s upload (d�limiteurs auto : , ; tab |). - Les futures �volutions pr�vues incluent l�import natif d�OCS Inventory pour afficher l��tat des disques (On/Off), faciliter maintenance/destruction et int�grer un flux 100% automatis�. +## 7. API REST HDD +Pour prparer lautomatisation (OCS ou autres), quatre routes REST ont t ajoutes. Toutes ncessitent le droit `ManageHDD` (ou `SiteAdmin`) et les en-ttes dauthentification habituels. + +### 7.1 GET /api/v1/hdd +- Paramtres optionnels : `DeviceID`, `HDDID` (valeur ou liste spare par virgules), `Status` (On, Off, Pending_destruction, Destroyed, Spare), `SerialNo` (recherche partielle). +- Retour : tableau de modles `HDD`. + +### 7.2 GET /api/v1/hdd/{HDDID} +- Retour : dtail du disque cibl. + +### 7.3 GET /api/v1/hdd/{HDDID}/proof +- Retour : JSON avec `ProofFile`, URL publique et chemin disque si le fichier existe. Aucun upload via API (GET uniquement). + +### 7.4 PUT /api/v1/hdd +- Cre un disque sur un quipement. Champs requis : `DeviceID`, `SerialNo`. Champs optionnels : `Status`, `TypeMedia`, `Size`. +- Contrle automatique du nombre de slots (message slot hdd is full si le device est plein). + +### 7.5 POST /api/v1/hdd/{HDDID} +- Met jour SerialNo/Status/TypeMedia/Size ou raffecte le disque un autre DeviceID (slots vrifis). + +### 7.6 DeviceTemplate & People +- `DeviceTemplate` expose dsormais `EnableHDDFeature` et `HDDCount` via GET/POST/PUT. +- `People` expose le boolen `ManageHDD` pour activer laccs API. + +La description complte (modles, exemples) est disponible dans `api/docs/swagger.yaml`, section `HDD`.