Files
cosmoserver/files/vizz/docker/web/html/test.php

324 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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>