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.
This commit is contained in:
7
api.php
7
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
|
||||
{
|
||||
//------------------------------------------
|
||||
|
||||
88
api/v2/post/com_log.php
Normal file
88
api/v2/post/com_log.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
//defined($security_key) or exit;
|
||||
|
||||
//------------------------------------------
|
||||
// Communication Log API endpoint
|
||||
// Stores all USB serial communication data in date-organized files
|
||||
//------------------------------------------
|
||||
$request = explode('/', trim($_SERVER['PATH_INFO'],'/'));
|
||||
|
||||
//------------------------------------------
|
||||
// Application related calls
|
||||
//------------------------------------------
|
||||
$action = $request[2];
|
||||
$post_content = json_decode($input,true);
|
||||
|
||||
//SET PARAMETERS FOR QUERY
|
||||
$date = date('Y-m-d H:i:s');
|
||||
$current_date = date('Y-m-d');
|
||||
|
||||
// Connect to DB (if needed for future enhancements)
|
||||
$pdo = dbConnect($dbname);
|
||||
|
||||
if ($action == 'log'){
|
||||
|
||||
// Validate input data
|
||||
if (!empty($post_content['data']) && isset($post_content['direction'])) {
|
||||
|
||||
$communication_data = $post_content['data'];
|
||||
$direction = $post_content['direction']; // 'sent' or 'received'
|
||||
$timestamp = $post_content['timestamp'] ?? $date;
|
||||
$serial_number = $post_content['serial_number'] ?? '';
|
||||
$maintenance_run = $post_content['maintenance_run'] ?? 0;
|
||||
|
||||
// Create log directory if it doesn't exist
|
||||
$log_dir = './log/';
|
||||
if (!is_dir($log_dir)) {
|
||||
mkdir($log_dir, 0755, true);
|
||||
}
|
||||
|
||||
// Create filename based on current date (YYYY-MM-DD)
|
||||
$log_file = $log_dir .'serial'. $current_date . '.log';
|
||||
|
||||
// Format log entry
|
||||
$log_entry = sprintf(
|
||||
"[%s] [%s] [SN:%s] [MAINT:%d] %s: %s\n",
|
||||
$timestamp,
|
||||
$direction,
|
||||
$serial_number,
|
||||
$maintenance_run,
|
||||
strtoupper($direction),
|
||||
$communication_data
|
||||
);
|
||||
|
||||
// Append to log file
|
||||
if (file_put_contents($log_file, $log_entry, FILE_APPEND | LOCK_EX) !== false) {
|
||||
// Success response
|
||||
echo json_encode(array(
|
||||
'status' => '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'
|
||||
));
|
||||
}
|
||||
?>
|
||||
@@ -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){
|
||||
|
||||
218
logfile.php
218
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')?>
|
||||
|
||||
<form action="" method="post">
|
||||
<?php if ($delete_message): ?>
|
||||
<div class="msg success" id="delete-notification">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<p><?=$delete_message?></p>
|
||||
<i class="fas fa-times"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="" method="post">
|
||||
<div class="content-title responsive-flex-wrap responsive-pad-bot-3">
|
||||
<h2 class="responsive-width-100">Debuglog</h2>
|
||||
<input type="submit" name="submit" value="Save" class="btn">
|
||||
</div>
|
||||
|
||||
<?php if (isset($success_msg)): ?>
|
||||
<div class="msg success">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<p><?=$success_msg?></p>
|
||||
<i class="fas fa-times"></i>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="tabs">
|
||||
<a href="#" class="active">Application <?php echo date('d'); ?></a>
|
||||
<a href="#" class="">Webserver</a>
|
||||
|
||||
</div>
|
||||
<div class="content-block">
|
||||
<div class="form responsive-width-100">
|
||||
<div class="tab-content active">
|
||||
<label for="Logfile"></label>
|
||||
<textarea name="logfile" id="logfile" style="min-height: 100vh;"><?=$contents?></textarea>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<label for="Logfile"></label>
|
||||
<textarea name="" id="" style="min-height: 100vh;"><?=$contents_webserver?></textarea>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; align-items: center;">
|
||||
<select id="log-file-selector" onchange="loadLogFile(this.value)" style="padding: 5px 10px; border: 1px solid #ddd; border-radius: 4px;">
|
||||
<?php foreach ($log_files as $log): ?>
|
||||
<?php $selected = ($log['filename'] === $selected_log) ? 'selected' : ''; ?>
|
||||
<?php $size_kb = round($log['size'] / 1024, 2); ?>
|
||||
<?php $date_str = date('Y-m-d H:i', $log['mtime']); ?>
|
||||
<option value="<?=htmlspecialchars($log['filename'])?>" <?=$selected?>>
|
||||
<?=htmlspecialchars($log['filename'])?> (<?=$size_kb?> KB) - <?=$date_str?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="button" onclick="refreshLog()" class="btn" >
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</button>
|
||||
<button type="submit" name="delete_all_logs" onclick="return confirmDeleteAll()" class="btn" style="background: #e74c3c; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer;">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<a href="#" class="active">Application</a>
|
||||
<a href="#" class="">Webserver</a>
|
||||
</div>
|
||||
|
||||
<div class="content-block">
|
||||
<div class="form responsive-width-100">
|
||||
<div class="tab-content active">
|
||||
<label for="Logfile">Application Log</label>
|
||||
<textarea name="logfile" id="logfile" style="min-height: 70vh; font-family: 'Courier New', monospace; font-size: 12px; background: #1e1e1e; color: #f8f8f2; border: 1px solid #333; padding: 15px;"><?=$contents?></textarea>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<label for="WebserverLog">Webserver Log</label>
|
||||
<textarea name="" id="webserver-log" style="min-height: 70vh; font-family: 'Courier New', monospace; font-size: 12px; background: #1e1e1e; color: #f8f8f2; border: 1px solid #333; padding: 15px;"><?=$contents_webserver?></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll("input[type='checkbox']").forEach(checkbox => {
|
||||
checkbox.onclick = () => checkbox.value = checkbox.checked ? 'true' : 'false';
|
||||
// Confirm delete all logs
|
||||
function confirmDeleteAll() {
|
||||
return confirm('⚠️ WARNING: This will permanently delete ALL log files in the logs directory.\n\nAre you absolutely sure you want to continue?');
|
||||
}
|
||||
|
||||
// Auto-hide delete notification
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const notification = document.getElementById('delete-notification');
|
||||
if (notification) {
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '0';
|
||||
notification.style.transition = 'opacity 0.5s ease';
|
||||
setTimeout(() => notification.remove(), 500);
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
|
||||
// Load selected log file
|
||||
function loadLogFile(filename) {
|
||||
if (filename) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('log', filename);
|
||||
window.location.href = url.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh log functionality
|
||||
function refreshLog() {
|
||||
const button = event.target.closest('button');
|
||||
const originalHTML = button.innerHTML;
|
||||
|
||||
// Show loading state
|
||||
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Refreshing...';
|
||||
button.disabled = true;
|
||||
|
||||
// Reload the page to get fresh content
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Clear log content from view
|
||||
function clearLogContent() {
|
||||
const textarea = document.getElementById('logfile');
|
||||
if (textarea) {
|
||||
if (confirm('Clear the log content from view? (This won\'t delete the actual log file)')) {
|
||||
textarea.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-scroll to bottom of logs when page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const activeTextarea = document.querySelector('.tab-content.active textarea');
|
||||
if (activeTextarea && activeTextarea.value.trim()) {
|
||||
activeTextarea.scrollTop = activeTextarea.scrollHeight;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user