update
This commit is contained in:
592
html/admin.html
592
html/admin.html
@@ -71,39 +71,41 @@
|
||||
|
||||
<!-- config_scripts_table -->
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_NPM" onchange="update_config_scripts_sqlite('NPM/get_data_modbus_v3.py', this.checked)">
|
||||
<label class="form-check-label" for="check_NPM">
|
||||
Next PM
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
Next PM send 5 channels (no script)
|
||||
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_scripts_sqlite('BME280/get_data_v2.py', this.checked)">
|
||||
<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">
|
||||
Sonde temp/hum (BME280)
|
||||
Send temp/hum data (BME280)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_CO2" onchange="update_config_scripts_sqlite('MH-Z19/write_data.py', this.checked)">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_CO2" onchange="update_config_sqlite('MHZ19', this.checked)">
|
||||
<label class="form-check-label" for="check_CO2">
|
||||
Sonde CO2 (MH-Z19)
|
||||
Send CO2 data (MH-Z19)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_sensirionSFA30" onchange="update_config_scripts_sqlite('sensirion/SFA30_read.py', this.checked)">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_sensirionSFA30" onchange="update_config_sqlite('SFA30', this.checked)">
|
||||
<label class="form-check-label" for="check_sensirionSFA30">
|
||||
Sonde Formaldehyde (Sensiririon SFA30)
|
||||
Send Formaldehyde data (Sensiririon SFA30)
|
||||
</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>
|
||||
|
||||
@@ -158,6 +160,44 @@
|
||||
|
||||
</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 -->
|
||||
|
||||
@@ -218,126 +258,111 @@
|
||||
|
||||
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 ID
|
||||
const deviceID = response.deviceID.trim().toUpperCase();
|
||||
const device_ID = document.getElementById("device_ID");
|
||||
device_ID.value = response.deviceID.toUpperCase();
|
||||
//nextPM send 5 channels
|
||||
const checkbox_nmp5channels = document.getElementById("check_NPM_5channels");
|
||||
checkbox_nmp5channels.checked = response.npm_5channel;
|
||||
|
||||
|
||||
|
||||
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('AJAX request failed:', status, error);
|
||||
}
|
||||
});//end AJAX
|
||||
|
||||
//getting config_scripts table
|
||||
//NEW way to get config (SQLite)
|
||||
$.ajax({
|
||||
url: 'launcher.php?type=get_config_scripts_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 scripts table:");
|
||||
console.log(response);
|
||||
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 ID
|
||||
const deviceID = response.deviceID.trim().toUpperCase();
|
||||
const device_ID = document.getElementById("device_ID");
|
||||
device_ID.value = response.deviceID.toUpperCase();
|
||||
|
||||
const checkbox_NPM = document.getElementById("check_NPM");
|
||||
const checkbox_bme = document.getElementById("check_bme280");
|
||||
const checkbox_CO2 = document.getElementById("check_CO2");
|
||||
const checkbox_SFA30 = document.getElementById("check_sensirionSFA30");
|
||||
|
||||
checkbox_NPM.checked = response["NPM/get_data_modbus_v3.py"];
|
||||
checkbox_bme.checked = response["BME280/get_data_v2.py"];
|
||||
checkbox_SFA30.checked = response["sensirion/SFA30_read.py"];
|
||||
checkbox_CO2.checked = response["MH-Z19/write_data.py"];
|
||||
|
||||
//si sonde envea is true
|
||||
if (response["envea/read_value_v2.py"]) {
|
||||
add_sondeEnveaContainer();
|
||||
const checkbox_nmp5channels = document.getElementById("check_NPM_5channels");
|
||||
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('AJAX request failed:', status, error);
|
||||
}
|
||||
});//end AJAX
|
||||
const checkbox_bme = document.getElementById("check_bme280");
|
||||
const checkbox_CO2 = document.getElementById("check_CO2");
|
||||
const checkbox_SFA30 = document.getElementById("check_sensirionSFA30");
|
||||
|
||||
//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(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;
|
||||
checkbox_nmp5channels.checked = response.npm_5channel;
|
||||
checkbox_bme.checked = response["BME280"];
|
||||
checkbox_SFA30.checked = response["SFA30"];
|
||||
checkbox_CO2.checked = response["MH-Z19"];
|
||||
|
||||
// Get the time difference
|
||||
const timeDiff = response.time_difference_seconds;
|
||||
//si sonde envea is true
|
||||
if (response["envea"]) {
|
||||
add_sondeEnveaContainer();
|
||||
}
|
||||
|
||||
// Reference to the alert container
|
||||
const alertContainer = document.getElementById("alert_container");
|
||||
|
||||
// Remove any previous alert
|
||||
alertContainer.innerHTML = "";
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('AJAX request failed:', status, error);
|
||||
}
|
||||
});//end AJAX
|
||||
|
||||
|
||||
|
||||
// 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 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(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 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
|
||||
// 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 windows on load
|
||||
|
||||
@@ -391,78 +416,6 @@ function update_config_sqlite(param, value){
|
||||
});
|
||||
}
|
||||
|
||||
function update_config_scripts_sqlite(param, value) {
|
||||
console.log("Updating scripts sqlite ", param, " : ", value);
|
||||
const toastLiveExample = document.getElementById('liveToast')
|
||||
const toastBody = toastLiveExample.querySelector('.toast-body');
|
||||
|
||||
$.ajax({
|
||||
url: 'launcher.php?type=update_config_scripts_sqlite¶m=' + param + '&value=' + value,
|
||||
dataType: 'json',
|
||||
method: 'GET',
|
||||
cache: false,
|
||||
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.script_path || param}<br>
|
||||
Value: ${response.enabled !== undefined ? response.enabled : value}<br>
|
||||
${response.message || ''}
|
||||
`;
|
||||
|
||||
if (response.script_path == "envea/read_value_v2.py") {
|
||||
console.log("envea sondes activated");
|
||||
add_sondeEnveaContainer();
|
||||
|
||||
}
|
||||
} 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.script_path || 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¶m='+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 updateGitPull(){
|
||||
console.log("Updating device (git pull)");
|
||||
@@ -537,6 +490,253 @@ function set_RTC_withBrowser(){
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
____ _ __ __ _
|
||||
/ ___| ___ _ ____ _(_) ___ ___| \/ | __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
|
||||
\___ \ / _ \ '__\ \ / / |/ __/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
|
||||
___) | __/ | \ 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>
|
||||
|
||||
@@ -664,3 +664,215 @@ if ($type == "wifi_scan_old") {
|
||||
echo $json_data;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
____ _ ____ _ __ __ _
|
||||
/ ___| _ _ ___| |_ ___ _ __ ___ | _ \ / ___| ___ _ ____ _(_) ___ ___| \/ | __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
|
||||
\___ \| | | / __| __/ _ \ '_ ` _ \| | | | \___ \ / _ \ '__\ \ / / |/ __/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
|
||||
___) | |_| \__ \ || __/ | | | | | |_| | ___) | __/ | \ V /| | (_| __/ | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_
|
||||
|____/ \__, |___/\__\___|_| |_| |_|____/ |____/ \___|_| \_/ |_|\___\___|_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__|
|
||||
|___/ |___/
|
||||
*/
|
||||
|
||||
// Get systemd services status
|
||||
if ($type == "get_systemd_services") {
|
||||
try {
|
||||
// List of NebuleAir services to monitor with descriptions and frequencies
|
||||
$services = [
|
||||
'moduleair-npm-data.timer' => [
|
||||
'description' => 'Collects particulate matter data from NextPM sensor',
|
||||
'frequency' => 'Every 10 seconds'
|
||||
],
|
||||
'moduleair-co2-data.timer' => [
|
||||
'description' => 'Reads environmental data from Envea sensors',
|
||||
'frequency' => 'Every 10 seconds'
|
||||
],
|
||||
'moduleair-sara-data.timer' => [
|
||||
'description' => 'Transmits collected data via 4G cellular modem',
|
||||
'frequency' => 'Every 60 seconds'
|
||||
],
|
||||
'moduleair-bme280-data.timer' => [
|
||||
'description' => 'Monitors temperature and humidity (BME280)',
|
||||
'frequency' => 'Every 2 minutes'
|
||||
],
|
||||
'moduleair-db-cleanup-data.timer' => [
|
||||
'description' => 'Cleans up old data from database',
|
||||
'frequency' => 'Daily'
|
||||
]
|
||||
];
|
||||
|
||||
$serviceStatus = [];
|
||||
|
||||
foreach ($services as $service => $serviceInfo) {
|
||||
// Get service active status
|
||||
$activeCmd = "systemctl is-active " . escapeshellarg($service) . " 2>/dev/null";
|
||||
$activeStatus = trim(shell_exec($activeCmd));
|
||||
$isActive = ($activeStatus === 'active');
|
||||
|
||||
// Get service enabled status
|
||||
$enabledCmd = "systemctl is-enabled " . escapeshellarg($service) . " 2>/dev/null";
|
||||
$enabledStatus = trim(shell_exec($enabledCmd));
|
||||
$isEnabled = ($enabledStatus === 'enabled');
|
||||
|
||||
// Clean up service name for display
|
||||
$displayName = str_replace(['.timer', 'nebuleair-', '-data'], '', $service);
|
||||
$displayName = ucfirst(str_replace('-', ' ', $displayName));
|
||||
|
||||
$serviceStatus[] = [
|
||||
'name' => $service,
|
||||
'display_name' => $displayName,
|
||||
'description' => $serviceInfo['description'],
|
||||
'frequency' => $serviceInfo['frequency'],
|
||||
'active' => $isActive,
|
||||
'enabled' => $isEnabled
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'services' => $serviceStatus
|
||||
], JSON_PRETTY_PRINT);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Restart a systemd service
|
||||
if ($type == "restart_systemd_service") {
|
||||
$service = $_GET['service'] ?? null;
|
||||
|
||||
if (empty($service)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'No service specified'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validate service name (security check)
|
||||
$allowedServices = [
|
||||
'moduleair-npm-data.timer',
|
||||
'moduleair-sara-data.timer',
|
||||
'nebuleair-bme280-data.timer',
|
||||
'moduleair-co2-data.timer',
|
||||
'moduleair-db-cleanup-data.timer'
|
||||
];
|
||||
|
||||
if (!in_array($service, $allowedServices)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Invalid service name'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
// Restart the service
|
||||
$command = "sudo systemctl restart " . escapeshellarg($service) . " 2>&1";
|
||||
$output = shell_exec($command);
|
||||
|
||||
// Check if restart was successful
|
||||
$statusCmd = "systemctl is-active " . escapeshellarg($service) . " 2>/dev/null";
|
||||
$status = trim(shell_exec($statusCmd));
|
||||
|
||||
if ($status === 'active' || empty($output)) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => "Service $service restarted successfully"
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => "Failed to restart service: $output"
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable/disable a systemd service
|
||||
if ($type == "toggle_systemd_service") {
|
||||
$service = $_GET['service'] ?? null;
|
||||
$enable = $_GET['enable'] ?? null;
|
||||
|
||||
if (empty($service) || $enable === null) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Missing service name or enable parameter'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validate service name (security check)
|
||||
$allowedServices = [
|
||||
'moduleair-npm-data.timer',
|
||||
'moduleair-sara-data.timer',
|
||||
'nebuleair-bme280-data.timer',
|
||||
'moduleair-co2-data.timer',
|
||||
'moduleair-db-cleanup-data.timer'
|
||||
];
|
||||
|
||||
if (!in_array($service, $allowedServices)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Invalid service name'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$enable = filter_var($enable, FILTER_VALIDATE_BOOLEAN);
|
||||
$action = $enable ? 'enable' : 'disable';
|
||||
|
||||
// Enable/disable the service
|
||||
$command = "sudo systemctl $action " . escapeshellarg($service) . " 2>&1";
|
||||
$output = shell_exec($command);
|
||||
|
||||
// If disabling, also stop the service
|
||||
if (!$enable) {
|
||||
$stopCommand = "sudo systemctl stop " . escapeshellarg($service) . " 2>&1";
|
||||
$stopOutput = shell_exec($stopCommand);
|
||||
}
|
||||
|
||||
// If enabling, also start the service
|
||||
if ($enable) {
|
||||
$startCommand = "sudo systemctl start " . escapeshellarg($service) . " 2>&1";
|
||||
$startOutput = shell_exec($startCommand);
|
||||
}
|
||||
|
||||
// Check if the operation was successful
|
||||
$statusCmd = "systemctl is-enabled " . escapeshellarg($service) . " 2>/dev/null";
|
||||
$status = trim(shell_exec($statusCmd));
|
||||
|
||||
$expectedStatus = $enable ? 'enabled' : 'disabled';
|
||||
|
||||
if ($status === $expectedStatus) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => "Service $service " . ($enable ? 'enabled and started' : 'disabled and stopped') . " successfully"
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => "Failed to $action service: $output"
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user