Compare commits
8 Commits
fdef8e2df0
...
020594e065
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
020594e065 | ||
|
|
5a1a4e0d81 | ||
|
|
3cd5b13c25 | ||
|
|
5a0f1c0745 | ||
|
|
2516a3bd1c | ||
|
|
1b8dc54fe0 | ||
|
|
2bd74ca91a | ||
|
|
f40c105abf |
@@ -59,6 +59,8 @@ ALL ALL=(ALL) NOPASSWD: /usr/bin/nmcli, /usr/sbin/reboot
|
|||||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/git pull
|
www-data ALL=(ALL) NOPASSWD: /usr/bin/git pull
|
||||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/ssh
|
www-data ALL=(ALL) NOPASSWD: /usr/bin/ssh
|
||||||
www-data ALL=(ALL) NOPASSWD: /usr/bin/python3 *
|
www-data ALL=(ALL) NOPASSWD: /usr/bin/python3 *
|
||||||
|
www-data ALL=(ALL) NOPASSWD: /bin/systemctl *
|
||||||
|
www-data ALL=(ALL) NOPASSWD: /var/www/nebuleair_pro_4g/*
|
||||||
```
|
```
|
||||||
## Serial
|
## Serial
|
||||||
|
|
||||||
|
|||||||
547
html/admin.html
547
html/admin.html
@@ -76,52 +76,45 @@
|
|||||||
|
|
||||||
<!-- config_scripts_table -->
|
<!-- 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">
|
<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)">
|
<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">
|
<label class="form-check-label" for="check_NPM_5channels">
|
||||||
Next PM send 5 channels (no script)
|
Send Next PM 5 channels data
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<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">
|
<label class="form-check-label" for="check_bme280">
|
||||||
Sonde temp/hum (BME280)
|
Send temp/hum data (BME280)
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
<input class="form-check-input" type="checkbox" value="" id="check_envea" onchange="update_config_scripts_sqlite('envea/read_value_v2.py', this.checked)">
|
<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">
|
<label class="form-check-label" for="check_envea">
|
||||||
Sonde Envea
|
Send Envea sensor data
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
<input class="form-check-input" type="checkbox" value="" id="check_solarBattery" onchange="update_config_scripts_sqlite('MPPT/read.py', this.checked)">
|
<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">
|
<label class="form-check-label" for="check_solarBattery">
|
||||||
Solar / Battery MPPT
|
Send Solar / Battery MPPT data
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
<input class="form-check-input" type="checkbox" value="" id="check_WindMeter" onchange="update_config_sqlite('windMeter', this.checked)">
|
<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">
|
<label class="form-check-label" for="check_WindMeter">
|
||||||
Wind Meter (no script -> systemd service)
|
Send Wind Meter data
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<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>
|
<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">
|
<label class="form-check-label" for="check_uSpot">
|
||||||
uSpot
|
Send to uSpot
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -172,12 +165,77 @@
|
|||||||
<div class="col-lg-4 col-12">
|
<div class="col-lg-4 col-12">
|
||||||
<h3 class="mt-4">Updates</h3>
|
<h3 class="mt-4">Updates</h3>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary" onclick="updateGitPull()">Update firmware</button>
|
<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>
|
||||||
|
|
||||||
</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 -->
|
<!-- toast -->
|
||||||
|
|
||||||
@@ -268,14 +326,20 @@ window.onload = function() {
|
|||||||
const modem_version = document.getElementById("modem_version");
|
const modem_version = document.getElementById("modem_version");
|
||||||
modem_version.value = response.modem_version;
|
modem_version.value = response.modem_version;
|
||||||
|
|
||||||
//nextPM send 5 channels
|
|
||||||
|
|
||||||
const checkbox_nmp5channels = document.getElementById("check_NPM_5channels");
|
const checkbox_nmp5channels = document.getElementById("check_NPM_5channels");
|
||||||
checkbox_nmp5channels.checked = response.npm_5channel;
|
|
||||||
//windMeter (as a config not a script -> it's running with a systemd service)
|
|
||||||
const checkbox_wind = document.getElementById("check_WindMeter");
|
const checkbox_wind = document.getElementById("check_WindMeter");
|
||||||
checkbox_wind.checked = response["windMeter"];
|
|
||||||
//send uSpot
|
|
||||||
const checkbox_uSpot = document.getElementById("check_uSpot");
|
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"];
|
checkbox_uSpot.checked = response["send_uSpot"];
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -284,37 +348,7 @@ window.onload = function() {
|
|||||||
}
|
}
|
||||||
});//end AJAX
|
});//end AJAX
|
||||||
|
|
||||||
//getting config_scripts table
|
|
||||||
$.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);
|
|
||||||
|
|
||||||
const checkbox_NPM = document.getElementById("check_NPM");
|
|
||||||
const checkbox_bme = document.getElementById("check_bme280");
|
|
||||||
const checkbox_envea = document.getElementById("check_envea");
|
|
||||||
const checkbox_solar = document.getElementById("check_solarBattery");
|
|
||||||
|
|
||||||
checkbox_NPM.checked = response["NPM/get_data_modbus_v3.py"];
|
|
||||||
checkbox_bme.checked = response["BME280/get_data_v2.py"];
|
|
||||||
checkbox_envea.checked = response["envea/read_value_v2.py"];
|
|
||||||
checkbox_solar.checked = response["MPPT/read.py"];
|
|
||||||
|
|
||||||
|
|
||||||
//si sonde envea is true
|
|
||||||
if (response["envea/read_value_v2.py"]) {
|
|
||||||
add_sondeEnveaContainer();
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
});//end AJAX
|
|
||||||
|
|
||||||
|
|
||||||
//OLD way to get config (JSON)
|
//OLD way to get config (JSON)
|
||||||
@@ -409,6 +443,8 @@ window.onload = function() {
|
|||||||
}
|
}
|
||||||
}); //end AJAx
|
}); //end AJAx
|
||||||
|
|
||||||
|
// Load services on page load
|
||||||
|
refreshServices();
|
||||||
|
|
||||||
} //end window.onload
|
} //end window.onload
|
||||||
|
|
||||||
@@ -466,62 +502,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){
|
function update_config(param, value){
|
||||||
console.log("Updating ",param," : ", value);
|
console.log("Updating ",param," : ", value);
|
||||||
@@ -539,28 +519,112 @@ function update_config(param, value){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateGitPull(){
|
function updateFirmware() {
|
||||||
console.log("Updating device (git pull)");
|
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({
|
$.ajax({
|
||||||
url: 'launcher.php?type=git_pull',
|
url: 'launcher.php?type=update_firmware',
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
method: 'GET',
|
||||||
dataType: 'text', // Specify that you expect a JSON response
|
dataType: 'json',
|
||||||
|
timeout: 120000, // 2 minutes timeout
|
||||||
|
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
// Handle success response if needed
|
console.log('Update completed:', response);
|
||||||
console.log(response);
|
|
||||||
alert(response);
|
|
||||||
// Reload the page after the device update
|
|
||||||
location.reload(); // This will reload the page
|
|
||||||
|
|
||||||
|
// 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) {
|
error: function(xhr, status, error) {
|
||||||
console.error('AJAX request failed:', 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(){
|
function set_RTC_withNTP(){
|
||||||
console.log("Set RTC module with WIFI (NTP server)");
|
console.log("Set RTC module with WIFI (NTP server)");
|
||||||
|
|
||||||
@@ -925,6 +989,251 @@ function updateSondeCoefficient(id, coefficient) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
____ _ __ __ _
|
||||||
|
/ ___| ___ _ ____ _(_) ___ ___| \/ | __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
|
||||||
|
\___ \ / _ \ '__\ \ / / |/ __/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
|
||||||
|
___) | __/ | \ 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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -218,52 +218,6 @@ if ($type == "update_config_sqlite") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//UPDATING the config_scripts table from SQLite DB
|
|
||||||
if ($type == "update_config_scripts_sqlite") {
|
|
||||||
$script_path = $_GET['param'] ?? null;
|
|
||||||
$enabled = $_GET['value'] ?? null;
|
|
||||||
|
|
||||||
if ($script_path === null || $enabled === null) {
|
|
||||||
echo json_encode(["error" => "Missing parameter or value"]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$db = new PDO("sqlite:$database_path");
|
|
||||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
||||||
|
|
||||||
// First, check if parameter exists and get its type
|
|
||||||
$checkStmt = $db->prepare("SELECT enabled FROM config_scripts_table WHERE script_path = :script_path");
|
|
||||||
$checkStmt->bindParam(':script_path', $script_path);
|
|
||||||
$checkStmt->execute();
|
|
||||||
$result = $checkStmt->fetch(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($result) {
|
|
||||||
// Convert enabled value to 0 or 1
|
|
||||||
$enabledValue = (filter_var($enabled, FILTER_VALIDATE_BOOLEAN)) ? 1 : 0;
|
|
||||||
|
|
||||||
// Update the enabled status
|
|
||||||
$updateStmt = $db->prepare("UPDATE config_scripts_table SET enabled = :enabled WHERE script_path = :script_path");
|
|
||||||
$updateStmt->bindParam(':enabled', $enabledValue, PDO::PARAM_INT);
|
|
||||||
$updateStmt->bindParam(':script_path', $script_path);
|
|
||||||
$updateStmt->execute();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
"success" => true,
|
|
||||||
"message" => "Script configuration updated successfully",
|
|
||||||
"script_path" => $script_path,
|
|
||||||
"enabled" => (bool)$enabledValue
|
|
||||||
], JSON_UNESCAPED_SLASHES); // Prevent escaping forward slashes
|
|
||||||
} else {
|
|
||||||
echo json_encode([
|
|
||||||
"error" => "Script path not found in configuration",
|
|
||||||
"script_path" => $script_path
|
|
||||||
], JSON_UNESCAPED_SLASHES); // Prevent escaping forward slashes
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
echo json_encode(["error" => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//UPDATING the envea_sondes_table table from SQLite DB
|
//UPDATING the envea_sondes_table table from SQLite DB
|
||||||
if ($type == "update_sonde") {
|
if ($type == "update_sonde") {
|
||||||
@@ -395,6 +349,20 @@ if ($type == "git_pull") {
|
|||||||
echo $output;
|
echo $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($type == "update_firmware") {
|
||||||
|
// Execute the comprehensive update script
|
||||||
|
$command = 'sudo /var/www/nebuleair_pro_4g/update_firmware.sh 2>&1';
|
||||||
|
$output = shell_exec($command);
|
||||||
|
|
||||||
|
// Return the output as JSON for better web display
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'output' => $output,
|
||||||
|
'timestamp' => date('Y-m-d H:i:s')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($type == "set_RTC_withNTP") {
|
if ($type == "set_RTC_withNTP") {
|
||||||
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py';
|
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py';
|
||||||
$output = shell_exec($command);
|
$output = shell_exec($command);
|
||||||
@@ -1014,3 +982,219 @@ if ($type == "execute_command") {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
____ _ ____ _ __ __ _
|
||||||
|
/ ___| _ _ ___| |_ ___ _ __ ___ | _ \ / ___| ___ _ ____ _(_) ___ ___| \/ | __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
|
||||||
|
\___ \| | | / __| __/ _ \ '_ ` _ \| | | | \___ \ / _ \ '__\ \ / / |/ __/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
|
||||||
|
___) | |_| \__ \ || __/ | | | | | |_| | ___) | __/ | \ V /| | (_| __/ | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_
|
||||||
|
|____/ \__, |___/\__\___|_| |_| |_|____/ |____/ \___|_| \_/ |_|\___\___|_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__|
|
||||||
|
|___/ |___/
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get systemd services status
|
||||||
|
if ($type == "get_systemd_services") {
|
||||||
|
try {
|
||||||
|
// List of NebuleAir services to monitor with descriptions and frequencies
|
||||||
|
$services = [
|
||||||
|
'nebuleair-npm-data.timer' => [
|
||||||
|
'description' => 'Collects particulate matter data from NextPM sensor',
|
||||||
|
'frequency' => 'Every 10 seconds'
|
||||||
|
],
|
||||||
|
'nebuleair-envea-data.timer' => [
|
||||||
|
'description' => 'Reads environmental data from Envea sensors',
|
||||||
|
'frequency' => 'Every 10 seconds'
|
||||||
|
],
|
||||||
|
'nebuleair-sara-data.timer' => [
|
||||||
|
'description' => 'Transmits collected data via 4G cellular modem',
|
||||||
|
'frequency' => 'Every 60 seconds'
|
||||||
|
],
|
||||||
|
'nebuleair-bme280-data.timer' => [
|
||||||
|
'description' => 'Monitors temperature and humidity (BME280)',
|
||||||
|
'frequency' => 'Every 2 minutes'
|
||||||
|
],
|
||||||
|
'nebuleair-mppt-data.timer' => [
|
||||||
|
'description' => 'Tracks solar panel and battery status',
|
||||||
|
'frequency' => 'Every 2 minutes'
|
||||||
|
],
|
||||||
|
'nebuleair-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 = [
|
||||||
|
'nebuleair-npm-data.timer',
|
||||||
|
'nebuleair-envea-data.timer',
|
||||||
|
'nebuleair-sara-data.timer',
|
||||||
|
'nebuleair-bme280-data.timer',
|
||||||
|
'nebuleair-mppt-data.timer',
|
||||||
|
'nebuleair-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 = [
|
||||||
|
'nebuleair-npm-data.timer',
|
||||||
|
'nebuleair-envea-data.timer',
|
||||||
|
'nebuleair-sara-data.timer',
|
||||||
|
'nebuleair-bme280-data.timer',
|
||||||
|
'nebuleair-mppt-data.timer',
|
||||||
|
'nebuleair-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()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<div class="col-lg-6 col-12">
|
<div class="col-lg-6 col-12">
|
||||||
<div class="card" style="height: 80vh;">
|
<div class="card" style="height: 80vh;">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Master logs
|
Sara logs
|
||||||
<button type="submit" class="btn btn-secondary btn-sm" id="refresh-master-log">Refresh</button>
|
<button type="submit" class="btn btn-secondary btn-sm" id="refresh-master-log">Refresh</button>
|
||||||
<button type="submit" class="btn btn-secondary btn-sm" onclick="clear_loopLogs()">Clear</button>
|
<button type="submit" class="btn btn-secondary btn-sm" onclick="clear_loopLogs()">Clear</button>
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ fi
|
|||||||
# Add sudo authorization (prevent duplicate entries)
|
# Add sudo authorization (prevent duplicate entries)
|
||||||
info "Setting up sudo authorization..."
|
info "Setting up sudo authorization..."
|
||||||
if ! sudo grep -q "/usr/bin/nmcli" /etc/sudoers; then
|
if ! sudo grep -q "/usr/bin/nmcli" /etc/sudoers; then
|
||||||
echo -e "ALL ALL=(ALL) NOPASSWD: /usr/bin/nmcli, /usr/sbin/reboot\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/git pull\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/ssh\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/python3 *" | sudo tee -a /etc/sudoers > /dev/null
|
echo -e "ALL ALL=(ALL) NOPASSWD: /usr/bin/nmcli, /usr/sbin/reboot\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/git pull\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/ssh\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/python3 * www-data ALL=(ALL) NOPASSWD: /bin/systemctl * www-data ALL=(ALL) NOPASSWD: /var/www/nebuleair_pro_4g/*" | sudo tee -a /etc/sudoers > /dev/null
|
||||||
success "Sudo authorization added."
|
success "Sudo authorization added."
|
||||||
else
|
else
|
||||||
warning "Sudo authorization already set. Skipping."
|
warning "Sudo authorization already set. Skipping."
|
||||||
|
|||||||
@@ -200,30 +200,6 @@ def load_config_sqlite():
|
|||||||
print(f"Error loading config from SQLite: {e}")
|
print(f"Error loading config from SQLite: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def load_config_scripts_sqlite():
|
|
||||||
"""
|
|
||||||
Load script configuration data from SQLite config_scripts_table
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: Script paths as keys and enabled status as boolean values
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Query the config_scripts_table
|
|
||||||
cursor.execute("SELECT script_path, enabled FROM config_scripts_table")
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
|
|
||||||
# Create config dictionary with script paths as keys and enabled status as boolean values
|
|
||||||
scripts_config = {}
|
|
||||||
for script_path, enabled in rows:
|
|
||||||
# Convert integer enabled value (0/1) to boolean
|
|
||||||
scripts_config[script_path] = bool(enabled)
|
|
||||||
|
|
||||||
return scripts_config
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error loading scripts config from SQLite: {e}")
|
|
||||||
return {}
|
|
||||||
|
|
||||||
#Load config
|
#Load config
|
||||||
config = load_config_sqlite()
|
config = load_config_sqlite()
|
||||||
#config
|
#config
|
||||||
@@ -234,16 +210,13 @@ device_latitude_raw = config.get('latitude_raw', 0)
|
|||||||
device_longitude_raw = config.get('longitude_raw', 0)
|
device_longitude_raw = config.get('longitude_raw', 0)
|
||||||
modem_version=config.get('modem_version', "")
|
modem_version=config.get('modem_version', "")
|
||||||
Sara_baudrate = config.get('SaraR4_baudrate', 115200)
|
Sara_baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
npm_5channel = config.get('npm_5channel', False) #5 canaux du NPM
|
|
||||||
selected_networkID = int(config.get('SARA_R4_neworkID', 0))
|
selected_networkID = int(config.get('SARA_R4_neworkID', 0))
|
||||||
send_uSpot = config.get('send_uSpot', False) #envoi sur MicroSpot ()
|
send_uSpot = config.get('send_uSpot', False) #envoi sur MicroSpot ()
|
||||||
|
npm_5channel = config.get('npm_5channel', False) #5 canaux du NPM
|
||||||
|
envea_cairsens= config.get('envea', False)
|
||||||
wind_meter= config.get('windMeter', False)
|
wind_meter= config.get('windMeter', False)
|
||||||
|
bme_280_config = config.get('BME280', False)
|
||||||
#config_scripts
|
mppt_charger= config.get('MPPT', False)
|
||||||
config_scripts = load_config_scripts_sqlite()
|
|
||||||
bme_280_config = config_scripts.get('BME280/get_data_v2.py', False)
|
|
||||||
envea_cairsens= config_scripts.get('envea/read_value_v2.py', False)
|
|
||||||
mppt_charger= config_scripts.get('MPPT/read.py', False)
|
|
||||||
|
|
||||||
#update device id in the payload json
|
#update device id in the payload json
|
||||||
payload_json["nebuleairid"] = device_id
|
payload_json["nebuleairid"] = device_id
|
||||||
|
|||||||
@@ -27,14 +27,6 @@ CREATE TABLE IF NOT EXISTS config_table (
|
|||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
#creates a config_scripts table
|
|
||||||
cursor.execute('''
|
|
||||||
CREATE TABLE IF NOT EXISTS config_scripts_table (
|
|
||||||
script_path TEXT PRIMARY KEY,
|
|
||||||
enabled INTEGER NOT NULL
|
|
||||||
)
|
|
||||||
''')
|
|
||||||
|
|
||||||
#creates a config table for envea sondes
|
#creates a config table for envea sondes
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS envea_sondes_table (
|
CREATE TABLE IF NOT EXISTS envea_sondes_table (
|
||||||
@@ -46,7 +38,6 @@ CREATE TABLE IF NOT EXISTS envea_sondes_table (
|
|||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
# Create a table timer
|
# Create a table timer
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS timestamp_table (
|
CREATE TABLE IF NOT EXISTS timestamp_table (
|
||||||
|
|||||||
@@ -20,37 +20,14 @@ cursor = conn.cursor()
|
|||||||
|
|
||||||
print(f"Connected to database")
|
print(f"Connected to database")
|
||||||
|
|
||||||
# Clear existing data (if any)
|
# Note: Using INSERT OR IGNORE to add only new configurations without overwriting existing ones
|
||||||
cursor.execute("DELETE FROM config_table")
|
print("Adding new configurations (existing ones will be preserved)")
|
||||||
cursor.execute("DELETE FROM config_scripts_table")
|
|
||||||
cursor.execute("DELETE FROM envea_sondes_table")
|
|
||||||
print("Existing data cleared")
|
|
||||||
|
|
||||||
#add values
|
|
||||||
|
|
||||||
# Insert script configurations
|
|
||||||
script_configs = [
|
|
||||||
("NPM/get_data_modbus_v3.py", True),
|
|
||||||
("loop/SARA_send_data_v2.py", True),
|
|
||||||
("RTC/save_to_db.py", True),
|
|
||||||
("BME280/get_data_v2.py", True),
|
|
||||||
("envea/read_value_v2.py", False),
|
|
||||||
("MPPT/read.py", False),
|
|
||||||
("windMeter/read.py", False),
|
|
||||||
("sqlite/flush_old_data.py", True)
|
|
||||||
]
|
|
||||||
|
|
||||||
for script_path, enabled in script_configs:
|
|
||||||
cursor.execute(
|
|
||||||
"INSERT INTO config_scripts_table (script_path, enabled) VALUES (?, ?)",
|
|
||||||
(script_path, 1 if enabled else 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Insert general configurations
|
# Insert general configurations
|
||||||
config_entries = [
|
config_entries = [
|
||||||
("modem_config_mode", "0", "bool"),
|
("modem_config_mode", "0", "bool"),
|
||||||
("deviceID", "XXXX", "str"),
|
("deviceID", "XXXX", "str"),
|
||||||
("npm_5channel", "0", "bool"),
|
|
||||||
("latitude_raw", "0", "int"),
|
("latitude_raw", "0", "int"),
|
||||||
("longitude_raw", "0", "int"),
|
("longitude_raw", "0", "int"),
|
||||||
("latitude_precision", "0", "int"),
|
("latitude_precision", "0", "int"),
|
||||||
@@ -65,13 +42,17 @@ config_entries = [
|
|||||||
("SARA_R4_neworkID", "20810", "int"),
|
("SARA_R4_neworkID", "20810", "int"),
|
||||||
("WIFI_status", "connected", "str"),
|
("WIFI_status", "connected", "str"),
|
||||||
("send_uSpot", "0", "bool"),
|
("send_uSpot", "0", "bool"),
|
||||||
|
("npm_5channel", "0", "bool"),
|
||||||
|
("envea", "0", "bool"),
|
||||||
("windMeter", "0", "bool"),
|
("windMeter", "0", "bool"),
|
||||||
|
("BME280", "0", "bool"),
|
||||||
|
("MPPT", "0", "bool"),
|
||||||
("modem_version", "XXX", "str")
|
("modem_version", "XXX", "str")
|
||||||
]
|
]
|
||||||
|
|
||||||
for key, value, value_type in config_entries:
|
for key, value, value_type in config_entries:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"INSERT INTO config_table (key, value, type) VALUES (?, ?, ?)",
|
"INSERT OR IGNORE INTO config_table (key, value, type) VALUES (?, ?, ?)",
|
||||||
(key, value, value_type)
|
(key, value, value_type)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -84,7 +65,7 @@ envea_sondes = [
|
|||||||
|
|
||||||
for connected, port, name, coefficient in envea_sondes:
|
for connected, port, name, coefficient in envea_sondes:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"INSERT INTO envea_sondes_table (connected, port, name, coefficient) VALUES (?, ?, ?, ?)",
|
"INSERT OR IGNORE INTO envea_sondes_table (connected, port, name, coefficient) VALUES (?, ?, ?, ?)",
|
||||||
(1 if connected else 0, port, name, coefficient)
|
(1 if connected else 0, port, name, coefficient)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
118
update_firmware.sh
Normal file
118
update_firmware.sh
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# NebuleAir Pro 4G - Comprehensive Update Script
|
||||||
|
# This script performs a complete system update including git pull,
|
||||||
|
# config initialization, and service management
|
||||||
|
|
||||||
|
echo "======================================"
|
||||||
|
echo "NebuleAir Pro 4G - Firmware Update"
|
||||||
|
echo "======================================"
|
||||||
|
echo "Started at: $(date)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
cd /var/www/nebuleair_pro_4g
|
||||||
|
|
||||||
|
# Function to print status messages
|
||||||
|
print_status() {
|
||||||
|
echo "[$(date '+%H:%M:%S')] $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check command success
|
||||||
|
check_status() {
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
print_status "✓ $1 completed successfully"
|
||||||
|
else
|
||||||
|
print_status "✗ $1 failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Step 1: Git operations
|
||||||
|
print_status "Step 1: Updating firmware from repository..."
|
||||||
|
git fetch origin
|
||||||
|
check_status "Git fetch"
|
||||||
|
|
||||||
|
# Show current branch and any changes
|
||||||
|
print_status "Current branch: $(git branch --show-current)"
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
print_status "Warning: Local changes detected:"
|
||||||
|
git status --short
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pull latest changes
|
||||||
|
git pull origin $(git branch --show-current)
|
||||||
|
check_status "Git pull"
|
||||||
|
|
||||||
|
# Step 2: Update database configuration
|
||||||
|
print_status ""
|
||||||
|
print_status "Step 2: Updating database configuration..."
|
||||||
|
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/set_config.py
|
||||||
|
check_status "Database configuration update"
|
||||||
|
|
||||||
|
# Step 3: Check and fix file permissions
|
||||||
|
print_status ""
|
||||||
|
print_status "Step 3: Checking file permissions..."
|
||||||
|
sudo chmod +x /var/www/nebuleair_pro_4g/update_firmware.sh
|
||||||
|
sudo chmod 755 /var/www/nebuleair_pro_4g/sqlite/*.py
|
||||||
|
sudo chmod 755 /var/www/nebuleair_pro_4g/NPM/*.py
|
||||||
|
sudo chmod 755 /var/www/nebuleair_pro_4g/BME280/*.py
|
||||||
|
sudo chmod 755 /var/www/nebuleair_pro_4g/SARA/*.py
|
||||||
|
sudo chmod 755 /var/www/nebuleair_pro_4g/envea/*.py
|
||||||
|
check_status "File permissions update"
|
||||||
|
|
||||||
|
# Step 4: Restart critical services if they exist
|
||||||
|
print_status ""
|
||||||
|
print_status "Step 4: Managing system services..."
|
||||||
|
|
||||||
|
# List of services to check and restart
|
||||||
|
services=(
|
||||||
|
"nebuleair-npm-data.timer"
|
||||||
|
"nebuleair-envea-data.timer"
|
||||||
|
"nebuleair-sara-data.timer"
|
||||||
|
"nebuleair-bme280-data.timer"
|
||||||
|
"nebuleair-mppt-data.timer"
|
||||||
|
)
|
||||||
|
|
||||||
|
for service in "${services[@]}"; do
|
||||||
|
if systemctl list-unit-files | grep -q "$service"; then
|
||||||
|
print_status "Restarting service: $service"
|
||||||
|
sudo systemctl restart "$service"
|
||||||
|
if systemctl is-active --quiet "$service"; then
|
||||||
|
print_status "✓ $service is running"
|
||||||
|
else
|
||||||
|
print_status "⚠ $service may not be active"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_status "ℹ Service $service not found (may not be installed)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Step 5: System health check
|
||||||
|
print_status ""
|
||||||
|
print_status "Step 5: System health check..."
|
||||||
|
|
||||||
|
# Check disk space
|
||||||
|
disk_usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
|
||||||
|
if [ "$disk_usage" -gt 90 ]; then
|
||||||
|
print_status "⚠ Warning: Disk usage is high ($disk_usage%)"
|
||||||
|
else
|
||||||
|
print_status "✓ Disk usage is acceptable ($disk_usage%)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if database is accessible
|
||||||
|
if [ -f "/var/www/nebuleair_pro_4g/sqlite/sensors.db" ]; then
|
||||||
|
print_status "✓ Database file exists"
|
||||||
|
else
|
||||||
|
print_status "⚠ Warning: Database file not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 6: Final cleanup
|
||||||
|
print_status ""
|
||||||
|
print_status "Step 6: Cleaning up..."
|
||||||
|
sudo find /var/www/nebuleair_pro_4g/logs -name "*.log" -size +10M -exec truncate -s 0 {} \;
|
||||||
|
check_status "Log cleanup"
|
||||||
|
|
||||||
|
print_status "Update completed successfully!"
|
||||||
|
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user