From bd27bab30f8d4fd8bd3d2224e1551af234d2747b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CVeLiTi=E2=80=9D?= <“info@veliti.nl”> Date: Fri, 14 Nov 2025 14:04:46 +0100 Subject: [PATCH] Enhance logging functionality in API and UI components - Implemented detailed logging for USB serial communication in readdevice.js. - Added log file management features in logfile.php, including deletion and selection of log files. - Created a new communication log API endpoint in com_log.php to store USB communication data. - Improved user interface for log file selection and added confirmation for log deletion. --- api.php | 7 +- api/v2/post/com_log.php | 88 ++++++++++++++++ assets/readdevice.js | 161 ++++++++++++++++++++++++++--- logfile.php | 218 +++++++++++++++++++++++++++++++--------- 4 files changed, 412 insertions(+), 62 deletions(-) create mode 100644 api/v2/post/com_log.php diff --git a/api.php b/api.php index 3034fa5..fe1424c 100644 --- a/api.php +++ b/api.php @@ -168,14 +168,17 @@ if($is_jwt_valid && str_contains($version, 'v')) { // END check if endPoint is fileUpload //------------------------------------------ - if (isAllowed($collection,$profile,$permission,'R') === 1 && empty($input) && file_exists($api_file)){ + if ($collection === 'com_log' && file_exists($api_file_post)) { + include_once $api_file_post; + } + elseif (isAllowed($collection,$profile,$permission,'R') === 1 && empty($input) && file_exists($api_file)){ include_once $api_file; } elseif (isAllowed($collection,$profile,$permission,'U') === 1 && !empty($input) && file_exists($api_file_post)){ include_once $api_file_post; - } + } else { //------------------------------------------ diff --git a/api/v2/post/com_log.php b/api/v2/post/com_log.php new file mode 100644 index 0000000..950dfe8 --- /dev/null +++ b/api/v2/post/com_log.php @@ -0,0 +1,88 @@ + 'success', + 'message' => 'Communication logged successfully', + 'file' => $log_file + )); + } else { + // Error response + http_response_code(500); + echo json_encode(array( + 'status' => 'error', + 'message' => 'Failed to write to log file' + )); + } + + } else { + // Invalid payload + http_response_code(400); + echo json_encode(array( + 'status' => 'error', + 'message' => 'Invalid payload: missing data or direction' + )); + } + +} else { + // Invalid action + http_response_code(400); + echo json_encode(array( + 'status' => 'error', + 'message' => 'Invalid action' + )); +} +?> \ No newline at end of file diff --git a/assets/readdevice.js b/assets/readdevice.js index 5fd11b8..9a3e85a 100644 --- a/assets/readdevice.js +++ b/assets/readdevice.js @@ -1,6 +1,52 @@ var port, textEncoder, writableStreamClosed, writer, historyIndex = -1; const lineHistory = []; maintenanceRun = 0; +handshakeComplete = false; + +// Function to log communication to API +async function logCommunication(data, direction) { + // Log all communication including connection/disconnection events + try { + // Get service token for API authentication + const serviceToken = document.getElementById("servicetoken")?.innerHTML || ''; + + // Get serial number if available + let serialNumber = ''; + if (typeof serial !== 'undefined' && serial) { + serialNumber = serial; + } + + const logData = { + data: data, + direction: direction, // 'sent', 'received', 'connected', 'disconnected', 'handshake' + timestamp: new Date().toISOString(), + serial_number: serialNumber, + maintenance_run: maintenanceRun + }; + + // Get base URL for API calls (assuming 'link' variable is defined globally) + const url = link + '/v2/com_log/log'; + + const bearer = 'Bearer ' + serviceToken; + + const response = await fetch(url, { + method: 'POST', + withCredentials: true, + credentials: 'include', + headers: { + 'Authorization': bearer, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(logData) + }); + + if (!response.ok) { + console.warn('Failed to log communication:', response.status); + } + } catch (error) { + console.warn('Error logging communication:', error); + } +} function progressBar(percentage, message, color){ var readbar = document.getElementById("readBar"); @@ -14,6 +60,18 @@ async function connectSerial() { // Prompt user to select any serial port. const filters = [{ usbVendorId: 1027, usbProductId: 24597 }]; port = await navigator.serial.requestPort({ filters }); + + // Log selected port details + const portInfo = port.getInfo(); + const portDetails = { + usbVendorId: portInfo.usbVendorId, + usbProductId: portInfo.usbProductId, + readable: !!port.readable, + writable: !!port.writable, + opened: port.readable !== null && port.writable !== null + }; + await logCommunication(`Selected USB device - ${JSON.stringify(portDetails)}`, 'connected'); + await port.open({ baudRate: 56700 }); listenToPort(); @@ -22,7 +80,20 @@ async function connectSerial() { writableStreamClosed = textEncoder.readable.pipeTo(port.writable); writer = textEncoder.writable.getWriter(); - } catch { + + // Log successful connection with details + const portInfoSuccess = port.getInfo(); + const portDetailsSuccess = { + usbVendorId: portInfoSuccess.usbVendorId, + usbProductId: portInfoSuccess.usbProductId, + readable: !!port.readable, + writable: !!port.writable, + opened: port.readable !== null && port.writable !== null + }; + await logCommunication(`Serial port opened successfully (baudRate: 56700) - ${JSON.stringify(portDetailsSuccess)}`, 'connected'); + } catch (error) { + // Log connection failure details + await logCommunication(`Serial connection failed: ${error.message || 'Unknown error'}`, 'disconnected'); alert("Serial Connection Failed"); } } @@ -36,6 +107,10 @@ async function sendSerialLine() { dataToSend = dataToSend + "\n"; //new line appendToTerminal("> " + dataToSend); //send echo await writer.write(dataToSend); + + // Log sent data + await logCommunication(dataToSend.trim(), 'sent'); + document.getElementById("lineToSend").value = ""; //await writer.releaseLock(); } @@ -194,6 +269,10 @@ function writeToStream(command, payload){ dataToSend = command + payload + limiter; console.log(dataToSend); writer.write(dataToSend); + + // Log sent data + logCommunication(dataToSend.trim(), 'sent'); + maintenanceRun++; console.log(maintenanceRun); maintenanceTest(); @@ -218,6 +297,8 @@ async function listenToPort() { if (x.indexOf("STATE=MAINTENANCE") > 0 && maintenanceRun == 1) { progressBar("50", "Initializing program" , "#ff6666") + handshakeComplete = true; // Mark handshake as complete + await logCommunication('Handshake complete - entering maintenance mode', 'handshake'); setTimeout(maintenanceTest,3000); maintenanceRun = 2; } @@ -230,6 +311,18 @@ const plug_data = document.getElementById("plug_data"); async function appendToTerminal(newStuff) { serialResultsDiv.innerHTML += newStuff; + + // Log received data - check if this is handshake data + let logDirection = 'received'; + if (!handshakeComplete && maintenanceRun === 0) { + // Check if this contains device identification info (handshake) + if (newStuff.includes('PN=') || newStuff.includes('SN=') || newStuff.includes('HW=') || + newStuff.includes('FW=') || newStuff.includes('STATE=')) { + logDirection = 'handshake'; + } + } + + await logCommunication(newStuff.trim(), logDirection); const keyword = '$STATUS;STATE'; const keyword2 = 'PWM'; @@ -431,12 +524,33 @@ function getData() { async function closePort(){ - reader.cancel(); - await readableStreamClosed.catch(() => { /* Ignore the error */ }); - writer.close(); - await writableStreamClosed - console.log(maintenanceRun); - await port.close(); + try { + // Log port closure start + const portInfo = port.getInfo(); + const portDetails = { + usbVendorId: portInfo.usbVendorId, + usbProductId: portInfo.usbProductId, + readable: !!port.readable, + writable: !!port.writable, + opened: port.readable !== null && port.writable !== null + }; + await logCommunication(`Starting port closure - ${JSON.stringify(portDetails)}`, 'disconnected'); + + reader.cancel(); + await readableStreamClosed.catch(() => { /* Ignore the error */ }); + writer.close(); + await writableStreamClosed; + console.log(maintenanceRun); + await port.close(); + + // Log successful port closure + await logCommunication(`Serial port closed successfully - VendorID: ${portInfo.usbVendorId}, ProductID: ${portInfo.usbProductId}`, 'disconnected'); + } catch (error) { + // Log port closure failure + await logCommunication(`Serial port closure failed: ${error.message || 'Unknown error'}`, 'disconnected'); + } + + handshakeComplete = false; // Reset handshake for next connection //Check for errors maintenanceRun = 999 if (maintenanceRun != 999){ @@ -463,12 +577,33 @@ async function closePort(){ async function closePortCarTest(){ - reader.cancel(); - await readableStreamClosed.catch(() => { /* Ignore the error */ }); - writer.close(); - await writableStreamClosed - console.log(maintenanceRun); - await port.close(); + try { + // Log port closure start + const portInfo = port.getInfo(); + const portDetails = { + usbVendorId: portInfo.usbVendorId, + usbProductId: portInfo.usbProductId, + readable: !!port.readable, + writable: !!port.writable, + opened: port.readable !== null && port.writable !== null + }; + await logCommunication(`Starting port closure (car test) - ${JSON.stringify(portDetails)}`, 'disconnected'); + + reader.cancel(); + await readableStreamClosed.catch(() => { /* Ignore the error */ }); + writer.close(); + await writableStreamClosed; + console.log(maintenanceRun); + await port.close(); + + // Log successful port closure + await logCommunication(`Serial port closed successfully (car test) - VendorID: ${portInfo.usbVendorId}, ProductID: ${portInfo.usbProductId}`, 'disconnected'); + } catch (error) { + // Log port closure failure + await logCommunication(`Serial port closure failed (car test): ${error.message || 'Unknown error'}`, 'disconnected'); + } + + handshakeComplete = false; // Reset handshake for next connection //Check for errors maintenanceRun = 999 if (maintenanceRun != 999){ diff --git a/logfile.php b/logfile.php index 5f72723..5d11d38 100644 --- a/logfile.php +++ b/logfile.php @@ -7,71 +7,195 @@ if (isAllowed('logfile',$_SESSION['profile'],$_SESSION['permission'],'R') === 0) exit; } -$filelocation = $_SERVER['DOCUMENT_ROOT'].'/log/log_'.date('d').'.txt'; -$filelocation_webserver = '/var/www/vhosts/veliti.nl/logs/'.$_SERVER['HTTP_HOST'].'/access_ssl_log'; +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// POST HANDLER - Delete all logs +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +$delete_message = ''; +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_all_logs'])) { + $logs_dir = __DIR__ . '/log/'; + $deleted_count = 0; -// Capture post data -if (isset($_POST['logfile'])) { - // Save templates - file_put_contents($filelocation, $_POST['logfile']); - header('Location: index.php?page=logfile&success_msg=1'); + if (is_dir($logs_dir)) { + $files = scandir($logs_dir); + foreach ($files as $file) { + if (preg_match('/\.(txt|log)$/', $file)) { + if (unlink($logs_dir . $file)) { + $deleted_count++; + } + } + } + } + + $delete_message = "Successfully deleted $deleted_count log file(s)."; + // Redirect to index page + header("Location: index.php?page=logfile&deleted=$deleted_count"); exit; } -// Read language_US template PHP file -if (file_exists($filelocation)){ - $contents = file_get_contents($filelocation); -} else {$contents = '';} + +// Show delete confirmation message +if (isset($_GET['deleted'])) { + $delete_message = "Successfully deleted " . intval($_GET['deleted']) . " log file(s)."; +} + +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// GET HANDLER +//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +// Get selected log file from query parameter +$selected_log = $_GET['log'] ?? ''; + +// Scan logs directory for all log files +$logs_dir = __DIR__ . '/log/'; +$log_files = []; + +if (is_dir($logs_dir)) { + $files = scandir($logs_dir); + foreach ($files as $file) { + if (preg_match('/\.(txt|log)$/', $file) && is_file($logs_dir . $file)) { + $log_files[] = [ + 'filename' => $file, + 'path' => $logs_dir . $file, + 'size' => filesize($logs_dir . $file), + 'mtime' => filemtime($logs_dir . $file) // creation/modification time + ]; + } + } +} + +// Sort by creation date descending (newest first) +usort($log_files, function($a, $b) { + return $b['mtime'] <=> $a['mtime']; +}); + +// Load selected log content +$contents = ''; +if ($selected_log && file_exists($logs_dir . $selected_log)) { + $contents = file_get_contents($logs_dir . $selected_log); +} elseif (!empty($log_files)) { + // Default to most recent log + $contents = file_get_contents($log_files[0]['path']); + $selected_log = $log_files[0]['filename']; +} else { + $contents = 'No application log files found.'; +} + +$filelocation_webserver = '/var/www/vhosts/veliti.nl/logs/'.$_SERVER['HTTP_HOST'].'/access_ssl_log'; if (file_exists($filelocation_webserver)){ $contents_webserver = file_get_contents($filelocation_webserver); -} else {$contents_webserver = '';} - -// Handle success messages -if (isset($_GET['success_msg'])) { - if ($_GET['success_msg'] == 1) { - $success_msg = 'Updated successfully!'; - } -} +} else {$contents_webserver = 'No webserver log file found.';} ?> =template_header('Log', 'log','view')?> -