Refactor software version update logic into a reusable function; enhance UI for file uploads and table responsiveness

This commit is contained in:
“VeLiTi”
2025-12-16 16:18:24 +01:00
parent a9f623cf22
commit 3693b52886
7 changed files with 243 additions and 72 deletions

View File

@@ -161,25 +161,8 @@ if ($sw_version_latest_update == 1){
//------------------------------------------ //------------------------------------------
//UPDATE SW_STATUS //UPDATE SW_STATUS
//------------------------------------------ //------------------------------------------
//UPDATE ASSETS-> SW_LATEST_VERSION WITH NO PRODUCT_SOFTWARE TO 2 // Use the reusable function to update software version status for all equipment
$sql = 'UPDATE equipment e LEFT JOIN products_software ps ON e.productrowid = ps.productrowid SET e.sw_version_latest = 2 WHERE ps.rowID IS NULL'; updateSoftwareVersionStatus($pdo);
$stmt = $pdo->prepare($sql);
$stmt->execute();
//UPDATE ASSETS-> SW_LATEST_VERSION WITH PRODUCT_SOFTWARE FROM 2 TO 0
$sql = 'UPDATE equipment e LEFT JOIN products_software ps ON e.productrowid = ps.productrowid SET e.sw_version_latest = 0 WHERE ps.rowID IS NOT NULL AND sw_version_latest = 2';
$stmt = $pdo->prepare($sql);
$stmt->execute();
//UPDATE LATEST TO NO IN CASE HW_VERSION ARE EQUAL AND SW_VERSIONS NOT AND NOT LATEST
$sql = 'UPDATE equipment e JOIN products_software ps ON e.productrowid = ps.productrowid SET e.sw_version_latest = 0 WHERE ps.latest = 1 AND lower(e.sw_version) <> lower(ps.version) AND lower(e.hw_version) = lower(ps.hw_version) AND e.sw_version_latest = 1';
$stmt = $pdo->prepare($sql);
$stmt->execute();
//UPDATE LATEST TO YES IN CASE HW_VERSION ARE EQUAL AND SW_VERSIONS ARE EQUAL
$sql = 'UPDATE equipment e JOIN products_software ps ON e.productrowid = ps.productrowid SET e.sw_version_latest = 1 WHERE ps.latest = 1 AND lower(e.sw_version) = lower(ps.version) AND lower(e.hw_version) = lower(ps.hw_version) AND e.sw_version_latest = 0';
$stmt = $pdo->prepare($sql);
$stmt->execute();
//------------------------------------------ //------------------------------------------
//------------------------------------------ //------------------------------------------
} }

View File

@@ -5111,4 +5111,84 @@ function checkAndInsertIdentityDealer($pdo, $identityUserkey) {
error_log('Database error in checkAndInsertIdentityDealer: ' . $e->getMessage()); error_log('Database error in checkAndInsertIdentityDealer: ' . $e->getMessage());
return false; return false;
} }
}
//------------------------------------------
// Update software version status for equipment
// Parameters:
// $pdo - PDO database connection
// $serialnumber - Optional serial number. If null, updates all equipment
// Returns: boolean - true on success, false on error
//------------------------------------------
function updateSoftwareVersionStatus($pdo, $serialnumber = null) {
try {
// Build WHERE clause for serial number filtering if provided
$sn_clause = '';
$bind_params = [];
if ($serialnumber !== null) {
$sn_clause = ' AND e.serialnumber = ?';
$bind_params = [$serialnumber];
}
//------------------------------------------
// STEP 1: Set sw_version_latest = 2 for equipment with NO software assignments
//------------------------------------------
$sql = 'UPDATE equipment e
LEFT JOIN products_software_assignment psa ON e.productrowid = psa.product_id AND psa.status = 1
SET e.sw_version_latest = 2
WHERE psa.product_id IS NULL' . $sn_clause;
$stmt = $pdo->prepare($sql);
$stmt->execute($bind_params);
//------------------------------------------
// STEP 2: Set sw_version_latest = 0 for equipment WITH software assignments (from previous 2)
//------------------------------------------
$sql = 'UPDATE equipment e
JOIN products_software_assignment psa ON e.productrowid = psa.product_id AND psa.status = 1
SET e.sw_version_latest = 0
WHERE e.sw_version_latest = 2' . $sn_clause;
$stmt = $pdo->prepare($sql);
$stmt->execute($bind_params);
//------------------------------------------
// STEP 3: Set sw_version_latest = 0 for equipment NOT matching latest version
//------------------------------------------
$sql = 'UPDATE equipment e
JOIN products_software_assignment psa ON e.productrowid = psa.product_id AND psa.status = 1
JOIN products_software_versions psv ON psa.software_version_id = psv.rowID
SET e.sw_version_latest = 0
WHERE psv.latest = 1
AND psv.status = 1
AND lower(e.sw_version) <> lower(psv.version)
AND (psv.hw_version = e.hw_version OR psv.hw_version IS NULL OR psv.hw_version = "")
AND e.sw_version_latest = 1' . $sn_clause;
$stmt = $pdo->prepare($sql);
$stmt->execute($bind_params);
//------------------------------------------
// STEP 4: Set sw_version_latest = 1 for equipment matching latest version
//------------------------------------------
$sql = 'UPDATE equipment e
JOIN products_software_assignment psa ON e.productrowid = psa.product_id AND psa.status = 1
JOIN products_software_versions psv ON psa.software_version_id = psv.rowID
SET e.sw_version_latest = 1
WHERE psv.latest = 1
AND psv.status = 1
AND lower(e.sw_version) = lower(psv.version)
AND (psv.hw_version = e.hw_version OR psv.hw_version IS NULL OR psv.hw_version = "")
AND e.sw_version_latest = 0' . $sn_clause;
$stmt = $pdo->prepare($sql);
$stmt->execute($bind_params);
return true;
} catch (PDOException $e) {
error_log('Database error in updateSoftwareVersionStatus: ' . $e->getMessage());
return false;
}
} }

View File

@@ -44,7 +44,7 @@ $filter_version_id = $_GET['from_version_id'] ?? $_GET['to_version_id'] ?? $_GET
if (isset($_GET['id']) && $_GET['id'] != '') { if (isset($_GET['id']) && $_GET['id'] != '') {
$api_url = '/v2/products_software_upgrade_paths/rowID=' . $_GET['id']; $api_url = '/v2/products_software_upgrade_paths/rowID=' . $_GET['id'];
$response = ioServer($api_url, ''); $response = ioServer($api_url, '');
var_dump($response);
if (!empty($response)) { if (!empty($response)) {
$existing = json_decode($response); $existing = json_decode($response);
if (!empty($existing)) { if (!empty($existing)) {
@@ -145,7 +145,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$api_url = '/v2/products_software_upgrade_paths/'; $api_url = '/v2/products_software_upgrade_paths/';
$result = ioServer($api_url, json_encode($data)); $result = ioServer($api_url, json_encode($data));
if ($result) { if ($result !== 'NOK') {
$success = isset($_POST['delete']) ? 3 : (isset($_POST['rowID']) && $_POST['rowID'] != '' ? 2 : 1); $success = isset($_POST['delete']) ? 3 : (isset($_POST['rowID']) && $_POST['rowID'] != '' ? 2 : 1);
header('Location: ' . $url . '&success_msg=' . $success); header('Location: ' . $url . '&success_msg=' . $success);
exit; exit;

View File

@@ -143,7 +143,6 @@ $view = '
<th>Currency</th> <th>Currency</th>
<th>Description</th> <th>Description</th>
<th>Active</th> <th>Active</th>
<th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -151,18 +150,17 @@ $view = '
$all_paths = array_merge($upgrade_paths_from ?: [], $upgrade_paths_to ?: []); $all_paths = array_merge($upgrade_paths_from ?: [], $upgrade_paths_to ?: []);
if (empty($all_paths)){ if (empty($all_paths)){
$view .= '<tr><td colspan="7">No upgrade paths found.</td></tr>'; $view .= '<tr><td colspan="6">No upgrade paths found.</td></tr>';
} else { } else {
foreach ($all_paths as $path){ foreach ($all_paths as $path){
$view .= ' $view .= '
<tr> <tr onclick="window.location.href=\'index.php?page=products_software_upgrade_paths_manage&id='.$path->rowID.'\'" style="cursor: pointer;">
<td>' . ($version_map[$path->from_version_id] ?? $path->from_version_id) . '</td> <td>' . ($version_map[$path->from_version_id] ?? $path->from_version_id) . '</td>
<td>' . ($version_map[$path->to_version_id] ?? $path->to_version_id) . '</td> <td>' . ($version_map[$path->to_version_id] ?? $path->to_version_id) . '</td>
<td>'.$path->price.'</td> <td>'.$path->price.'</td>
<td>'.$path->currency.'</td> <td>'.$path->currency.'</td>
<td>'.$path->description.'</td> <td>'.$path->description.'</td>
<td>'.($path->is_active ? 'Yes' : 'No').'</td> <td>'.($path->is_active ? 'Yes' : 'No').'</td>
<td><a href="index.php?page=products_software_upgrade_paths_manage&id='.$path->rowID.'" class="btn_link">Edit</a></td>
</tr> </tr>
'; ';
} }

View File

@@ -104,7 +104,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$api_url = '/v2/products_software_versions/'; $api_url = '/v2/products_software_versions/';
$result = ioServer($api_url, json_encode($data)); $result = ioServer($api_url, json_encode($data));
if ($result) { if ($result !== 'NOK') {
$success = isset($_POST['delete']) ? 3 : (isset($_POST['rowID']) && $_POST['rowID'] != '' ? 2 : 1); $success = isset($_POST['delete']) ? 3 : (isset($_POST['rowID']) && $_POST['rowID'] != '' ? 2 : 1);
header('Location: ' . $url . '&success_msg=' . $success); header('Location: ' . $url . '&success_msg=' . $success);
exit; exit;
@@ -147,7 +147,14 @@ $view .= '<div class="content-block">
<label for="hw_version">HW Version</label> <label for="hw_version">HW Version</label>
<input id="hw_version" type="text" name="hw_version" placeholder="HW Version" value="' . htmlspecialchars($version['hw_version']) . '"> <input id="hw_version" type="text" name="hw_version" placeholder="HW Version" value="' . htmlspecialchars($version['hw_version']) . '">
<label for="fileToUpload">Upload File</label> <label for="fileToUpload">Upload File</label>
<input type="file" name="fileToUpload" id="fileToUpload" onchange="updateFields()"> <div class="file-upload-wrapper">
<button type="button" class="file-upload-btn" onclick="document.getElementById(\'fileToUpload\').click()">
<i class="fas fa-cloud-upload-alt"></i>
<span id="uploadBtnText">Choose File</span>
</button>
<input type="file" name="fileToUpload" id="fileToUpload" style="display: none;" onchange="updateFields()" accept=".hex,.bin,.fw">
<span class="file-upload-info" id="fileUploadInfo">No file selected</span>
</div>
<label for="file_path">File Path</label> <label for="file_path">File Path</label>
<input id="file_path" type="text" name="file_path" placeholder="File Path" value="' . htmlspecialchars($version['file_path']) . '" readonly> <input id="file_path" type="text" name="file_path" placeholder="File Path" value="' . htmlspecialchars($version['file_path']) . '" readonly>
<label class="checkbox"> <label class="checkbox">
@@ -164,7 +171,10 @@ $view .= '<div class="content-block">
<script> <script>
function updateFields() { function updateFields() {
var fileInput = document.getElementById(\'fileToUpload\'); var fileInput = document.getElementById(\'fileToUpload\');
var uploadBtnText = document.getElementById(\'uploadBtnText\');
var fileUploadInfo = document.getElementById(\'fileUploadInfo\');
var file = fileInput.files[0]; var file = fileInput.files[0];
if (file) { if (file) {
var fileName = file.name; var fileName = file.name;
var filePathInput = document.getElementById(\'file_path\'); var filePathInput = document.getElementById(\'file_path\');
@@ -175,8 +185,25 @@ function updateFields() {
var nameWithoutExt = fileName.replace(/\.[^/.]+$/, ""); var nameWithoutExt = fileName.replace(/\.[^/.]+$/, "");
versionInput.value = nameWithoutExt; versionInput.value = nameWithoutExt;
} }
// Update button and info text
uploadBtnText.textContent = \'Change File\';
fileUploadInfo.textContent = fileName + \' (\' + formatFileSize(file.size) + \')\';
fileUploadInfo.style.color = \'#28a745\';
} else {
uploadBtnText.textContent = \'Choose File\';
fileUploadInfo.textContent = \'No file selected\';
fileUploadInfo.style.color = \'#6c757d\';
} }
} }
function formatFileSize(bytes) {
if (bytes === 0) return \'0 Bytes\';
const k = 1024;
const sizes = [\'Bytes\', \'KB\', \'MB\', \'GB\'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + \' \' + sizes[i];
}
</script> </script>
'; ';

View File

@@ -298,59 +298,63 @@ $view .= '</form>';
// Add basic JavaScript functionality // Add basic JavaScript functionality
$view .= ' $view .= '
<style> <style>
.profile-content { display: none !important; } .profile-content { display: none; }
.profile-content.active { display: block !important; }
.profile-tab { background: #f8f9fa; color: #333; } .profile-tab { background: #f8f9fa; color: #333; }
.profile-tab.active { background: #007bff; color: white; } .profile-tab.active { background: #007bff; color: white; }
</style> </style>
<script> <script>
document.addEventListener("DOMContentLoaded", function() { // Use the working tab functionality from admin.js
document.querySelectorAll(\'.profile-tab\').forEach((element, index) => {
// Ensure all tabs start inactive element.onclick = event => {
document.querySelectorAll(".profile-tab").forEach(tab => { event.preventDefault();
tab.classList.remove("active");
});
document.querySelectorAll(".profile-content").forEach(content => {
content.style.display = "none";
});
// Tab functionality
document.addEventListener("click", function(e) {
if (e.target.classList.contains("profile-tab")) {
e.preventDefault();
// Hide all content
document.querySelectorAll(".profile-content").forEach(content => {
content.style.display = "none";
});
// Remove active class from all tabs
document.querySelectorAll(".profile-tab").forEach(t => {
t.classList.remove("active");
});
// Show selected content and mark tab as active
const profileId = e.target.dataset.profile;
const content = document.getElementById(profileId + "-content");
if (content) {
content.style.display = "block";
e.target.classList.add("active");
}
}
// Bulk select functionality // Toggle the clicked tab
if (e.target.classList.contains("select-all")) { const isActive = element.classList.contains(\'active\');
const profile = e.target.dataset.profile; const profileId = element.dataset.profile;
const checkboxes = document.querySelectorAll("[name=\\"" + profile + "[]\\"]"); const tabContent = document.getElementById(profileId + \'-content\');
checkboxes.forEach(cb => cb.checked = true);
}
if (e.target.classList.contains("select-none")) { // Remove active class from all tabs and contents
const profile = e.target.dataset.profile; document.querySelectorAll(\'.profile-tab\').forEach(el => el.classList.remove(\'active\'));
const checkboxes = document.querySelectorAll("[name=\\"" + profile + "[]\\"]"); document.querySelectorAll(\'.profile-content\').forEach(content => {
checkboxes.forEach(cb => cb.checked = false); content.classList.remove(\'active\');
content.style.display = \'none\';
});
// If it wasn\'t active, make it active (collapsible behavior)
if (!isActive && tabContent) {
element.classList.add(\'active\');
tabContent.classList.add(\'active\');
tabContent.style.display = \'block\';
} }
}); };
});
// Initialize first tab as open by default
if (document.querySelectorAll(\'.profile-tab\').length > 0) {
const firstTab = document.querySelectorAll(\'.profile-tab\')[0];
const profileId = firstTab.dataset.profile;
const firstContent = document.getElementById(profileId + \'-content\');
if (firstTab && firstContent) {
firstTab.classList.add(\'active\');
firstContent.classList.add(\'active\');
firstContent.style.display = \'block\';
}
}
// Bulk select functionality
document.addEventListener("click", function(e) {
if (e.target.classList.contains("select-all")) {
const profile = e.target.dataset.profile;
const checkboxes = document.querySelectorAll("[name=\\"" + profile + "[]\\"]");
checkboxes.forEach(cb => cb.checked = true);
}
if (e.target.classList.contains("select-none")) {
const profile = e.target.dataset.profile;
const checkboxes = document.querySelectorAll("[name=\\"" + profile + "[]\\"]");
checkboxes.forEach(cb => cb.checked = false);
}
}); });
</script>'; </script>';

View File

@@ -3006,4 +3006,83 @@ main .products .product .price, main .products .products-wrapper .product .price
.filter-actions { .filter-actions {
justify-content: center; justify-content: center;
} }
/* Fix table scrolling on smaller screens */
main .content-block {
overflow: visible !important;
padding: 5px;
}
main .content-block .table {
overflow-x: auto !important;
overflow-y: visible !important;
-webkit-overflow-scrolling: touch;
max-width: 100%;
margin: 0 -10px; /* Extend to container edges */
padding: 0 10px;
}
main .content-block .table table {
min-width: 700px; /* Ensure table maintains minimum width */
width: max-content; /* Allow table to expand naturally */
}
/* Ensure table cells don't wrap */
main .content-block .table table td,
main .content-block .table table th {
white-space: nowrap;
min-width: 80px;
}
/* Make version columns wider as they contain longer text */
main .content-block .table table th:first-child,
main .content-block .table table td:first-child,
main .content-block .table table th:nth-child(2),
main .content-block .table table td:nth-child(2) {
min-width: 120px;
}
}
/* File Upload Button Styles */
.file-upload-wrapper {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 15px;
}
.file-upload-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 10px;
color: white;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0, 123, 255, 0.2);
}
.file-upload-btn:hover {
background: linear-gradient(135deg, #0056b3, #004085);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3);
}
.file-upload-btn:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0, 123, 255, 0.2);
}
.file-upload-btn i {
font-size: 12px;
}
.file-upload-info {
font-size: 12px;
color: #6c757d;
font-style: italic;
} }