Ajout redirection des logs sur websocket et page /logs/view

This commit is contained in:
Arnaud Nelissen
2025-09-30 19:05:40 +02:00
parent 3ff1b73cd2
commit 539b6f262e
7 changed files with 633 additions and 9 deletions

View File

@@ -31,20 +31,23 @@ const getRequestToDo = async function (msn) {
///// Programs
if (device.programs != undefined) {
if (child.programmingTimestamp < device.programs.timestamp) { todo.programs = 1 }
else if (child.programmingTimestamp > device.programs.timestamp) { todo.programs = 2 }
timestamp = child.programmingTimestamp || -1
if (timestamp < device.programs.timestamp) { todo.programs = 1 }
else if (timestamp > device.programs.timestamp) { todo.programs = 2 }
}
///// Configuration
if (device.configuration != undefined) {
if (child.configurationTimestamp < device.configuration.timestamp) { todo.configuration = 1 }
else if (child.configurationTimestamp > device.configuration.timestamp) { todo.configuration = 2 }
timestamp = child.configurationTimestamp || child.relayConfigurationTimestamp || -1
if (timestamp < device.configuration.timestamp) { todo.configuration = 1 }
else if (timestamp > device.configuration.timestamp) { todo.configuration = 2 }
}
///// Slots
if (device.slots != undefined) {
if (child.programmingTimestamp < device.slots.timestamp) { todo.slots = 1 }
else if (child.programmingTimestamp > device.slots.timestamp) { todo.slots = 2 }
timestamp = child.programmingTimestamp || -1
if (timestamp < device.slots.timestamp) { todo.slots = 1 }
else if (timestamp > device.slots.timestamp) { todo.slots = 2 }
}
///// Status

View File

@@ -6,6 +6,7 @@ const ipx = require('./ipx.service')
const journal = require('./journal.service')
const longpolling = require('./longpolling.service')
const device = require('./device.service')
const log = require('./log.service')
module.exports = {
status,
@@ -16,4 +17,5 @@ module.exports = {
journal,
longpolling,
device,
log
}

123
services/log.service.js Normal file
View File

@@ -0,0 +1,123 @@
// services/logService.js
const WebSocket = require('ws');
const decodeURIComponentSafe = str => {
try {
return decodeURIComponent(str);
} catch {
return str; // fallback
}
};
const wssList = []; // all WS servers
const sockets = []; // { ws, name }
const originalConsole = {};
let clients = new Map(); // ws => username
// ASCII check
function isAscii(str) {
return /^[\x00-\x7F]*$/.test(str);
}
const attachLogService = async function (server, basePath = '/logs') {
try {
const wss = new WebSocket.Server({ noServer: true });
wssList.push(wss);
// Save original console methods once
["log", "error", "warn", "info", "debug"].forEach(fn => {
if (!originalConsole[fn]) originalConsole[fn] = console[fn];
});
// Override console methods (only once)
if (!originalConsole._overridden) {
["log", "error", "warn", "info", "debug"].forEach(fn => {
console[fn] = (...args) => {
originalConsole[fn].apply(console, args);
const msg = args.map(a => (typeof a === "string" ? a : JSON.stringify(a))).join(" ");
broadcast({ type: fn, message: msg, client: "server" });
};
});
originalConsole._overridden = true;
}
// Handle HTTP upgrade requests
server.on("upgrade", (request, socket, head) => {
const urlParts = request.url.split('/').filter(Boolean); // e.g. ['logs','😎']
if (urlParts[0] === basePath.replace(/^\/+/, '') && urlParts[1]) {
// Decode URL safely, preserving emojis
const clientName = decodeURIComponentSafe(urlParts[1]);
wss.handleUpgrade(request, socket, head, ws => {
ws.clientName = clientName;
wss.emit('connection', ws, request);
clients.set(ws, clientName);
sendClientsUpdate();
});
} else {
socket.destroy();
}
});
// WS connection
wss.on('connection', ws => {
sockets.push(ws);
ws.on("message", rawMsg => {
const msg = rawMsg.toString();
broadcast({ type: "message", message: msg, client: ws.clientName });
});
ws.on("close", () => {
const index = sockets.indexOf(ws);
if (index !== -1) sockets.splice(index, 1);
clients.delete(ws);
sendClientsUpdate();
});
});
return { success: true };
} catch (e) {
throw new Error(e.message);
}
};
const detachLogService = async function () {
try {
Object.keys(originalConsole).forEach(fn => {
if (fn !== "_overridden") console[fn] = originalConsole[fn];
});
sockets.forEach(ws => ws.close());
sockets.length = 0;
wssList.forEach(wss => wss.close());
wssList.length = 0;
return { success: true };
} catch (e) {
throw new Error(e.message);
}
};
// Broadcast helper
function broadcast(obj) {
// Add timestamp
obj.timestamp = new Date().toISOString();
const msg = JSON.stringify(obj);
sockets.forEach(ws => {
if (ws.readyState === WebSocket.OPEN) ws.send(msg);
});
}
function sendClientsUpdate() {
const clientNames = Array.from(clients.values());
broadcast({ clients: clientNames });
}
module.exports = {
attachLogService,
detachLogService,
};