$s !== ''
);
} else {
$remove_hosts = [];
}
if (!empty($remove_hosts)){
foreach ($remove_hosts as $host) {
echo "remove ".$host." ";
}
removeClient($remove_hosts);
}
}
}
# authelia user handler
$authelia_user = "not-set";
if (isset($_SERVER['HTTP_REMOTE_USER'])) {
$authelia_user = $_SERVER['HTTP_REMOTE_USER'];
}
/* ---------- Helper: remove client details ---------- */
function removeClient($clientList)
{
$url = "http://0.0.0.0:5001/storage_client_delete";
$payload = [
'API_KEY' => "deadbeef",
'remove_hosts' => $clientList,
];
$json = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
if ($json === false) {
throw new RuntimeException('JSON encoding failed: ' . json_last_error_msg());
}
$ch = curl_init($url);
if ($ch === false) {
throw new RuntimeException('Unable to initialise cURL.');
}
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $json,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Content-Length: ' . strlen($json),
'Accept: application/json',
],
CURLOPT_RETURNTRANSFER=> true,
CURLOPT_TIMEOUT => 2,
CURLOPT_FOLLOWLOCATION=> true, // follow redirects if any
]);
// Execute curl request
$response = curl_exec($ch);
// cURL error handling
if ($response === false) {
$error = curl_error($ch);
$errno = curl_errno($ch);
curl_close($ch);
throw new RuntimeException("cURL error ({$errno}): {$error}");
}
// Grab HTTP status code
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) {
throw new RuntimeException("API returned HTTP {$httpCode}: {$response}");
}
// Decode the JSON response
$decoded = json_decode($response, true);
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('Failed to decode JSON response: ' . json_last_error_msg());
}
return $decoded;
}
/**
* Escape HTML
*/
function h(string $s): string
{
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
}
/**
* Load simple key/value pairs from a YAML file.
* Lines starting with '#' are ignored.
* The function returns an associative array.
*/
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, "\"'"); // remove surrounding quotes
if ($value === '') {
$value = null;
}
$data[$key] = $value;
}
return $data;
}
/* ---------- Load settings ---------- */
$settingsPath = '/app/cosmostat_settings.yaml';
$settings = loadYaml($settingsPath);
/* ---------- Page mode handling ---------- */
$mode = $_GET['mode'] ?? 'cosmostat'; // default mode
$validModes = ['cosmostat', 'drive_health']; // extend as needed
// 'gali',
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' => '0.0.0.0', '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' => 2,
'header' => "User-Agent: PHP/" . PHP_VERSION . "\r\n"
]
]);
$json = @file_get_contents($url, false, $ctx);
if ($json === false) {
return []; // caller will handle empty case
}
$data = json_decode($json, true);
if (!is_array($data)) {
return ['fail'];
}
return $data;
}
/* ---------- Fetch client details ---------- */
$apiInfo = $apiConfig[$mode];
$clients = fetchClientDetails($apiInfo['bind'], $apiInfo['port']);
/* ---------- 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'] ?? [];
if ($mode === 'drive_health') {
if (isset($_GET['action'])) {
switch ($_GET['action']) {
case 'all':
$selectedHosts = array_column($clients, 'short_id');
break;
case 'none':
$selectedHosts = [];
break;
// 'apply' – nothing to do; $selectedHosts already contains the posted hosts
}
}
}
if ($mode === 'drive_health' && empty($selectedHosts) && !isset($_GET['action'])) {
$selectedHosts = array_column($clients, 'short_id');
}
/* ---- Determine selected host ---- */
$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) {
// Default to the first client (if any)
$selectedIdx = 0;
$selectedId = $clients[$selectedIdx]['short_id'] ?? '';
}
#global $clients, $client, $properties, $systemProperties, $systemComponents, $selectedHost, $selectedHosts, $selectedId, $selectedIdx;
$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, $selectedHosts, $selectedId, $selectedIdx, $remove_hosts;
$modes = [
'cosmostat' => 'Cosmostat',
/* 'gali' => 'Shuttle Gali',*/
'drive_health' => 'Drive Health',
];
?>
Could not retrieve data from the API for mode "' . h($mode) . '".
');
}
if ($mode === 'drive_health') {
// If nothing is selected, show a friendly message
if (empty($selectedHosts)) {
echo '';
return;
}
echo '';
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 '
';
echo '
Drive Health - ' . h($hostname) . '
IP: '.h($c['ip']).'
Timestamp: '.date('F j, Y g:i a', (int) $c['timestamp']).'
';
if (isset($c['drives']) && is_array($c['drives']) && count($c['drives']) > 0) {
echo '
';
echo 'Drive Letter Disk ID Health Status Model Capacity Power On Hours Host Writes Wear Level
';
echo '
';
foreach ($c['drives'] as $drive) {
echo '
';
echo '' . h($drive['drive_letter'] ?? '') . ' ';
echo '' . h("".$drive['disk_id'] ?? '') . ' ';
echo '' . h($drive['health_status'] ?? '') . ' ';
echo '' . h($drive['model'] ?? '') . ' ';
echo '' . h($drive['capacity'] ?? '') . ' ';
echo '' . h($drive['power_on_hours'] ?? '') . ' ';
echo '' . h($drive['host_writes'] ?? '') . ' ';
echo '' . h($drive['wear_level'] ?? '') . ' ';
echo '
';
}
echo '
';
} else {
echo '
No drive data available for this host.
';
}
echo '
';
}
echo '
';
return;
}
?>
Matt-Cloud Cosmostat Dashboard
This dashboard shows the local Matt-Cloud system stats.
API
Component Desriptor
To view the component descriptor, you may
curl -s https://= h($_SERVER['SERVER_NAME']) ?>/descriptor
This will return the entire JSON descriptor variable.
The endpoint agent uses this descriptor to build out its local System Object.
The agent then reports back to the Cosmostat Server with all the data found in the descriptor.
Full Source Code can be found at its
Gitea page.
System Properties
= h($prop['Property']) ?>
Live System Metrics
Connecting...
Toggle Component Details
Components
= h($comp['component_name']) ?>
Shuttle Gali
Cosmostat - = h($selectedHost) ?>