From c4af12159711315c501c916020fd5e8864c311df Mon Sep 17 00:00:00 2001 From: Yamaray Date: Fri, 5 Jun 2026 23:23:30 +0200 Subject: [PATCH] Configurar Top-Rack como ajuste global --- DOCUMENTACION.md | 12 ++++++++++-- config.example.json | 10 ++-------- config.html | 32 +++++++++++++++++++++++++------- docs.html | 5 +++-- server.js | 32 ++++++++++++++++++++++++++++---- 5 files changed, 68 insertions(+), 23 deletions(-) diff --git a/DOCUMENTACION.md b/DOCUMENTACION.md index a14d198..c14e4ce 100644 --- a/DOCUMENTACION.md +++ b/DOCUMENTACION.md @@ -275,10 +275,18 @@ Si una RPi tiene un sensor externo de temperatura del rack, Node-RED puede escri } ``` -En el configurador, coloca la ruta en el dispositivo que tiene el sensor: +En el configurador, usa la seccion general `Temperatura Top-Rack`: ```text -/home/yamaray/monitor-rpi/rack-temp.json +RPi origen: RPi - MeshCore · 192.168.0.60 +Path JSON temperatura: /home/yamaray/monitor-rpi/rack-temp.json +``` + +La configuracion se guarda como: + +```text +rackTemperatureSourceDeviceId: rpi3-meshcore +rackTemperatureFile: /home/yamaray/monitor-rpi/rack-temp.json ``` El backend lee ese archivo durante el mismo scan SSH y muestra el valor como `Temperatura Top-Rack` en la primera linea del dashboard. diff --git a/config.example.json b/config.example.json index 86a8d8f..785e6b1 100644 --- a/config.example.json +++ b/config.example.json @@ -5,6 +5,8 @@ "sshTimeoutSeconds": 8, "sshPrivateKeyPath": "/ssh/monitor_rpi_ed25519", "sshPublicKeyPath": "/ssh/monitor_rpi_ed25519.pub", + "rackTemperatureSourceDeviceId": "rpi3-meshcore", + "rackTemperatureFile": "/home/yamaray/monitor-rpi/rack-temp.json", "temperatureThresholdsC": { "warning": 60, "hot": 70, @@ -41,7 +43,6 @@ "password": "", "authMethod": "password", "privateKeyPath": "", - "rackTemperatureFile": "", "role": "Home Assistant Local", "port": 22, "id": "rpi5-ha-main", @@ -56,7 +57,6 @@ "password": "", "authMethod": "password", "privateKeyPath": "", - "rackTemperatureFile": "", "role": "Home Assistant Cabaña", "port": 22, "id": "rpi4-ha-second", @@ -71,7 +71,6 @@ "password": "", "authMethod": "password", "privateKeyPath": "", - "rackTemperatureFile": "", "role": "Docker", "port": 22, "id": "rpi5-jose-docker", @@ -86,7 +85,6 @@ "password": "", "authMethod": "password", "privateKeyPath": "", - "rackTemperatureFile": "", "role": "Docker - Daniel", "port": 22, "id": "rpi5-dani-docker", @@ -101,7 +99,6 @@ "password": "", "authMethod": "password", "privateKeyPath": "", - "rackTemperatureFile": "", "role": "nginx - wireguard ", "port": 22, "id": "rpi3-nginx", @@ -116,7 +113,6 @@ "password": "", "authMethod": "password", "privateKeyPath": "", - "rackTemperatureFile": "", "role": "MQTT - varios", "port": 22, "id": "rpi3-pi3home", @@ -131,7 +127,6 @@ "password": "", "authMethod": "password", "privateKeyPath": "", - "rackTemperatureFile": "/home/yamaray/monitor-rpi/rack-temp.json", "role": "Meshcore-Interface", "port": 22, "id": "rpi3-meshcore", @@ -146,7 +141,6 @@ "password": "", "authMethod": "password", "privateKeyPath": "", - "rackTemperatureFile": "", "role": "Reserva", "port": 22, "id": "rpi3-node-3", diff --git a/config.html b/config.html index e44602c..e1a3ca3 100644 --- a/config.html +++ b/config.html @@ -233,7 +233,6 @@ .field-role { grid-column: span 3; } .field-model { grid-column: span 2; } .field-location { grid-column: span 2; } - .field-rack-temp { grid-column: span 4; } .field-host { grid-column: span 3; } .field-port { grid-column: span 1; } .field-username { grid-column: span 2; } @@ -446,7 +445,7 @@ @media (max-width: 1180px) { .device { grid-template-columns: repeat(6, minmax(0, 1fr)); align-items: start; } - .field-name, .field-role, .field-host, .password-credential, .field-rack-temp { grid-column: span 3; } + .field-name, .field-role, .field-host, .password-credential { grid-column: span 3; } .field-id, .field-model, .field-location, .field-username, .field-auth, .field-active, .field-delete { grid-column: span 2; } .field-port { grid-column: span 1; } .credential-note { grid-column: span 4; } @@ -461,7 +460,7 @@ .top-grid, .device { grid-template-columns: 1fr; } .field-active, .field-name, .field-id, .field-role, .field-model, .field-location, .field-host, .field-port, .field-username, .field-auth, .password-credential, - .credential-note, .field-rack-temp, .field-delete { grid-column: span 1; } + .credential-note, .field-delete { grid-column: span 1; } .threshold-row { grid-template-columns: 1fr; } } @@ -503,6 +502,17 @@ +
+

Temperatura Top-Rack

+
+ + +
+

Clave SSH del monitor

@@ -703,9 +713,6 @@
Metodo activo: ${authMethod === "key" ? "Clave asimetrica: copiar clave publica del monitor en la RPi" : "Password"}
- @@ -720,6 +727,16 @@ document.querySelector("#refreshIntervalSeconds").value = state.config.refreshIntervalSeconds || 30; document.querySelector("#idleScanIntervalSeconds").value = state.config.idleScanIntervalSeconds || 300; document.querySelector("#sshTimeoutSeconds").value = state.config.sshTimeoutSeconds || 8; + const rackSource = document.querySelector("#rackTemperatureSourceDeviceId"); + rackSource.innerHTML = [ + ``, + ...state.config.devices.map((device) => { + const value = device.id || device.host || device.name || ""; + const label = `${device.name || value} · ${device.host || "sin IP"}`; + return ``; + }) + ].join(""); + document.querySelector("#rackTemperatureFile").value = state.config.rackTemperatureFile || ""; document.querySelector("#sshPrivateKeyPath").value = state.config.sshPrivateKeyPath || "/ssh/monitor_rpi_ed25519"; document.querySelector("#sshPublicKeyPath").value = state.config.sshPublicKeyPath || `${document.querySelector("#sshPrivateKeyPath").value}.pub`; document.querySelector("#warningTemp").value = state.config.temperatureThresholdsC?.warning || 60; @@ -745,6 +762,8 @@ state.config.refreshIntervalSeconds = Number(document.querySelector("#refreshIntervalSeconds").value || 30); state.config.idleScanIntervalSeconds = Number(document.querySelector("#idleScanIntervalSeconds").value || 300); state.config.sshTimeoutSeconds = Number(document.querySelector("#sshTimeoutSeconds").value || 8); + state.config.rackTemperatureSourceDeviceId = document.querySelector("#rackTemperatureSourceDeviceId").value || ""; + state.config.rackTemperatureFile = document.querySelector("#rackTemperatureFile").value || ""; state.config.sshPrivateKeyPath = document.querySelector("#sshPrivateKeyPath").value || "/ssh/monitor_rpi_ed25519"; state.config.sshPublicKeyPath = document.querySelector("#sshPublicKeyPath").value || `${state.config.sshPrivateKeyPath}.pub`; state.config.temperatureThresholdsC = { @@ -852,7 +871,6 @@ password: "", privateKeyPath: "", authMethod: "password", - rackTemperatureFile: "", model: "RPi 4", role: "", location: "Rack", diff --git a/docs.html b/docs.html index 828ac93..b9f4469 100644 --- a/docs.html +++ b/docs.html @@ -156,8 +156,9 @@ chmod 600 ~/.ssh/authorized_keys "sensor": "rack-top", "updatedAt": "2026-06-05T21:12:18.997Z" } -

En el configurador, en ese dispositivo, usa Archivo temp rack con una ruta como:

-
/home/yamaray/monitor-rpi/rack-temp.json
+

En el configurador, usa la seccion general Temperatura Top-Rack:

+
RPi origen: RPi - MeshCore · 192.168.0.60
+Path JSON temperatura: /home/yamaray/monitor-rpi/rack-temp.json

Docker

cd /home/yamaray/docker/monitorRPi
diff --git a/server.js b/server.js
index 78ec852..7a6e163 100644
--- a/server.js
+++ b/server.js
@@ -49,6 +49,7 @@ async function readConfig() {
   parsed.temperatureThresholdsC = normalizeTemperatureThresholds(parsed.temperatureThresholdsC);
   parsed.metricThresholdsPercent = normalizeMetricThresholds(parsed.metricThresholdsPercent);
   parsed.devices = Array.isArray(parsed.devices) ? parsed.devices.map(normalizeDeviceConfig) : [];
+  normalizeRackTemperatureConfig(parsed);
   return parsed;
 }
 
@@ -65,6 +66,7 @@ async function writeConfig(nextConfig) {
     metricThresholdsPercent: normalizeMetricThresholds(nextConfig.metricThresholdsPercent),
     devices: Array.isArray(nextConfig.devices) ? nextConfig.devices.map(normalizeDeviceConfig) : []
   };
+  normalizeRackTemperatureConfig(normalized);
   await fs.mkdir(path.dirname(CONFIG_PATH), { recursive: true });
   await fs.writeFile(CONFIG_PATH, JSON.stringify(normalized, null, 2) + "\n", "utf8");
   config = normalized;
@@ -80,6 +82,19 @@ function normalizeDeviceConfig(device = {}) {
   };
 }
 
+function normalizeRackTemperatureConfig(nextConfig) {
+  nextConfig.rackTemperatureSourceDeviceId = String(nextConfig.rackTemperatureSourceDeviceId || "");
+  nextConfig.rackTemperatureFile = String(nextConfig.rackTemperatureFile || "");
+
+  if (!nextConfig.rackTemperatureSourceDeviceId || !nextConfig.rackTemperatureFile) {
+    const legacyDevice = nextConfig.devices.find((device) => device.rackTemperatureFile);
+    if (legacyDevice) {
+      nextConfig.rackTemperatureSourceDeviceId = legacyDevice.id || "";
+      nextConfig.rackTemperatureFile = legacyDevice.rackTemperatureFile || "";
+    }
+  }
+}
+
 function classifyTemp(tempC) {
   const thresholds = config.temperatureThresholdsC || {};
   if (tempC >= (thresholds.critical || 80)) return "critical";
@@ -130,6 +145,7 @@ function mockMetrics(device) {
   const temp = seededNumber(`${device.id}:temp`, baseTemp - 4, baseTemp + 18);
   const dockerRole = /docker|gitea|nginx/i.test(device.role || "");
   const haRole = /home assistant/i.test(device.role || "");
+  const rackTemperatureFile = rackTemperatureFileForDevice(device);
 
   return {
     cpuTempC: Number(temp.toFixed(1)),
@@ -166,13 +182,13 @@ function mockMetrics(device) {
       rxBytesPerSecond: Math.round(seededNumber(`${device.id}:rx`, 1200, 95000)),
       txBytesPerSecond: Math.round(seededNumber(`${device.id}:tx`, 700, 55000))
     },
-    rackTemperature: device.rackTemperatureFile
+    rackTemperature: rackTemperatureFile
       ? {
           temperatureC: Number(seededNumber(`${device.id}:rack-temp`, 25, 37).toFixed(1)),
           sensor: "rack-top",
           updatedAt: new Date().toISOString(),
           ageSeconds: 0,
-          sourceFile: device.rackTemperatureFile
+          sourceFile: rackTemperatureFile
         }
       : null
   };
@@ -340,6 +356,13 @@ function shellQuote(value) {
   return `'${String(value || "").replace(/'/g, `'\\''`)}'`;
 }
 
+function rackTemperatureFileForDevice(device) {
+  const sourceId = config.rackTemperatureSourceDeviceId || "";
+  const sourceMatches = sourceId && [device.id, device.host, device.name].includes(sourceId);
+  if (sourceMatches) return config.rackTemperatureFile || "";
+  return device.rackTemperatureFile || "";
+}
+
 function networkRates(device, values) {
   const sampleKey = device.id || `${device.host}:${device.port || 22}`;
   const now = Date.now();
@@ -470,7 +493,8 @@ async function sshMetrics(device) {
     ];
   }
 
-  const remoteScript = `RACK_TEMPERATURE_FILE=${shellQuote(device.rackTemperatureFile || "")}\n${REMOTE_METRICS_SCRIPT}`;
+  const rackTemperatureFile = rackTemperatureFileForDevice(device);
+  const remoteScript = `RACK_TEMPERATURE_FILE=${shellQuote(rackTemperatureFile)}\n${REMOTE_METRICS_SCRIPT}`;
   const { stdout } = await runCommandWithInput(command, args, remoteScript, timeoutMs);
   const values = parseKeyValueOutput(stdout);
   const temp = toNumber(values.cpuTempC, 0);
@@ -511,7 +535,7 @@ async function sshMetrics(device) {
           sensor: values.rackTemperatureSensor || "",
           updatedAt: values.rackTemperatureUpdatedAt || "",
           ageSeconds: toNumber(values.rackTemperatureAgeSeconds, null),
-          sourceFile: device.rackTemperatureFile || ""
+          sourceFile: rackTemperatureFile
         }
       : null
   };