diff --git a/web_dash/web/html/index.php b/web_dash/web/html/index.php
index 0925ff9..401c729 100644
--- a/web_dash/web/html/index.php
+++ b/web_dash/web/html/index.php
@@ -1,356 +1,98 @@
$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)
+# ---------- Helper: JSON fetch ----------
+function fetch_json(string $url): ?array
{
-
- $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'], // space filler */
- 'drive_health' => ['bind' => '0.0.0.0', 'port' => '5001'], // drive health 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"
- ]
+ 'http' => ['timeout' => 2]
]);
- $json = @file_get_contents($url, false, $ctx);
- if ($json === false) {
- return []; // caller will handle empty case
+ $raw = @file_get_contents($url, false, $ctx);
+ if ($raw === false) {
+ return null;
}
- $data = json_decode($json, true);
- if (!is_array($data)) {
- return ['fail'];
- }
- return $data;
+ $data = json_decode($raw, true);
+ return is_array($data) ? $data : null;
}
-/* ---------- Fetch client details ---------- */
-$apiInfo = $apiConfig[$mode];
-$clients = fetchClientDetails($apiInfo['bind'], $apiInfo['port']);
+# ---------- Load data ----------
+$api_url = "$api_host:$api_port$api_path";
+$payload = fetch_json($api_url);
-/* ---------- 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';
-
-
-/* ---- Main Content Renderer ---- */
-
-function renderMainContent(string $mode){
- global $clients, $client, $properties, $systemProperties, $systemComponents, $selectedHost, $selectedHosts, $remove_hosts, $selectedId, $selectedIdx;
- /* ---------- Handle empty / error ---------- */
- if ($clients === ['fail']) {
- die('
Could not retrieve data from the API for mode "' . h($mode) . '".
');
- }
-
- ?>
-
-
-
-
-
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) ?>
+Sensor Dashboard
+
+
+
Sensor Dashboard
-
-
-
-
-
-
-
-
-
-
-
+
+
⚠️ Could not fetch data from the API.
+
+
+
+
+ Device
+ Comment
+ Temp (°C)
+ Humidity %
+
+
+
+
+
+
+ = htmlspecialchars($s['name']) ?>
+ = htmlspecialchars($s['comment'] ?? '') ?>
+ = htmlspecialchars($temp) ?>
+ = htmlspecialchars($hum) ?>
+
+
+
+
+
+
+
+
-
-
\ No newline at end of file
diff --git a/web_dash/web/node_server/server.js b/web_dash/web/node_server/server.js
index 214ac8f..a183e28 100644
--- a/web_dash/web/node_server/server.js
+++ b/web_dash/web/node_server/server.js
@@ -1,153 +1,59 @@
-// server.js
-const http = require('http');
+// server.js – minimal Express + Socket.io + Redis subscriber
+const http = require('http');
const express = require('express');
-const { createClient } = require('redis');
const { Server } = require('socket.io');
-const fetch = require('node-fetch'); // npm i node-fetch@2
-const fs = require('fs');
-const yaml = require('js-yaml'); // npm i js-yaml
-const path = require('path');
+const { createClient } = require('redis');
-const app = express();
+const app = express();
const server = http.createServer(app);
-const io = new Server(server);
+const io = new Server(server);
-/* --------------------------------------------------------------------- */
-/* ---------- 1. Load the YAML configuration file ---------------------- */
-/* --------------------------------------------------------------------- */
-let config = {};
-try {
- const filePath = '/app/cosmostat_settings.yaml';
- const file = fs.readFileSync(filePath, 'utf8');
- config = yaml.load(file);
-} catch (e) {
- console.error('Failed to load config.yaml:', e);
- process.exit(1);
+// ---------- 1. Settings ----------
+const REDIS_URL = 'redis://0.0.0.0:6379'; // adjust if needed
+const REDIS_CHANNEL = 'temp_hum'; // channel you subscribed to
+
+// ---------- 2. Serve static files ----------
+app.use(express.static('public')); // public contains index.php, src/… etc.
+
+// ---------- 3. Redis subscriber ----------
+const redisClient = createClient({ url: REDIS_URL });
+redisClient.on('error', err => console.error('Redis error:', err));
+
+async function startRedis() {
+ await redisClient.connect();
+ const sub = redisClient.duplicate();
+ await sub.connect();
+
+ // Helper: forward a Redis message to all Socket.io clients
+ const forward = (msg) => {
+ try {
+ const payload = JSON.parse(msg);
+ io.emit('temp_hum', payload); // event name matches the JS listener
+ } catch (e) {
+ console.error('Failed to parse Redis message:', e);
+ }
+ };
+
+ await sub.subscribe(REDIS_CHANNEL, forward);
+ console.log(`Subscribed to Redis channel "${REDIS_CHANNEL}"`);
}
-const API_PORT = config.custom_api_port || 5000; // fallback to 5000
-const API_HOST = config.api_bind_ip || '192.168.37.1'; // fallback IP
-const API_BASE = `http://${API_HOST}:${API_PORT}`;
-console.log('API URL:', API_BASE);
-/* --------------------------------------------------------------------- */
-/* ---------- 2. Socket.io -------------------------------------------- */
-/* --------------------------------------------------------------------- */
-io.on('connection', async socket => {
- console.log('client connected:', socket.id);
+startRedis().catch(console.error);
- /* ------------- send cached client_summary ------------- */
- if (clientSummaryCache.last) {
- socket.emit('client_summary', clientSummaryCache.last);
- console.log('sent cached client_summary to', socket.id);
- }
+// ---------- 4. Socket.io ----------
+io.on('connection', socket => {
+ console.log('Socket.io client connected:', socket.id);
- /* ----------------------------------------------------------------- */
- /* Call the external API every time a client connects */
- /* ----------------------------------------------------------------- */
- try {
- const resp = await fetch(`${API_BASE}/start_timer`, { method: 'GET' });
- const data = await resp.json();
- console.log('API responded to connect:', data);
- } catch (err) {
- console.error('Failed to hit start_timer endpoint:', err);
- }
+ // Optionally send the most recent snapshot
+ // (If you keep a cache, emit it here)
- /* ----------------------------------------------------------------- */
- /* Listen for tableRendered event from the client */
- /* ----------------------------------------------------------------- */
- socket.on('tableRendered', async () => {
- console.log('Client reported table rendered - starting timer');
- try {
- const resp = await fetch(`${API_BASE}/start_timer`, { method: 'GET' });
- const text = await resp.text();
- console.log('Timer endpoint responded:', text);
- } catch (err) {
- console.error('Failed to hit start_timer:', err);
- }
- });
+ socket.on('disconnect', () => {
+ console.log('Socket.io client disconnected:', socket.id);
+ });
});
-/* --------------------------------------------------------------------- */
-/* ---------- 3. Serve static files ----------------------------------- */
-/* --------------------------------------------------------------------- */
-app.use(express.static('public'));
-
-/* --------------------------------------------------------------------- */
-/* ---------- 4. Redis subscriber ------------------------------------- */
-/* --------------------------------------------------------------------- */
-const redisClient = createClient({
- url: 'redis://0.0.0.0:6379',
- socket: { keepAlive: 60000, // 60 s TCP keep-alive
- reconnectStrategy: attempts => Math.min(attempts * 100, 3000) } // back-off
-});
-redisClient.on('error', err => console.error('Redis error', err));
-
-/* --- local cache for client_summary -------------------------------- */
-const clientSummaryCache = {}; // { last: }
-
-/* --------------------------------------------------------------------- */
-(async () => {
- await redisClient.connect();
- const sub = redisClient.duplicate();
- await sub.connect();
-
- /* --------------------------------------------------------------------- */
- /* Helper that re-subscribes to a channel (and re-sends the handler) */
- /* --------------------------------------------------------------------- */
- async function safeSubscribe(channel, handler) {
- try {
- await sub.subscribe(channel, handler);
- console.log(`Subscribed to ${channel}`);
- } catch (e) {
- console.error(`Failed to subscribe to ${channel}`, e);
- }
- }
-
- /* --------------------------------------------------------------------- */
- /* Subscribe to all required channels */
- /* --------------------------------------------------------------------- */
- await safeSubscribe('host_metrics', (msg) => forward('host_metrics', msg));
- await safeSubscribe('client_summary', (msg) => forward('client_summary', msg));
-
- /* --------------------------------------------------------------------- */
- /* Forward messages to Socket.io */
- /* --------------------------------------------------------------------- */
- function forward(channel, message) {
- try {
- const payload = JSON.parse(message);
-
- /* ----- update cache on client_summary ----- */
- if (channel === 'client_summary') {
- clientSummaryCache.last = payload;
- }
-
- io.emit(channel, payload);
- } catch (e) {
- console.error(`Failed to parse message from ${channel}`, e);
- }
- }
-
- /* --------------------------------------------------------------------- */
- /* Re-subscribe automatically when the Redis connection reconnects */
- /* --------------------------------------------------------------------- */
- sub.on('reconnecting', () => console.log('Redis reconnecting…'));
- sub.on('ready', async () => {
- console.log('Redis ready - re-subscribing to all channels');
- await safeSubscribe('host_metrics', (msg) => forward('host_metrics', msg));
- await safeSubscribe('client_summary', (msg) => forward('client_summary', msg));
- });
-
- /* Optional: if the connection ends for any reason, close the process */
- sub.on('end', () => {
- console.error('Redis connection closed - exiting');
- process.exit(1);
- });
-})();
-
-/* --------------------------------------------------------------------- */
-/* ---------- 5. Start the HTTP server --------------------------------- */
-/* --------------------------------------------------------------------- */
+// ---------- 5. Start HTTP server ----------
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
- console.log(`Server listening on http://localhost:${PORT}`);
+ console.log(`Dashboard server listening on http://localhost:${PORT}`);
});
\ No newline at end of file