cosmostat has working drive health dashboard

This commit is contained in:
2026-04-19 14:23:32 -07:00
parent c6007d9c33
commit 46d9f86d55
48 changed files with 4295 additions and 257 deletions

View File

@ -0,0 +1,324 @@
<?php
declare(strict_types=1);
/* ---------- Utility functions ---------- */
function h(string $s): string
{
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
function loadYaml(string $path): array
{
if (!file_exists($path)) return [];
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$data = [];
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || $line[0] === '#') continue;
$pos = strpos($line, ':');
if ($pos === false) continue;
$key = trim(substr($line, 0, $pos));
$value = trim(substr($line, $pos + 1));
$value = trim($value, "\"'");
$data[$key] = ($value === '') ? null : $value;
}
return $data;
}
/* ---------- Load settings ---------- */
$settingsPath = '/app/cosmostat_settings.yaml';
$settings = loadYaml($settingsPath);
/* ---------- Determine mode ---------- */
$mode = $_GET['mode'] ?? 'cosmostat';
$validModes = ['cosmostat', 'gali', 'drive_health'];
if (!in_array($mode, $validModes, true)) {
$mode = 'cosmostat';
}
/* ---------- API configuration per mode ---------- */
$apiConfig = [
'cosmostat' => ['bind' => '10.200.27.20', 'port' => '5000'],
'gali' => ['bind' => '10.200.27.20', 'port' => '5000'], // same as cosmostat
'drive_health' => ['bind' => '172.25.1.18', 'port' => '5001'], // new API
];
/* ---------- Helper: fetch client details ---------- */
function fetchClientDetails(string $bindIp, string $port, string $path = '/client_details'): array
{
$url = "http://{$bindIp}:{$port}{$path}";
$ctx = stream_context_create([
'http' => [
'timeout' => 5,
'header' => "User-Agent: PHP/" . PHP_VERSION . "\r\n"
]
]);
$json = @file_get_contents($url, false, $ctx);
if ($json === false) {
return [];
}
$data = json_decode($json, true);
return (is_array($data)) ? $data : [];
}
/* ---------- Fetch client details ---------- */
$apiInfo = $apiConfig[$mode];
$clients = fetchClientDetails($apiInfo['bind'], $apiInfo['port']);
/* ---------- Handle empty / error ---------- */
if ($clients === []) {
die('<p style="color:red;">Could not retrieve data from the API for mode "' . h($mode) . '".</p>');
}
/* ---------- Ensure each client has a short_id ---------- */
foreach ($clients as &$client) {
if (!isset($client['short_id'])) {
$client['short_id'] = substr($client['uuid'] ?? '', 0, 8);
}
}
unset($client);
/* ---------- Determine selected hosts (Drive Health only) ---------- */
$selectedHosts = $_GET['hosts'] ?? []; // default: whatever was submitted
if ($mode === 'drive_health') {
if (isset($_GET['action'])) {
switch ($_GET['action']) {
case 'all':
// add every clients short_id
$selectedHosts = array_column($clients, 'short_id');
break;
case 'none':
$selectedHosts = []; // nothing selected
break;
// 'apply' does nothing extra the array above already contains the chosen hosts
}
}
}
/* ---------- Determine selected host for other modes ---------- */
$selectedId = $_GET['host'] ?? '';
$selectedIdx = null;
foreach ($clients as $idx => $client) {
if (isset($client['short_id']) && $client['short_id'] === $selectedId) {
$selectedIdx = $idx;
break;
}
}
if ($selectedIdx === null) {
$selectedIdx = 0;
$selectedId = $clients[$selectedIdx]['short_id'] ?? '';
}
$client = $clients[$selectedIdx] ?? null;
$properties = $client['client_properties'][0] ?? [];
$systemProperties = $properties['system_properties'] ?? [];
$systemComponents = $properties['system_components'] ?? [];
$selectedHost = $clients[$selectedIdx]['hostname'] ?? 'Unknown';
/* ---- Sidebar Renderer ---- */
function renderSidebar(string $mode)
{
global $clients, $client, $properties, $systemProperties, $systemComponents, $selectedHost, $selectedId, $selectedIdx, $selectedHosts;
$modes = [
'cosmostat' => 'Cosmostat',
'gali' => 'Shuttle Gali',
'drive_health' => 'Drive Health',
];
?>
<nav class="sidebar">
<form method="get" id="modeForm">
<label for="modeSelect">Mode:</label>
<select name="mode" id="modeSelect" onchange="this.form.submit()">
<?php foreach ($modes as $key => $label): ?>
<option value="<?= h($key) ?>" <?= $mode === $key ? 'selected' : '' ?>><?= h($label) ?></option>
<?php endforeach; ?>
</select>
</form>
<?php if ($mode === 'drive_health'): ?>
<h3>Hosts</h3>
<form method="get" id="driveHealthForm">
<input type="hidden" name="mode" value="drive_health">
<ul>
<?php foreach ($clients as $c): ?>
<?php $id = $c['short_id']; ?>
<li>
<label>
<input type="checkbox" name="hosts[]" value="<?= h($id) ?>"
<?= in_array($id, $selectedHosts, true) ? 'checked' : '' ?>>
<?= h($c['name']) ?>
</label>
</li>
<?php endforeach; ?>
</ul>
<button type="submit" name="action" value="apply">Apply</button>
<button type="submit" name="action" value="all">Select All</button>
<button type="submit" name="action" value="none">Select None</button>
</form>
<?php endif; ?>
<?php if ($mode == 'cosmostat'): ?>
<input type="hidden" name="host" value="<?= h($selectedId) ?>">
<h3>Endpoints</h3>
<ol id="endpointList"></ol>
<?php endif; ?>
<?php if ($mode == 'gali'): ?>
<h3>Shuttle Gali</h3>
<?php endif; ?>
</nav>
<?php
}
/* ---- Main Content Renderer ---- */
function renderMainContent(string $mode)
{
global $clients, $client, $properties, $systemProperties, $systemComponents,
$selectedHost, $selectedId, $selectedIdx, $selectedHosts;
if ($mode === 'drive_health') {
// If nothing is selected, show a friendly message
if (empty($selectedHosts)) {
echo '<div class="card"><p>No hosts selected.</p></div>';
return;
}
foreach ($selectedHosts as $sid) {
// Find the client that matches this short_id
$c = null;
foreach ($clients as $cl) {
if ($cl['short_id'] === $sid) {
$c = $cl;
break;
}
}
if ($c === null) continue; // safety
$hostname = $c['name'] ?? 'Unknown';
echo '<div class="card">';
echo '<h2>Drive Health - ' . h($hostname) . '</h2>';
if (isset($c['drives']) && is_array($c['drives']) && count($c['drives']) > 0) {
echo '<table id="host_metrics_table">';
echo '<thead><tr><th>Drive Letter</th><th>Model</th><th>Capacity</th><th>Power On Hours</th><th>Host Writes</th><th>Wear Level</th></tr></thead>';
echo '<tbody>';
foreach ($c['drives'] as $drive) {
echo '<tr>';
echo '<td>' . h($drive['drive_letter'] ?? '') . '</td>';
echo '<td>' . h($drive['model'] ?? '') . '</td>';
echo '<td>' . h($drive['capacity'] ?? '') . '</td>';
echo '<td>' . h($drive['power_on_hours'] ?? '') . '</td>';
echo '<td>' . h($drive['host_writes'] ?? '') . '</td>';
echo '<td>' . h($drive['wear_level'] ?? '') . '</td>';
echo '</tr>';
}
echo '</tbody></table>';
} else {
echo '<p>No drive data available for this host.</p>';
}
echo '</div>';
}
return;
}
/* ---------- Other modes ---------- */
?>
<div class="main">
<?php if ($mode == 'cosmostat'): ?>
<!-- Header Card -->
<div class="card">
<h2>Matt-Cloud Cosmostat Dashboard</h2>
<p>This dashboard shows the local Matt-Cloud system stats.</p>
<div class="help-link" id="helpToggle">API</div>
</div>
<!-- Hidden API Card -->
<div id="helpText" class="card">
<strong>Component Desriptor</strong>
<p>To view the component descriptor, you may <br>
<code>curl -s https://<?= h($_SERVER['SERVER_NAME']) ?>/descriptor</code></p>
This will return the entire JSON descriptor variable.<br>
The endpoint agent uses this descriptor to build out its local System Object.<br>
The agent then reports back to the Cosmostat Server with all the data found in the descriptor.<br>
Full Source Code can be found at its <a target="_blank" rel="noopener noreferrer" href="https://gitea.matt-cloud.com/matt/cosmoserver">Gitea</a> page.
</div>
<!-- summary card -->
<div class="card">
<?php if (!empty($systemProperties)): ?>
<h2>System Properties</h2>
<table><tr>
<td>
<ul class="system-list">
<?php foreach ($systemProperties as $prop): ?>
<li><?= h($prop['Property']) ?></li>
<?php endforeach; ?>
</ul>
</td>
<td>
<h2>Live System Metrics</h2>
<div id="host_metrics" class="column">Connecting...</div>
</td>
</tr></table>
<?php endif; ?><br>
<div class="componentDetail-link" id="componentDetailToggle">Toggle Component Details</div>
</div>
<!-- hidden detail card -->
<div id="componentDetailText" class="card">
<?php if (!empty($systemComponents)): ?>
<h2>Components</h2>
<div class="components">
<?php foreach ($systemComponents as $comp): ?>
<div class="component">
<h3><?= h($comp['component_name']) ?></h3>
<ul class="info-list">
<?php foreach ($comp['info_strings'] as $info): ?>
<li><?= h($info) ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if ($mode == 'gali'): ?>
<h3>Shuttle Gali</h3>
<?php endif; ?>
</div>
<?php
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Cosmostat - <?= h($selectedHost) ?> (<?= h($mode) ?>)</title>
<link rel="stylesheet" href="src/styles.css">
</head>
<body>
<div class="wrapper">
<!-- Sidebar -->
<?php renderSidebar($mode); ?>
<!-- Main content -->
<?php renderMainContent($mode); ?>
</div> <!-- /wrapper -->
<script src="socket.io/socket.io.js"></script>
<script src="src/system_metrics.js"></script>
<script>
document.getElementById('helpToggle')?.addEventListener('click', function () {
const help = document.getElementById('helpText');
help.style.display = help.style.display === 'none' || help.style.display === '' ? 'block' : 'none';
});
document.getElementById('componentDetailToggle')?.addEventListener('click', function () {
const help = document.getElementById('componentDetailText');
help.style.display = help.style.display === 'none' || help.style.display === '' ? 'block' : 'none';
});
</script>
</body>
</html>