Files
nebuleair_pro_4g/html/admin.html
Your Name 020594e065 updates
2025-05-23 15:09:22 +02:00

1242 lines
45 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NebuleAir</title>
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
<style>
body {
overflow-x: hidden;
}
#sidebar a.nav-link {
position: relative;
display: flex;
align-items: center;
}
#sidebar a.nav-link:hover {
background-color: rgba(0, 0, 0, 0.5);
}
#sidebar a.nav-link svg {
margin-right: 8px; /* Add spacing between icons and text */
}
#sidebar {
transition: transform 0.3s ease-in-out;
}
.offcanvas-backdrop {
z-index: 1040;
}
</style>
</head>
<body>
<!-- Topbar -->
<span id="topbar"></span>
<!-- Sidebar Offcanvas for Mobile -->
<div class="offcanvas offcanvas-start text-white bg-dark" tabindex="-1" id="sidebarOffcanvas" aria-labelledby="sidebarOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="sidebarOffcanvasLabel">NebuleAir</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body" id="sidebar_mobile">
</div>
</div>
<div class="container-fluid mt-5">
<div class="row">
<!-- Side bar -->
<aside class="col-md-2 col-lg-1 d-none d-md-block vh-100 position-fixed bg-dark text-white" id="sidebar">
</aside>
<!-- Main content -->
<main class="col-md-10 ms-sm-auto col-lg-11 offset-md-2 offset-lg-1 px-md-4">
<h1 class="mt-4">Admin</h1>
<div class="row mb-3">
<div class="col-lg-3 col-12">
<h3 class="mt-4">Parameters (config)</h3>
<form>
<div class="mb-3">
<label for="device_name" class="form-label">Device Name</label>
<input type="text" class="form-control" id="device_name" onchange="update_config_sqlite('deviceName', this.value)">
</div>
<div class="mb-3">
<label for="device_ID" class="form-label">Device ID</label>
<input type="text" class="form-control" id="device_ID" disabled>
</div>
<div class="mb-3">
<label for="modem_version" class="form-label">Modem Version</label>
<input type="text" class="form-control" id="modem_version" disabled>
</div>
<!-- config_scripts_table -->
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="" id="check_NPM_5channels" onchange="update_config_sqlite('npm_5channel', this.checked)">
<label class="form-check-label" for="check_NPM_5channels">
Send Next PM 5 channels data
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="" id="check_bme280" onchange="update_config_sqlite('BME280', this.checked)">
<label class="form-check-label" for="check_bme280">
Send temp/hum data (BME280)
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="" id="check_envea" onchange="update_config_sqlite('envea', this.checked)">
<label class="form-check-label" for="check_envea">
Send Envea sensor data
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="" id="check_solarBattery" onchange="update_config_sqlite('MPPT', this.checked)">
<label class="form-check-label" for="check_solarBattery">
Send Solar / Battery MPPT data
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="" id="check_WindMeter" onchange="update_config_sqlite('windMeter', this.checked)">
<label class="form-check-label" for="check_WindMeter">
Send Wind Meter data
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" value="" id="check_uSpot" onchange="update_config_sqlite('send_uSpot', this.checked)" disabled>
<label class="form-check-label" for="check_uSpot">
Send to uSpot
</label>
</div>
<div class="input-group mb-3" id="sondes_envea_div"></div>
<div id="envea_table"></div>
<!--<button type="submit" class="btn btn-primary">Submit</button>-->
</form>
</div>
<!-- CLOCK-->
<div class="col-lg-3 col-12">
<h3 class="mt-4">Clock</h3>
<div class="mb-3">
<label for="sys_local_time" class="form-label">System time (local)</label>
<input type="text" class="form-control" id="sys_local_time" disabled>
</div>
<div class="mb-3">
<label for="sys_UTC_time" class="form-label">System time (UTC)</label>
<input type="text" class="form-control" id="sys_UTC_time" disabled>
</div>
<div class="mb-3">
<label for="RTC_utc_time" class="form-label">RTC time (UTC)</label>
<input type="text" class="form-control" id="RTC_utc_time" disabled>
</div>
<div id="alert_container"></div>
<h5 class="mt-4">Set RTC</h5>
<button type="submit" class="btn btn-primary mb-1" onclick="set_RTC_withNTP()">WiFi (NTP) </button>
<button type="submit" class="btn btn-primary mb-1" onclick="set_RTC_withBrowser()">Browser time </button>
<button type="submit" class="btn btn-primary mb-1" onclick="set_RTC_with4G()" disabled>4G (NTP) </button>
</div>
<!-- UPDATE-->
<div class="col-lg-4 col-12">
<h3 class="mt-4">Updates</h3>
<button type="submit" class="btn btn-primary" onclick="updateFirmware()" id="updateBtn">
<span id="updateBtnText">Update firmware</span>
<span id="updateSpinner" class="spinner-border spinner-border-sm ms-2" style="display: none;"></span>
</button>
<!-- Update Output Console -->
<div id="updateOutput" class="mt-3" style="display: none;">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span class="fw-bold">Update Log</span>
<div>
<button type="button" class="btn btn-sm btn-success me-2" onclick="location.reload()" id="reloadBtn" style="display: none;">
<svg width="14" height="14" fill="currentColor" class="bi bi-arrow-clockwise me-1" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
</svg>
Reload Page
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearUpdateOutput()">
Clear
</button>
</div>
</div>
<div class="card-body">
<pre id="updateOutputContent" class="mb-0" style="max-height: 400px; overflow-y: auto; font-size: 0.85rem; background-color: #f8f9fa; padding: 1rem; border-radius: 0.375rem;"></pre>
</div>
</div>
</div>
</div>
</div>
<!-- SYSTEMD SERVICES SECTION -->
<div class="row mb-3">
<div class="col-lg-8 col-12">
<h4 class="mt-4">SystemD Services</h4>
<div id="services-table" class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span class="fw-bold">Service Status</span>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="refreshServices()">
<svg width="14" height="14" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
</svg>
Refresh
</button>
</div>
<div class="card-body p-0">
<table class="table table-sm table-hover mb-0">
<thead class="table-light">
<tr>
<th style="width: 20%">Service</th>
<th style="width: 25%">Description</th>
<th style="width: 15%">Frequency</th>
<th style="width: 10%">Status</th>
<th style="width: 10%">Enabled</th>
<th style="width: 20%">Actions</th>
</tr>
</thead>
<tbody id="services-tbody">
<tr>
<td colspan="6" class="text-center py-3">Loading services...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- toast -->
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="liveToast" class="toast align-items-center text-bg-primary border-1" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
Hello, world! This is a toast message.
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
</div>
</main>
</div>
</div>
<!-- JAVASCRIPT -->
<!-- Link Ajax locally -->
<script src="assets/jquery/jquery-3.7.1.min.js"></script>
<!-- Link Bootstrap JS and Popper.js locally -->
<script src="assets/js/bootstrap.bundle.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const elementsToLoad = [
{ id: 'topbar', file: 'topbar.html' },
{ id: 'sidebar', file: 'sidebar.html' },
{ id: 'sidebar_mobile', file: 'sidebar.html' }
];
elementsToLoad.forEach(({ id, file }) => {
fetch(file)
.then(response => response.text())
.then(data => {
const element = document.getElementById(id);
if (element) {
element.innerHTML = data;
}
})
.catch(error => console.error(`Error loading ${file}:`, error));
});
});
//end document.addEventListener
/*
___ _ _
/ _ \ _ __ | | ___ __ _ __| |
| | | | '_ \| | / _ \ / _` |/ _` |
| |_| | | | | |__| (_) | (_| | (_| |
\___/|_| |_|_____\___/ \__,_|\__,_|
*/
window.onload = function() {
//NEW way to get config (SQLite)
$.ajax({
url: 'launcher.php?type=get_config_sqlite',
dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Getting SQLite config table:");
console.log(response);
//device name
const deviceName = document.getElementById("device_name");
deviceName.value = response.deviceName;
//device name_side bar
const elements = document.querySelectorAll('.sideBar_sensorName');
elements.forEach((element) => {
element.innerText = response.deviceName;
});
//device name html page title
if (response.deviceName) {
document.title = response.deviceName;
}
//device ID
const deviceID = response.deviceID.trim().toUpperCase();
const device_ID = document.getElementById("device_ID");
device_ID.value = response.deviceID.toUpperCase();
//modem_version
const modem_version = document.getElementById("modem_version");
modem_version.value = response.modem_version;
const checkbox_nmp5channels = document.getElementById("check_NPM_5channels");
const checkbox_wind = document.getElementById("check_WindMeter");
const checkbox_uSpot = document.getElementById("check_uSpot");
const checkbox_bme = document.getElementById("check_bme280");
const checkbox_envea = document.getElementById("check_envea");
const checkbox_solar = document.getElementById("check_solarBattery");
checkbox_bme.checked = response["BME280"];
checkbox_envea.checked = response["envea"];
checkbox_solar.checked = response["MPPT"];
checkbox_nmp5channels.checked = response.npm_5channel;
checkbox_wind.checked = response["windMeter"];
checkbox_uSpot.checked = response["send_uSpot"];
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});//end AJAX
//OLD way to get config (JSON)
/*
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
.then(response => response.json()) // Parse response as JSON
.then(data => {
console.log("Getting config file (onload)");
//get device ID
//document.getElementById('pageTitle_plus_ID').innerText = 'token: ' + deviceID;
//get device Name
//const deviceName = data.deviceName;
//get BME check
const checkbox = document.getElementById("check_bme280");
checkbox.checked = data["BME280/get_data_v2.py"];
//get NPM-5channels check
const checkbox_NPM_5channels = document.getElementById("check_NPM_5channels");
checkbox_NPM_5channels.checked = data["NextPM_5channels"];
//get sonde Envea check
const checkbox_envea = document.getElementById("check_envea");
checkbox_envea.checked = data["envea/read_value_v2.py"];
//device name
//const device_name = document.getElementById("device_name");
//device_name.value = data.deviceName;
})
.catch(error => console.error('Error loading config.json:', error));
*/
//get system time and RTC module
$.ajax({
url: 'launcher.php?type=sys_RTC_module_time',
dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Getting RTC times");
console.log(response);
// Update the input fields with the received JSON data
document.getElementById("sys_local_time").value = response.system_local_time;
document.getElementById("sys_UTC_time").value = response.system_utc_time;
document.getElementById("RTC_utc_time").value = response.rtc_module_time;
// Get the time difference
const timeDiff = response.time_difference_seconds;
// Reference to the alert container
const alertContainer = document.getElementById("alert_container");
// Remove any previous alert
alertContainer.innerHTML = "";
// Add an alert based on time difference
if (typeof timeDiff === "number") {
if (timeDiff >= 0 && timeDiff <= 10) {
alertContainer.innerHTML = `
<div class="alert alert-success" role="alert">
RTC and system time are in sync (Difference: ${timeDiff} sec).
</div>`;
} else if (timeDiff > 10) {
alertContainer.innerHTML = `
<div class="alert alert-danger" role="alert">
RTC time is out of sync! (Difference: ${timeDiff} sec).
</div>`;
}
}
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});//end AJAX
//get local RTC
$.ajax({
url: 'launcher.php?type=RTC_time',
dataType: 'text', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
//console.log("Local RTC: " + response);
const RTC_Element = document.getElementById("RTC_time");
RTC_Element.textContent = response;
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
}); //end AJAx
// Load services on page load
refreshServices();
} //end window.onload
function update_config_sqlite(param, value){
console.log("Updating sqlite ",param," : ", value);
const toastLiveExample = document.getElementById('liveToast')
const toastBody = toastLiveExample.querySelector('.toast-body');
$.ajax({
url: 'launcher.php?type=update_config_sqlite&param='+param+'&value='+value,
dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
cache: false, // Prevent AJAX from caching
success: function(response) {
console.log(response);
// Format the response nicely
let formattedMessage = '';
if (response.success) {
// Success message
toastLiveExample.classList.remove('text-bg-danger');
toastLiveExample.classList.add('text-bg-success');
formattedMessage = `
<strong>Success!</strong><br>
Parameter: ${response.param || param}<br>
Value: ${response.value || checked}<br>
${response.message || ''}
`;
} else {
// Error message
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
formattedMessage = `
<strong>Error!</strong><br>
${response.error || 'Unknown error'}<br>
Parameter: ${response.param || param}
`;
}
// Update the toast body with formatted content
toastBody.innerHTML = formattedMessage;
// Show the toast
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample)
toastBootstrap.show()
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function update_config(param, value){
console.log("Updating ",param," : ", value);
$.ajax({
url: 'launcher.php?type=update_config&param='+param+'&value='+value,
dataType: 'text', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
cache: false, // Prevent AJAX from caching
success: function(response) {
console.log(response);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function updateFirmware() {
console.log("Starting comprehensive firmware update...");
// Show loading state
const updateBtn = document.getElementById('updateBtn');
const updateBtnText = document.getElementById('updateBtnText');
const updateSpinner = document.getElementById('updateSpinner');
const updateOutput = document.getElementById('updateOutput');
const updateOutputContent = document.getElementById('updateOutputContent');
// Disable button and show spinner
updateBtn.disabled = true;
updateBtnText.textContent = 'Updating...';
updateSpinner.style.display = 'inline-block';
// Show output console
updateOutput.style.display = 'block';
updateOutputContent.textContent = 'Starting update process...\n';
$.ajax({
url: 'launcher.php?type=update_firmware',
method: 'GET',
dataType: 'json',
timeout: 120000, // 2 minutes timeout
success: function(response) {
console.log('Update completed:', response);
// Display formatted output
if (response.success && response.output) {
// Format the output for better readability
const formattedOutput = response.output
.replace(/\[\d{2}:\d{2}:\d{2}\]/g, function(match) {
return `<span style="color: #007bff; font-weight: bold;">${match}</span>`;
})
.replace(/✓/g, '<span style="color: #28a745;">✓</span>')
.replace(/✗/g, '<span style="color: #dc3545;">✗</span>')
.replace(/⚠/g, '<span style="color: #ffc107;">⚠</span>')
.replace(//g, '<span style="color: #17a2b8;"></span>');
updateOutputContent.innerHTML = formattedOutput;
// Show success toast and reload button
showToast('Update completed successfully!', 'success');
document.getElementById('reloadBtn').style.display = 'inline-block';
} else {
updateOutputContent.textContent = 'Update completed but no output received.';
showToast('Update may have completed with issues', 'warning');
}
},
error: function(xhr, status, error) {
console.error('Update failed:', status, error);
updateOutputContent.textContent = `Update failed: ${error}\n\nStatus: ${status}\nResponse: ${xhr.responseText || 'No response'}`;
showToast('Update failed! Check the output for details.', 'error');
},
complete: function() {
// Reset button state
updateBtn.disabled = false;
updateBtnText.textContent = 'Update firmware';
updateSpinner.style.display = 'none';
}
});
}
function clearUpdateOutput() {
const updateOutput = document.getElementById('updateOutput');
const updateOutputContent = document.getElementById('updateOutputContent');
const reloadBtn = document.getElementById('reloadBtn');
updateOutputContent.textContent = '';
updateOutput.style.display = 'none';
reloadBtn.style.display = 'none';
}
function showToast(message, type) {
const toastLiveExample = document.getElementById('liveToast');
const toastBody = toastLiveExample.querySelector('.toast-body');
// Set toast color based on type
toastLiveExample.classList.remove('text-bg-primary', 'text-bg-success', 'text-bg-danger', 'text-bg-warning');
switch(type) {
case 'success':
toastLiveExample.classList.add('text-bg-success');
break;
case 'error':
toastLiveExample.classList.add('text-bg-danger');
break;
case 'warning':
toastLiveExample.classList.add('text-bg-warning');
break;
default:
toastLiveExample.classList.add('text-bg-primary');
}
toastBody.textContent = message;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
}
// Legacy function for backward compatibility
function updateGitPull() {
updateFirmware();
}
function set_RTC_withNTP(){
console.log("Set RTC module with WIFI (NTP server)");
$.ajax({
url: 'launcher.php?type=set_RTC_withNTP',
method: 'GET', // Use GET or POST depending on your needs
dataType: 'text', // Specify that you expect a JSON response
success: function(response) {
// Handle success response if needed
console.log(response);
alert(response);
// Reload the page after the device update
location.reload(); // This will reload the page
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
}); //end ajax
}
function set_RTC_withBrowser(){
console.log("Set RTC module with browser time");
const browserTime = new Date(); // Get the current time in the browser
const formattedTime = browserTime.toISOString(); // Convert to ISO string (UTC)
console.log(formattedTime);
$.ajax({
url: `launcher.php?type=set_RTC_withBrowser&time=${encodeURIComponent(formattedTime)}`,
method: 'GET', // Use GET or POST depending on your needs
dataType: 'json', // Specify that you expect a JSON response
success: function(response) {
// Handle success response if needed
console.log(response);
if (response.success) {
alert("RTC successfully updated!");
} else {
alert(`Error: ${response.message}`);
} // Reload the page after the device update
location.reload(); // This will reload the page
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
}); //end ajax
}
/*
____ _ _____
/ ___| ___ _ __ __| | ___ ___ | ____|_ ____ _____ __ _
\___ \ / _ \| '_ \ / _` |/ _ \/ __| | _| | '_ \ \ / / _ \/ _` |
___) | (_) | | | | (_| | __/\__ \ | |___| | | \ V / __/ (_| |
|____/ \___/|_| |_|\__,_|\___||___/ |_____|_| |_|\_/ \___|\__,_|
*/
function add_sondeEnveaContainer() {
console.log("Sonde Envea is true: need to add container!");
// Getting envea_sondes_table data
$.ajax({
url: 'launcher.php?type=get_envea_sondes_table_sqlite',
dataType: 'json',
method: 'GET',
success: function(sondes) {
console.log("Getting SQLite envea sondes table:");
console.log(sondes);
// Create container div if it doesn't exist
if ($('#sondes_envea_div').length === 0) {
$('#advanced_options').append('<div id="sondes_envea_div" class="input-group mt-4 border p-3 rounded"><legend>Sondes Envea</legend><p>Plouf</p></div>');
} else {
// Clear existing content if container exists
$('#sondes_envea_div').html('<legend>Sondes Envea</legend>');
$('#envea_table').html('<table class="table table-striped table-bordered">'+
'<thead><tr><th scope="col">Software</th><th scope="col">Hardware (PCB)</th></tr></thead>'+
'<tbody>' +
'<tr><td>ttyAMA5</td><td>NPM1</td></tr>' +
'<tr><td>ttyAMA4</td><td>NPM2</td></tr>' +
'<tr><td>ttyAMA3</td><td>NPM3</td></tr>' +
'<tr><td>ttyAMA2</td><td>SARA</td></tr>' +
'</tbody></table>');
}
// Loop through each sonde and create UI elements
sondes.forEach(function(sonde) {
// Create a unique ID for this sonde
const sondeId = `sonde_${sonde.id}`;
// Create HTML for this sonde
const sondeHtml = `
<div class="input-group mb-3" id="${sondeId}_container">
<div class="input-group-text">
<input class="form-check-input mt-0" type="checkbox" id="${sondeId}_enabled"
${sonde.connected ? 'checked' : ''}
onchange="updateSondeStatus(${sonde.id}, this.checked)">
</div>
<input type="text" class="form-control" placeholder="Name" value="${sonde.name}"
id="${sondeId}_name" onchange="updateSondeName(${sonde.id}, this.value)">
<input type="text" class="form-control" placeholder="Port" value="${sonde.port}"
id="${sondeId}_port" onchange="updateSondePort(${sonde.id}, this.value)">
<input type="number" class="form-control" placeholder="Coefficient" value="${sonde.coefficient}"
id="${sondeId}_coefficient" onchange="updateSondeCoefficient(${sonde.id}, this.value)">
</div>
`;
// Append this sonde to the container
$('#sondes_envea_div').append(sondeHtml);
});
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
// Helper functions for updating sonde properties
function updateSondeStatus(id, connected) {
console.log(`Updating sonde ${id} connected status to: ${connected}`);
const toastLiveExample = document.getElementById('liveToast');
const toastBody = toastLiveExample.querySelector('.toast-body');
$.ajax({
url: `launcher.php?type=update_sonde&id=${id}&field=connected&value=${connected ? 1 : 0}`,
dataType: 'json',
method: 'GET',
cache: false,
success: function(response) {
console.log('Sonde status updated:', response);
// Format the response for toast
let formattedMessage = '';
if (response.success) {
// Success message
toastLiveExample.classList.remove('text-bg-danger');
toastLiveExample.classList.add('text-bg-success');
formattedMessage = `
<strong>Success!</strong><br>
Sonde ID: ${response.id}<br>
Connected: ${connected ? "Yes" : "No"}<br>
${response.message || ''}
`;
} else {
// Error message
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
formattedMessage = `
<strong>Error!</strong><br>
${response.error || 'Unknown error'}<br>
Sonde ID: ${id}
`;
}
// Update and show toast
toastBody.innerHTML = formattedMessage;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
},
error: function(xhr, status, error) {
console.error('Failed to update sonde status:', error);
// Show error toast
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
toastBody.innerHTML = `
<strong>Request Failed!</strong><br>
Error: ${error}<br>
Sonde ID: ${id}
`;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
}
});
}
function updateSondeName(id, name) {
console.log(`Updating sonde ${id} name to: ${name}`);
const toastLiveExample = document.getElementById('liveToast');
const toastBody = toastLiveExample.querySelector('.toast-body');
$.ajax({
url: `launcher.php?type=update_sonde&id=${id}&field=name&value=${encodeURIComponent(name)}`,
dataType: 'json',
method: 'GET',
cache: false,
success: function(response) {
console.log('Sonde name updated:', response);
// Format the response for toast
let formattedMessage = '';
if (response.success) {
// Success message
toastLiveExample.classList.remove('text-bg-danger');
toastLiveExample.classList.add('text-bg-success');
formattedMessage = `
<strong>Success!</strong><br>
Sonde ID: ${response.id}<br>
Name: ${name}<br>
${response.message || ''}
`;
} else {
// Error message
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
formattedMessage = `
<strong>Error!</strong><br>
${response.error || 'Unknown error'}<br>
Sonde ID: ${id}
`;
}
// Update and show toast
toastBody.innerHTML = formattedMessage;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
},
error: function(xhr, status, error) {
console.error('Failed to update sonde name:', error);
// Show error toast
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
toastBody.innerHTML = `
<strong>Request Failed!</strong><br>
Error: ${error}<br>
Sonde ID: ${id}
`;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
}
});
}
function updateSondePort(id, port) {
console.log(`Updating sonde ${id} port to: ${port}`);
const toastLiveExample = document.getElementById('liveToast');
const toastBody = toastLiveExample.querySelector('.toast-body');
$.ajax({
url: `launcher.php?type=update_sonde&id=${id}&field=port&value=${encodeURIComponent(port)}`,
dataType: 'json',
method: 'GET',
cache: false,
success: function(response) {
console.log('Sonde port updated:', response);
// Format the response for toast
let formattedMessage = '';
if (response.success) {
// Success message
toastLiveExample.classList.remove('text-bg-danger');
toastLiveExample.classList.add('text-bg-success');
formattedMessage = `
<strong>Success!</strong><br>
Sonde ID: ${response.id}<br>
Port: ${port}<br>
${response.message || ''}
`;
} else {
// Error message
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
formattedMessage = `
<strong>Error!</strong><br>
${response.error || 'Unknown error'}<br>
Sonde ID: ${id}
`;
}
// Update and show toast
toastBody.innerHTML = formattedMessage;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
},
error: function(xhr, status, error) {
console.error('Failed to update sonde port:', error);
// Show error toast
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
toastBody.innerHTML = `
<strong>Request Failed!</strong><br>
Error: ${error}<br>
Sonde ID: ${id}
`;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
}
});
}
function updateSondeCoefficient(id, coefficient) {
console.log(`Updating sonde ${id} coefficient to: ${coefficient}`);
const toastLiveExample = document.getElementById('liveToast');
const toastBody = toastLiveExample.querySelector('.toast-body');
$.ajax({
url: `launcher.php?type=update_sonde&id=${id}&field=coefficient&value=${coefficient}`,
dataType: 'json',
method: 'GET',
cache: false,
success: function(response) {
console.log('Sonde coefficient updated:', response);
// Format the response for toast
let formattedMessage = '';
if (response.success) {
// Success message
toastLiveExample.classList.remove('text-bg-danger');
toastLiveExample.classList.add('text-bg-success');
formattedMessage = `
<strong>Success!</strong><br>
Sonde ID: ${response.id}<br>
Coefficient: ${coefficient}<br>
${response.message || ''}
`;
} else {
// Error message
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
formattedMessage = `
<strong>Error!</strong><br>
${response.error || 'Unknown error'}<br>
Sonde ID: ${id}
`;
}
// Update and show toast
toastBody.innerHTML = formattedMessage;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
},
error: function(xhr, status, error) {
console.error('Failed to update sonde coefficient:', error);
// Show error toast
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
toastBody.innerHTML = `
<strong>Request Failed!</strong><br>
Error: ${error}<br>
Sonde ID: ${id}
`;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
}
});
}
/*
____ _ __ __ _
/ ___| ___ _ ____ _(_) ___ ___| \/ | __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
\___ \ / _ \ '__\ \ / / |/ __/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
___) | __/ | \ V /| | (_| __/ | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_
|____/ \___|_| \_/ |_|\___\___|_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__|
|___/
*/
function refreshServices() {
console.log("Refreshing services status");
$.ajax({
url: 'launcher.php?type=get_systemd_services',
dataType: 'json',
method: 'GET',
cache: false,
success: function(response) {
console.log("Services data:", response);
if (response.success) {
displayServices(response.services);
} else {
showServiceError("Failed to load services: " + response.error);
}
},
error: function(xhr, status, error) {
console.error('Failed to load services:', error);
showServiceError("Failed to load services: " + error);
}
});
}
function displayServices(services) {
const tbody = document.getElementById('services-tbody');
tbody.innerHTML = '';
services.forEach(function(service) {
const row = document.createElement('tr');
// Service name
const nameCell = document.createElement('td');
nameCell.textContent = service.display_name || service.name;
row.appendChild(nameCell);
// Description
const descCell = document.createElement('td');
descCell.textContent = service.description || 'No description available';
descCell.className = 'text-muted small';
row.appendChild(descCell);
// Frequency
const freqCell = document.createElement('td');
freqCell.textContent = service.frequency || 'Unknown';
freqCell.className = 'text-primary small fw-bold';
row.appendChild(freqCell);
// Status
const statusCell = document.createElement('td');
const statusBadge = document.createElement('span');
statusBadge.className = `badge ${service.active ? 'bg-success' : 'bg-danger'}`;
statusBadge.textContent = service.active ? 'Running' : 'Stopped';
statusCell.appendChild(statusBadge);
row.appendChild(statusCell);
// Enabled
const enabledCell = document.createElement('td');
const enabledBadge = document.createElement('span');
enabledBadge.className = `badge ${service.enabled ? 'bg-info' : 'bg-secondary'}`;
enabledBadge.textContent = service.enabled ? 'Enabled' : 'Disabled';
enabledCell.appendChild(enabledBadge);
row.appendChild(enabledCell);
// Actions
const actionsCell = document.createElement('td');
// Restart button
const restartBtn = document.createElement('button');
restartBtn.className = 'btn btn-sm btn-warning me-2';
restartBtn.innerHTML = '<i class="bi bi-arrow-clockwise"></i> Restart';
restartBtn.onclick = function() {
restartService(service.name);
};
actionsCell.appendChild(restartBtn);
// Enable/Disable button
const toggleBtn = document.createElement('button');
toggleBtn.className = `btn btn-sm ${service.enabled ? 'btn-danger' : 'btn-success'}`;
toggleBtn.innerHTML = service.enabled ? '<i class="bi bi-stop"></i> Disable' : '<i class="bi bi-play"></i> Enable';
toggleBtn.onclick = function() {
toggleService(service.name, !service.enabled);
};
actionsCell.appendChild(toggleBtn);
row.appendChild(actionsCell);
tbody.appendChild(row);
});
}
function showServiceError(message) {
const tbody = document.getElementById('services-tbody');
tbody.innerHTML = `
<tr>
<td colspan="6" class="text-center text-danger">
<i class="bi bi-exclamation-triangle"></i> ${message}
</td>
</tr>
`;
}
function restartService(serviceName) {
console.log(`Restarting service: ${serviceName}`);
if (!confirm(`Are you sure you want to restart ${serviceName}?`)) {
return;
}
const toastLiveExample = document.getElementById('liveToast');
const toastBody = toastLiveExample.querySelector('.toast-body');
$.ajax({
url: 'launcher.php?type=restart_systemd_service&service=' + encodeURIComponent(serviceName),
dataType: 'json',
method: 'GET',
cache: false,
success: function(response) {
console.log('Service restart response:', response);
let formattedMessage = '';
if (response.success) {
// Success message
toastLiveExample.classList.remove('text-bg-danger');
toastLiveExample.classList.add('text-bg-success');
formattedMessage = `
<strong>Success!</strong><br>
Service: ${serviceName}<br>
${response.message || 'Service restarted successfully'}
`;
// Refresh services after a short delay
setTimeout(refreshServices, 2000);
} else {
// Error message
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
formattedMessage = `
<strong>Error!</strong><br>
Service: ${serviceName}<br>
${response.error || 'Unknown error occurred'}
`;
}
// Update and show toast
toastBody.innerHTML = formattedMessage;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
},
error: function(xhr, status, error) {
console.error('Failed to restart service:', error);
// Show error toast
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
toastBody.innerHTML = `
<strong>Request Failed!</strong><br>
Service: ${serviceName}<br>
Error: ${error}
`;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
}
});
}
function toggleService(serviceName, enable) {
const action = enable ? 'enable' : 'disable';
console.log(`${action} service: ${serviceName}`);
if (!confirm(`Are you sure you want to ${action} ${serviceName}?`)) {
return;
}
const toastLiveExample = document.getElementById('liveToast');
const toastBody = toastLiveExample.querySelector('.toast-body');
$.ajax({
url: 'launcher.php?type=toggle_systemd_service&service=' + encodeURIComponent(serviceName) + '&enable=' + enable,
dataType: 'json',
method: 'GET',
cache: false,
success: function(response) {
console.log('Service toggle response:', response);
let formattedMessage = '';
if (response.success) {
// Success message
toastLiveExample.classList.remove('text-bg-danger');
toastLiveExample.classList.add('text-bg-success');
formattedMessage = `
<strong>Success!</strong><br>
Service: ${serviceName}<br>
${response.message || `Service ${action}d successfully`}
`;
// Refresh services after a short delay
setTimeout(refreshServices, 2000);
} else {
// Error message
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
formattedMessage = `
<strong>Error!</strong><br>
Service: ${serviceName}<br>
${response.error || 'Unknown error occurred'}
`;
}
// Update and show toast
toastBody.innerHTML = formattedMessage;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
},
error: function(xhr, status, error) {
console.error('Failed to toggle service:', error);
// Show error toast
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
toastBody.innerHTML = `
<strong>Request Failed!</strong><br>
Service: ${serviceName}<br>
Error: ${error}
`;
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
}
});
}
</script>
</body>
</html>