1967 lines
75 KiB
HTML
Executable File
1967 lines
75 KiB
HTML
Executable File
<!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>
|
||
|
||
<button class="btn btn-success mb-3 btn_selfTest" onclick="runSelfTest()">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check2-circle me-1" viewBox="0 0 16 16">
|
||
<path d="M2.5 8a5.5 5.5 0 1 1 11 0 5.5 5.5 0 0 1-11 0z"/>
|
||
<path d="M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l7-7z"/>
|
||
</svg>
|
||
Run Self Test
|
||
</button>
|
||
|
||
<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);add_sondeEnveaContainer() ">
|
||
<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_NOISE" onchange="update_config_sqlite('NOISE', this.checked)">
|
||
<label class="form-check-label" for="check_NOISE">
|
||
Send Noise data
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-check mb-3">
|
||
<input class="form-check-input" type="checkbox" value="" id="check_mhz19" onchange="update_config_sqlite('MHZ19', this.checked)">
|
||
<label class="form-check-label" for="check_mhz19">
|
||
Send CO2 data (MH-Z19)
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-check mb-3">
|
||
<input class="form-check-input" type="checkbox" value="" id="check_wifi_power_saving" onchange="update_config_sqlite('wifi_power_saving', this.checked)">
|
||
<label class="form-check-label" for="check_wifi_power_saving">
|
||
WiFi Power Saving
|
||
</label>
|
||
<small class="form-text text-muted d-block ms-4">
|
||
Disable WiFi 10 minutes after boot to save power (~100-200mA). WiFi will re-enable after reboot.
|
||
</small>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="cpu_power_mode" class="form-label">CPU Power Mode</label>
|
||
<select class="form-select" id="cpu_power_mode" onchange="set_cpu_power_mode(this.value)">
|
||
<option value="normal">Normal (600-1500MHz dynamic)</option>
|
||
<option value="powersave">Power Saving (600MHz fixed)</option>
|
||
</select>
|
||
<small class="form-text text-muted d-block">
|
||
<span id="cpu_mode_status" class="text-success"></span>
|
||
</small>
|
||
<small class="form-text text-muted d-block">
|
||
Power saving mode reduces CPU performance by ~30-40% but saves power.
|
||
</small>
|
||
</div>
|
||
|
||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||
<span class="fw-bold">Protected Settings</span>
|
||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="toggleProtectedSettings()" id="unlockBtn">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-lock-fill" viewBox="0 0 16 16">
|
||
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/>
|
||
</svg>
|
||
Unlock
|
||
</button>
|
||
</div>
|
||
|
||
<div class="form-check mb-3">
|
||
<input class="form-check-input protected-checkbox" type="checkbox" value="" id="check_aircarto" onchange="update_config_sqlite('send_aircarto', this.checked)" disabled>
|
||
<label class="form-check-label" for="check_aircarto">
|
||
Send to AirCarto (HTTP)
|
||
</label>
|
||
</div>
|
||
|
||
<div class="form-check mb-3">
|
||
<input class="form-check-input protected-checkbox" 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 (HTTPS)
|
||
</label>
|
||
</div>
|
||
|
||
|
||
<div class="form-check mb-3">
|
||
<input class="form-check-input protected-checkbox" type="checkbox" value="" id="check_miotiq" onchange="update_config_sqlite('send_miotiq', this.checked)" disabled>
|
||
<label class="form-check-label" for="check_miotiq">
|
||
Send to miotiq (UDP)
|
||
</label>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="device_type" class="form-label">Device Type</label>
|
||
<select class="form-select protected-checkbox" id="device_type" onchange="update_config_sqlite('device_type', this.value)" disabled>
|
||
<option value="nebuleair_pro">NebuleAir Pro</option>
|
||
<option value="moduleair_pro">ModuleAir Pro</option>
|
||
</select>
|
||
</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>
|
||
<small class="text-muted">Horloge Linux du Raspberry Pi. Se synchronise via internet (NTP). Non utilisee par le capteur.</small>
|
||
</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>
|
||
<small class="text-muted">Module DS3231 avec pile de sauvegarde. Garde l'heure meme hors tension. Horloge de reference du capteur.</small>
|
||
</div>
|
||
|
||
|
||
<div id="alert_container"></div>
|
||
|
||
<h5 class="mt-4">Synchroniser le RTC</h5>
|
||
<small class="text-muted d-block mb-2">Met a jour l'horloge RTC pour qu'elle reste precise sans internet.</small>
|
||
|
||
<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">
|
||
<div class="d-flex align-items-center mt-4 mb-2">
|
||
<h3 class="mb-0 me-2">Updates</h3>
|
||
<span id="firmwareVersionBadge" class="badge bg-secondary">Version...</span>
|
||
<button type="button" class="btn btn-sm btn-outline-info ms-2" onclick="showChangelogModal()">Changelog</button>
|
||
</div>
|
||
|
||
<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>
|
||
|
||
<hr class="my-3">
|
||
<label class="form-label fw-bold">Mise à jour hors-ligne (upload)</label>
|
||
<div class="input-group mb-2">
|
||
<input type="file" class="form-control" id="firmwareFileInput" accept=".zip">
|
||
<button class="btn btn-warning" type="button" onclick="uploadFirmware()" id="uploadBtn">
|
||
<span id="uploadBtnText">Upload & Install</span>
|
||
<span id="uploadSpinner" class="spinner-border spinner-border-sm ms-2" style="display: none;"></span>
|
||
</button>
|
||
</div>
|
||
<div class="progress mb-2" id="uploadProgressBar" style="display: none; height: 20px;">
|
||
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%" id="uploadProgress">0%</div>
|
||
</div>
|
||
<small class="text-muted">
|
||
1. Telecharger le .zip depuis <a href="http://gitea.aircarto.fr/PaulVua/nebuleair_pro_4g/releases" target="_blank">Gitea (releases)</a>
|
||
ou <a href="http://gitea.aircarto.fr/PaulVua/nebuleair_pro_4g/archive/main.zip" target="_blank">derniere version (main.zip)</a><br>
|
||
2. Deposer le fichier .zip ci-dessus puis cliquer sur Upload & Install
|
||
</small>
|
||
|
||
<!-- 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>
|
||
|
||
<!-- Envea Detection Modal -->
|
||
<div class="modal fade" id="enveaDetectionModal" tabindex="-1" aria-labelledby="enveaDetectionModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-xl">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="enveaDetectionModalLabel">Envea Sondes Detection</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body" style="max-height: 70vh; overflow-y: auto;">
|
||
<div id="detectionProgress" class="text-center" style="display: none;">
|
||
<div class="spinner-border text-primary" role="status">
|
||
<span class="visually-hidden">Loading...</span>
|
||
</div>
|
||
<p class="mt-2">Scanning ports for Envea devices...</p>
|
||
</div>
|
||
<div id="detectionResults">
|
||
<p>Click "Start Detection" to scan for connected Envea devices.</p>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||
<button type="button" class="btn btn-primary" id="startDetectionBtn" onclick="startEnveaDetection()">Start Detection</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- Changelog Modal -->
|
||
<div class="modal fade" id="changelogModal" tabindex="-1" aria-labelledby="changelogModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="changelogModalLabel">Changelog</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body" id="changelogModalBody">
|
||
<div class="text-center">
|
||
<div class="spinner-border text-primary" role="status">
|
||
<span class="visually-hidden">Loading...</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||
</div>
|
||
</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>
|
||
<!-- i18n translation system -->
|
||
<script src="assets/js/i18n.js"></script>
|
||
<script src="assets/js/topbar-logo.js"></script>
|
||
<script src="assets/js/selftest.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);
|
||
window._adminConfig = 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_aircarto = document.getElementById("check_aircarto");
|
||
const checkbox_miotiq = document.getElementById("check_miotiq");
|
||
|
||
const checkbox_bme = document.getElementById("check_bme280");
|
||
const checkbox_envea = document.getElementById("check_envea");
|
||
const checkbox_solar = document.getElementById("check_solarBattery");
|
||
const checkbox_noise = document.getElementById("check_NOISE");
|
||
const checkbox_mhz19 = document.getElementById("check_mhz19");
|
||
const checkbox_wifi_power_saving = document.getElementById("check_wifi_power_saving");
|
||
|
||
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_noise.checked = response["NOISE"];
|
||
checkbox_mhz19.checked = response["MHZ19"];
|
||
checkbox_wifi_power_saving.checked = response["wifi_power_saving"];
|
||
|
||
checkbox_uSpot.checked = response["send_uSpot"];
|
||
checkbox_aircarto.checked = response["send_aircarto"];
|
||
checkbox_miotiq.checked = response["send_miotiq"];
|
||
|
||
// Set device type
|
||
const device_type_select = document.getElementById("device_type");
|
||
if (response["device_type"]) {
|
||
device_type_select.value = response["device_type"];
|
||
}
|
||
|
||
// Set CPU power mode
|
||
const cpu_power_mode_select = document.getElementById("cpu_power_mode");
|
||
if (response["cpu_power_mode"]) {
|
||
cpu_power_mode_select.value = response["cpu_power_mode"];
|
||
// Update status display
|
||
const statusElement = document.getElementById('cpu_mode_status');
|
||
statusElement.textContent = `Current: ${response["cpu_power_mode"]}`;
|
||
statusElement.className = 'text-success';
|
||
}
|
||
|
||
// If envea is enabled, show the envea sondes container
|
||
if (response["envea"]) {
|
||
add_sondeEnveaContainer();
|
||
}
|
||
|
||
},
|
||
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();
|
||
|
||
// Load firmware version
|
||
loadFirmwareVersion();
|
||
|
||
} //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¶m='+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 set_cpu_power_mode(mode) {
|
||
console.log("Setting CPU power mode to:", mode);
|
||
|
||
const toastLiveExample = document.getElementById('liveToast');
|
||
const toastBody = toastLiveExample.querySelector('.toast-body');
|
||
const statusElement = document.getElementById('cpu_mode_status');
|
||
|
||
// Show loading status
|
||
statusElement.textContent = 'Applying mode...';
|
||
statusElement.className = 'text-warning';
|
||
|
||
$.ajax({
|
||
url: 'launcher.php?type=set_cpu_power_mode&mode=' + mode,
|
||
dataType: 'json',
|
||
method: 'GET',
|
||
cache: false,
|
||
success: function(response) {
|
||
console.log(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>
|
||
CPU mode set to: <strong>${mode}</strong><br>
|
||
${response.description || ''}
|
||
`;
|
||
|
||
// Update status
|
||
statusElement.textContent = `Current: ${mode}`;
|
||
statusElement.className = 'text-success';
|
||
|
||
} else {
|
||
// Error message
|
||
toastLiveExample.classList.remove('text-bg-success');
|
||
toastLiveExample.classList.add('text-bg-danger');
|
||
|
||
formattedMessage = `
|
||
<strong>Error!</strong><br>
|
||
${response.error || 'Failed to set CPU power mode'}
|
||
`;
|
||
|
||
// Reset status
|
||
statusElement.textContent = 'Error setting mode';
|
||
statusElement.className = 'text-danger';
|
||
}
|
||
|
||
// 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);
|
||
|
||
// Show error in toast
|
||
toastLiveExample.classList.remove('text-bg-success');
|
||
toastLiveExample.classList.add('text-bg-danger');
|
||
toastBody.innerHTML = `<strong>Error!</strong><br>Network error: ${error}`;
|
||
|
||
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
|
||
toastBootstrap.show();
|
||
|
||
// Update status
|
||
statusElement.textContent = 'Network error';
|
||
statusElement.className = 'text-danger';
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
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 updateFirmware() {
|
||
// Check if connected to internet (not in hotspot mode)
|
||
if (window._adminConfig && window._adminConfig.WIFI_status === 'hotspot') {
|
||
alert('Mise à jour impossible en mode hotspot.\nConnectez d\'abord le capteur à un réseau WiFi avec accès internet.');
|
||
return;
|
||
}
|
||
|
||
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 uploadFirmware() {
|
||
const fileInput = document.getElementById('firmwareFileInput');
|
||
const file = fileInput.files[0];
|
||
|
||
if (!file) {
|
||
showToast('Please select a .zip file first', 'warning');
|
||
return;
|
||
}
|
||
|
||
// Validate extension
|
||
if (!file.name.toLowerCase().endsWith('.zip')) {
|
||
showToast('Only .zip files are allowed', 'error');
|
||
return;
|
||
}
|
||
|
||
// Validate size (50MB)
|
||
if (file.size > 50 * 1024 * 1024) {
|
||
showToast('File too large (max 50MB)', 'error');
|
||
return;
|
||
}
|
||
|
||
if (!confirm('Install firmware from "' + file.name + '"?\nThis will update the system files and restart services.')) {
|
||
return;
|
||
}
|
||
|
||
// UI elements
|
||
const uploadBtn = document.getElementById('uploadBtn');
|
||
const uploadBtnText = document.getElementById('uploadBtnText');
|
||
const uploadSpinner = document.getElementById('uploadSpinner');
|
||
const progressBar = document.getElementById('uploadProgressBar');
|
||
const progress = document.getElementById('uploadProgress');
|
||
const updateOutput = document.getElementById('updateOutput');
|
||
const updateOutputContent = document.getElementById('updateOutputContent');
|
||
|
||
// Show loading state
|
||
uploadBtn.disabled = true;
|
||
uploadBtnText.textContent = 'Uploading...';
|
||
uploadSpinner.style.display = 'inline-block';
|
||
progressBar.style.display = 'flex';
|
||
progress.style.width = '0%';
|
||
progress.textContent = '0%';
|
||
updateOutput.style.display = 'block';
|
||
updateOutputContent.textContent = 'Uploading firmware file...\n';
|
||
|
||
// Build FormData
|
||
const formData = new FormData();
|
||
formData.append('firmware_file', file);
|
||
|
||
// Use XMLHttpRequest for upload progress
|
||
const xhr = new XMLHttpRequest();
|
||
xhr.timeout = 300000; // 5 minutes
|
||
|
||
xhr.upload.addEventListener('progress', function(e) {
|
||
if (e.lengthComputable) {
|
||
const pct = Math.round((e.loaded / e.total) * 100);
|
||
progress.style.width = pct + '%';
|
||
progress.textContent = pct + '%';
|
||
if (pct >= 100) {
|
||
uploadBtnText.textContent = 'Installing...';
|
||
updateOutputContent.textContent = 'Upload complete. Installing firmware...\n';
|
||
}
|
||
}
|
||
});
|
||
|
||
xhr.addEventListener('load', function() {
|
||
try {
|
||
const response = JSON.parse(xhr.responseText);
|
||
if (response.success && response.output) {
|
||
const formattedOutput = response.output
|
||
.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;
|
||
showToast('Firmware updated: ' + (response.old_version || '?') + ' → ' + (response.new_version || '?'), 'success');
|
||
document.getElementById('reloadBtn').style.display = 'inline-block';
|
||
} else {
|
||
updateOutputContent.textContent = 'Error: ' + (response.message || 'Unknown error');
|
||
showToast('Upload failed: ' + (response.message || 'Unknown error'), 'error');
|
||
}
|
||
} catch (e) {
|
||
updateOutputContent.textContent = 'Error parsing response: ' + xhr.responseText;
|
||
showToast('Update failed: invalid server response', 'error');
|
||
}
|
||
resetUploadUI();
|
||
});
|
||
|
||
xhr.addEventListener('error', function() {
|
||
updateOutputContent.textContent = 'Network error during upload';
|
||
showToast('Upload failed: network error', 'error');
|
||
resetUploadUI();
|
||
});
|
||
|
||
xhr.addEventListener('timeout', function() {
|
||
updateOutputContent.textContent = 'Upload timed out (5 min limit)';
|
||
showToast('Upload timed out', 'error');
|
||
resetUploadUI();
|
||
});
|
||
|
||
function resetUploadUI() {
|
||
uploadBtn.disabled = false;
|
||
uploadBtnText.textContent = 'Upload & Install';
|
||
uploadSpinner.style.display = 'none';
|
||
progressBar.style.display = 'none';
|
||
}
|
||
|
||
xhr.open('POST', 'launcher.php?type=upload_firmware');
|
||
xhr.send(formData);
|
||
}
|
||
|
||
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 <button type="button" class="btn btn-sm btn-info ms-2" onclick="detectEnveaSondes()">Detect Devices</button></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" readonly style="background-color: #f8f9fa;">
|
||
<select class="form-control" id="${sondeId}_port" onchange="updateSondePort(${sonde.id}, this.value)">
|
||
<option value="ttyAMA3" ${sonde.port === 'ttyAMA3' ? 'selected' : ''}>ttyAMA3</option>
|
||
<option value="ttyAMA4" ${sonde.port === 'ttyAMA4' ? 'selected' : ''}>ttyAMA4</option>
|
||
<option value="ttyAMA5" ${sonde.port === 'ttyAMA5' ? 'selected' : ''}>ttyAMA5</option>
|
||
</select>
|
||
<input type="number" class="form-control" placeholder="Coefficient" value="${sonde.coefficient}"
|
||
id="${sondeId}_coefficient" onchange="updateSondeCoefficientWithConfirm(${sonde.id}, this.value, this)">
|
||
</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 updateSondeCoefficientWithConfirm(id, coefficient, inputElement) {
|
||
// Store the previous value in case user cancels
|
||
const previousValue = inputElement.getAttribute('data-previous-value') || inputElement.defaultValue;
|
||
|
||
// Show confirmation dialog
|
||
const confirmed = confirm(`Are you sure you want to change the coefficient to ${coefficient}?\n\nThis will affect sensor calibration and data accuracy.`);
|
||
|
||
if (confirmed) {
|
||
// Store the new value as previous for next time
|
||
inputElement.setAttribute('data-previous-value', coefficient);
|
||
updateSondeCoefficient(id, coefficient);
|
||
} else {
|
||
// Revert to previous value
|
||
inputElement.value = previousValue;
|
||
}
|
||
}
|
||
|
||
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();
|
||
}
|
||
});
|
||
}
|
||
|
||
/*
|
||
_____ ____ _ _ _
|
||
| ____|_ ____ _____ __ _ | _ \ ___| |_ ___ ___| |_(_) ___ _ __
|
||
| _| | '_ \ \ / / _ \/ _` | | | | |/ _ \ __/ _ \/ __| __| |/ _ \| '_ \
|
||
| |___| | | \ V / __/ (_| | | |_| | __/ || __/ (__| |_| | (_) | | | |
|
||
|_____|_| |_|\_/ \___|\__,_| |____/ \___|\__\___|\___|\__|_|\___/|_| |_|
|
||
|
||
*/
|
||
|
||
function detectEnveaSondes() {
|
||
console.log("Opening Envea detection modal");
|
||
const modal = new bootstrap.Modal(document.getElementById('enveaDetectionModal'));
|
||
modal.show();
|
||
|
||
// Reset modal content
|
||
document.getElementById('detectionProgress').style.display = 'none';
|
||
document.getElementById('detectionResults').innerHTML = '<p>Click "Start Detection" to scan for connected Envea devices.</p>';
|
||
document.getElementById('startDetectionBtn').style.display = 'inline-block';
|
||
}
|
||
|
||
function startEnveaDetection() {
|
||
console.log("Starting Envea device detection");
|
||
|
||
// Show progress spinner
|
||
document.getElementById('detectionProgress').style.display = 'block';
|
||
document.getElementById('detectionResults').innerHTML = '';
|
||
document.getElementById('startDetectionBtn').style.display = 'none';
|
||
|
||
// Test the three ports: ttyAMA3, ttyAMA4, ttyAMA5
|
||
const ports = ['ttyAMA3', 'ttyAMA4', 'ttyAMA5'];
|
||
let completedTests = 0;
|
||
let results = [];
|
||
|
||
ports.forEach(function(port, index) {
|
||
$.ajax({
|
||
url: `launcher.php?type=detect_envea_device&port=${port}`,
|
||
dataType: 'json',
|
||
method: 'GET',
|
||
cache: false,
|
||
timeout: 10000, // 10 second timeout per port
|
||
success: function(response) {
|
||
console.log(`Detection result for ${port}:`, response);
|
||
|
||
results[index] = {
|
||
port: port,
|
||
success: response.success,
|
||
data: response.data || '',
|
||
error: response.error || '',
|
||
detected: response.detected || false,
|
||
device_info: response.device_info || ''
|
||
};
|
||
|
||
completedTests++;
|
||
if (completedTests === ports.length) {
|
||
displayDetectionResults(results);
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
console.error(`Detection failed for ${port}:`, error);
|
||
|
||
results[index] = {
|
||
port: port,
|
||
success: false,
|
||
data: '',
|
||
error: `Request failed: ${error}`,
|
||
detected: false,
|
||
device_info: ''
|
||
};
|
||
|
||
completedTests++;
|
||
if (completedTests === ports.length) {
|
||
displayDetectionResults(results);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function displayDetectionResults(results) {
|
||
console.log("Displaying detection results:", results);
|
||
|
||
// Hide progress spinner
|
||
document.getElementById('detectionProgress').style.display = 'none';
|
||
|
||
let htmlContent = '<h6>Detection Results:</h6>';
|
||
|
||
// Create cards for each port result
|
||
results.forEach(function(result, index) {
|
||
const statusBadge = result.detected ?
|
||
'<span class="badge bg-success">Device Detected</span>' :
|
||
result.success ?
|
||
'<span class="badge bg-warning">No Device</span>' :
|
||
'<span class="badge bg-danger">Error</span>';
|
||
|
||
const deviceInfo = result.device_info || (result.detected ? 'Envea Device' : 'None');
|
||
const rawData = result.data || 'No data';
|
||
|
||
htmlContent += `
|
||
<div class="card mb-3">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<h6 class="mb-0"><strong>Port ${result.port}</strong></h6>
|
||
${statusBadge}
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<div class="col-12 mb-3">
|
||
<strong>Device Information:</strong>
|
||
<p class="mb-0">${deviceInfo}</p>
|
||
</div>
|
||
${result.error ? `<div class="col-12 mb-3"><div class="alert alert-danger mb-0"><strong>Error:</strong> ${result.error}</div></div>` : ''}
|
||
<div class="col-12">
|
||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||
<strong>Raw Data Output:</strong>
|
||
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#rawData${index}" aria-expanded="false">
|
||
Toggle Raw Data
|
||
</button>
|
||
</div>
|
||
<div class="collapse" id="rawData${index}">
|
||
<pre class="bg-light p-3 rounded" style="white-space: pre-wrap; word-wrap: break-word; max-height: 300px; overflow-y: auto; font-size: 0.85rem;">${rawData}</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
// Add summary
|
||
const detectedCount = results.filter(r => r.detected).length;
|
||
htmlContent += `<div class="alert alert-info mt-3">
|
||
<i class="bi bi-info-circle"></i> <strong>Summary:</strong> ${detectedCount} device(s) detected out of ${results.length} ports tested.
|
||
</div>`;
|
||
|
||
document.getElementById('detectionResults').innerHTML = htmlContent;
|
||
document.getElementById('startDetectionBtn').style.display = 'inline-block';
|
||
document.getElementById('startDetectionBtn').textContent = 'Scan Again';
|
||
}
|
||
|
||
/*
|
||
____ _ _ _ ____ _ _ _
|
||
| _ \ _ __ ___ | |_ ___ ___| |_ ___ __| | / ___| ___| |_| |_(_)_ __ __ _ ___
|
||
| |_) | '__/ _ \| __/ _ \/ __| __/ _ \/ _` | \___ \ / _ \ __| __| | '_ \ / _` / __|
|
||
| __/| | | (_) | || __/ (__| || __/ (_| | ___) | __/ |_| |_| | | | | (_| \__ \
|
||
|_| |_| \___/ \__\___|\___|\__\___|\__,_| |____/ \___|\__|\__|_|_| |_|\__, |___/
|
||
|___/
|
||
*/
|
||
|
||
// Track if protected settings are unlocked
|
||
let protectedSettingsUnlocked = false;
|
||
|
||
function toggleProtectedSettings() {
|
||
const unlockBtn = document.getElementById('unlockBtn');
|
||
const protectedCheckboxes = document.querySelectorAll('.protected-checkbox');
|
||
|
||
if (protectedSettingsUnlocked) {
|
||
// Lock the settings
|
||
protectedSettingsUnlocked = false;
|
||
protectedCheckboxes.forEach(checkbox => {
|
||
checkbox.disabled = true;
|
||
});
|
||
|
||
// Update button appearance
|
||
unlockBtn.classList.remove('btn-success');
|
||
unlockBtn.classList.add('btn-outline-primary');
|
||
unlockBtn.innerHTML = `
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-lock-fill" viewBox="0 0 16 16">
|
||
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/>
|
||
</svg>
|
||
Unlock
|
||
`;
|
||
|
||
// Show toast notification
|
||
showToast('Protected settings locked', 'info');
|
||
} else {
|
||
// Prompt for password
|
||
const password = prompt('Enter admin password to unlock protected settings:');
|
||
|
||
if (password === '123plouf') {
|
||
// Correct password - unlock the settings
|
||
protectedSettingsUnlocked = true;
|
||
protectedCheckboxes.forEach(checkbox => {
|
||
checkbox.disabled = false;
|
||
});
|
||
|
||
// Update button appearance
|
||
unlockBtn.classList.remove('btn-outline-primary');
|
||
unlockBtn.classList.add('btn-success');
|
||
unlockBtn.innerHTML = `
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-unlock-fill" viewBox="0 0 16 16">
|
||
<path d="M11 1a2 2 0 0 0-2 2v4a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h5V3a3 3 0 0 1 6 0v4a.5.5 0 0 1-1 0V3a2 2 0 0 0-2-2z"/>
|
||
</svg>
|
||
Lock
|
||
`;
|
||
|
||
// Show success toast
|
||
showToast('Protected settings unlocked! You can now edit the checkboxes.', 'success');
|
||
} else if (password !== null) {
|
||
// Wrong password (null means user cancelled)
|
||
showToast('Incorrect password!', 'error');
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
__ __ _ _
|
||
\ \ / /__ _ __ ___(_) ___ _ __ (_)_ __ __ _
|
||
\ \ / / _ \ '__/ __| |/ _ \| '_ \| | '_ \ / _` |
|
||
\ V / __/ | \__ \ | (_) | | | | | | | | (_| |
|
||
\_/ \___|_| |___/_|\___/|_| |_|_|_| |_|\__, |
|
||
|___/
|
||
*/
|
||
|
||
function loadFirmwareVersion() {
|
||
$.ajax({
|
||
url: 'launcher.php?type=get_firmware_version',
|
||
dataType: 'json',
|
||
method: 'GET',
|
||
cache: false,
|
||
success: function(response) {
|
||
const badge = document.getElementById('firmwareVersionBadge');
|
||
if (response.success) {
|
||
badge.textContent = 'v' + response.version;
|
||
badge.className = 'badge bg-primary';
|
||
} else {
|
||
badge.textContent = 'Version unknown';
|
||
badge.className = 'badge bg-secondary';
|
||
}
|
||
},
|
||
error: function() {
|
||
const badge = document.getElementById('firmwareVersionBadge');
|
||
badge.textContent = 'Version unknown';
|
||
badge.className = 'badge bg-secondary';
|
||
}
|
||
});
|
||
}
|
||
|
||
function showChangelogModal() {
|
||
const modal = new bootstrap.Modal(document.getElementById('changelogModal'));
|
||
modal.show();
|
||
|
||
// Load changelog data
|
||
$.ajax({
|
||
url: 'launcher.php?type=get_changelog',
|
||
dataType: 'json',
|
||
method: 'GET',
|
||
cache: false,
|
||
success: function(response) {
|
||
if (response.success && response.changelog) {
|
||
displayChangelog(response.changelog);
|
||
} else {
|
||
document.getElementById('changelogModalBody').innerHTML =
|
||
'<div class="alert alert-warning">Could not load changelog.</div>';
|
||
}
|
||
},
|
||
error: function() {
|
||
document.getElementById('changelogModalBody').innerHTML =
|
||
'<div class="alert alert-danger">Failed to load changelog.</div>';
|
||
}
|
||
});
|
||
}
|
||
|
||
function displayChangelog(data) {
|
||
const container = document.getElementById('changelogModalBody');
|
||
let html = '';
|
||
|
||
data.versions.forEach(function(version) {
|
||
html += `<div class="card mb-3">`;
|
||
html += `<div class="card-header d-flex justify-content-between align-items-center">`;
|
||
html += `<h5 class="mb-0">v${version.version}</h5>`;
|
||
html += `<span class="text-muted">${version.date}</span>`;
|
||
html += `</div>`;
|
||
html += `<div class="card-body">`;
|
||
|
||
// Features
|
||
if (version.changes.features && version.changes.features.length > 0) {
|
||
html += `<h6 class="text-success">Features</h6><ul>`;
|
||
version.changes.features.forEach(function(f) {
|
||
html += `<li>${f}</li>`;
|
||
});
|
||
html += `</ul>`;
|
||
}
|
||
|
||
// Improvements
|
||
if (version.changes.improvements && version.changes.improvements.length > 0) {
|
||
html += `<h6 class="text-info">Improvements</h6><ul>`;
|
||
version.changes.improvements.forEach(function(i) {
|
||
html += `<li>${i}</li>`;
|
||
});
|
||
html += `</ul>`;
|
||
}
|
||
|
||
// Fixes
|
||
if (version.changes.fixes && version.changes.fixes.length > 0) {
|
||
html += `<h6 class="text-danger">Fixes</h6><ul>`;
|
||
version.changes.fixes.forEach(function(f) {
|
||
html += `<li>${f}</li>`;
|
||
});
|
||
html += `</ul>`;
|
||
}
|
||
|
||
// Compatibility
|
||
if (version.changes.compatibility && version.changes.compatibility.length > 0) {
|
||
html += `<div class="alert alert-warning mt-2 mb-0"><strong>Compatibility:</strong><ul class="mb-0">`;
|
||
version.changes.compatibility.forEach(function(c) {
|
||
html += `<li>${c}</li>`;
|
||
});
|
||
html += `</ul></div>`;
|
||
}
|
||
|
||
// Notes
|
||
if (version.notes) {
|
||
html += `<p class="text-muted mt-2 mb-0"><em>${version.notes}</em></p>`;
|
||
}
|
||
|
||
html += `</div></div>`;
|
||
});
|
||
|
||
container.innerHTML = html;
|
||
}
|
||
|
||
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|