190 lines
6.0 KiB
PHP
190 lines
6.0 KiB
PHP
<?php
|
||
// Function to fetch SSD information from the API
|
||
function fetchSSDData() {
|
||
$url = 'http://172.17.0.1:5000/drives';
|
||
$options = [
|
||
'http' => [
|
||
'header' => "Content-type: application/json\r\n",
|
||
'method' => 'GET',
|
||
],
|
||
];
|
||
|
||
$context = stream_context_create($options);
|
||
$result = file_get_contents($url, false, $context);
|
||
|
||
if ($result === FALSE) {
|
||
die('Error Fetching data');
|
||
}
|
||
|
||
return json_decode($result, true); // Decode JSON as an associative array
|
||
}
|
||
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>SSD Health Dashboard</title>
|
||
<link rel="stylesheet" href="styles.css">
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<button onclick="window.location.reload();" class="title-button"><h2>SSD Health Dashboard</h2></button><br>
|
||
This is a historical list of every disk ever scanned by this device.<p>
|
||
For a live dashboard, please visit <a href=/>home</a>.
|
||
</div>
|
||
<div class="container">
|
||
Search for disk:<br>
|
||
<input id="search" type="text" placeholder="Search by ID, model, serial…" /><p>
|
||
<?php
|
||
$ssdData = fetchSSDData();
|
||
echo '<table class="ssd-list" style="border-collapse:collapse;width:100%;">';
|
||
echo '<thead>
|
||
<tr>
|
||
<th data-sort="id">Disk ID</th>
|
||
<th data-sort="model">Model String</th>
|
||
<th data-sort="serial">Serial Number</th>
|
||
<th data-sort="gb_written">GB Written</th>
|
||
<th data-sort="capacity">Disk Capacity</th>
|
||
<th data-sort="flavor">Disk Flavor</th>
|
||
<th data-sort="smart">SMART Result</th>
|
||
</tr>
|
||
</thead>';
|
||
echo '<tbody id="ssd-body">';
|
||
foreach ($ssdData as $ssd) {
|
||
// Escape the values so the page stays safe
|
||
$id = htmlspecialchars($ssd['id']);
|
||
$model = htmlspecialchars($ssd['model']);
|
||
$serial = htmlspecialchars($ssd['serial']);
|
||
$gbw = htmlspecialchars($ssd['gb_written']);
|
||
$cap = htmlspecialchars($ssd['capacity']);
|
||
$flavor = htmlspecialchars($ssd['flavor']);
|
||
$smart = htmlspecialchars($ssd['smart']);
|
||
|
||
echo "<tr>
|
||
<td>{$id}</td>
|
||
<td>{$model}</td>
|
||
<td>{$serial}</td>
|
||
<td>{$gbw}</td>
|
||
<td>{$cap}</td>
|
||
<td>{$flavor}</td>
|
||
<td>{$smart}</td>
|
||
</tr>";
|
||
}
|
||
echo '</tbody></table>';
|
||
?>
|
||
</div>
|
||
|
||
</body>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const searchInput = document.getElementById('search');
|
||
const tbody = document.getElementById('ssd-body');
|
||
const rows = Array.from(tbody.rows); // snapshot of rows
|
||
|
||
// Optional: a simple debounce so we don't react on every keystroke
|
||
const debounce = (fn, delay) => {
|
||
let timer;
|
||
return (...args) => {
|
||
clearTimeout(timer);
|
||
timer = setTimeout(() => fn.apply(this, args), delay);
|
||
};
|
||
};
|
||
|
||
const filterRows = debounce(() => {
|
||
const query = searchInput.value.trim().toLowerCase();
|
||
rows.forEach(row => {
|
||
// Grab all cells in this row as a single string
|
||
const rowText = Array.from(row.cells)
|
||
.map(cell => cell.textContent)
|
||
.join(' ')
|
||
.toLowerCase();
|
||
|
||
row.style.display = rowText.includes(query) ? '' : 'none';
|
||
});
|
||
}, 200); // 200ms debounce
|
||
|
||
searchInput.addEventListener('input', filterRows);
|
||
});
|
||
</script>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
|
||
/* ----- Search filter ----- */
|
||
const searchInput = document.getElementById('search');
|
||
const tbody = document.getElementById('ssd-body');
|
||
const rowsSnapshot = Array.from(tbody.rows); // keep a static snapshot
|
||
|
||
const debounce = (fn, delay) => {
|
||
let timer;
|
||
return (...args) => {
|
||
clearTimeout(timer);
|
||
timer = setTimeout(() => fn.apply(this, args), delay);
|
||
};
|
||
};
|
||
|
||
const filterRows = debounce(() => {
|
||
const q = searchInput.value.trim().toLowerCase();
|
||
rowsSnapshot.forEach(r => {
|
||
const rowText = Array.from(r.cells)
|
||
.map(c => c.textContent.trim().toLowerCase())
|
||
.join(' ');
|
||
r.style.display = rowText.includes(q) ? '' : 'none';
|
||
});
|
||
}, 200);
|
||
|
||
searchInput.addEventListener('input', filterRows);
|
||
|
||
/* ----- Table sorting ----- */
|
||
const table = document.querySelector('.ssd-list');
|
||
const headerCells = table.querySelectorAll('th[data-sort]');
|
||
const bodyRows = Array.from(tbody.rows);
|
||
|
||
headerCells.forEach((th, index) => {
|
||
th.classList.add('sortable'); // gives the arrow styling
|
||
th.style.cursor = 'pointer';
|
||
|
||
th.addEventListener('click', () => {
|
||
const currentSort = th.dataset.currentSort || 'none';
|
||
const ascending = currentSort !== 'asc';
|
||
|
||
// reset other headers
|
||
headerCells.forEach(h => {
|
||
h.dataset.currentSort = 'none';
|
||
h.classList.remove('asc', 'desc');
|
||
});
|
||
|
||
// set current header state
|
||
th.dataset.currentSort = ascending ? 'asc' : 'desc';
|
||
th.classList.toggle('asc', ascending);
|
||
th.classList.toggle('desc', !ascending);
|
||
|
||
// sort
|
||
bodyRows.sort((a, b) => {
|
||
const aText = a.cells[index].textContent.trim();
|
||
const bText = b.cells[index].textContent.trim();
|
||
|
||
// try numeric comparison
|
||
const aNum = parseFloat(aText.replace(/,/g, ''));
|
||
const bNum = parseFloat(bText.replace(/,/g, ''));
|
||
|
||
if (!isNaN(aNum) && !isNaN(bNum)) {
|
||
return ascending ? aNum - bNum : bNum - aNum;
|
||
}
|
||
|
||
// fallback to localeCompare (case‑insensitive)
|
||
return ascending
|
||
? aText.localeCompare(bText, undefined, { sensitivity: 'base' })
|
||
: bText.localeCompare(aText, undefined, { sensitivity: 'base' });
|
||
});
|
||
|
||
// re‑attach sorted rows
|
||
bodyRows.forEach(r => tbody.appendChild(r));
|
||
});
|
||
});
|
||
});
|
||
</script>
|
||
|
||
</html>
|