v1.9.17: database.html - modal + pagination, boutons S88
Refonte des boutons 'Consulter la base de donnée': ils ouvrent désormais un grand modal Bootstrap (modal-xl scrollable) avec pagination 20 lignes/page (Précédent/Suivant + indicateur de plage). Le dropdown 'Nombre de mesures' est supprimé. Ajout des boutons Senseair S88 dans les 3 cartes pointant sur data_S88, et renommage du bouton MH-Z19 pour le distinguer. Backend: sqlite/read.py accepte un OFFSET optionnel (3e argument, défaut 0) et launcher.php endpoint table_mesure transmet ?offset=N. Rétrocompatible. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,21 @@
|
|||||||
{
|
{
|
||||||
"versions": [
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.9.17",
|
||||||
|
"date": "2026-06-01",
|
||||||
|
"changes": {
|
||||||
|
"features": [
|
||||||
|
"database.html: refonte de la consultation des mesures. Les boutons 'Consulter la base' ouvrent désormais un grand modal (modal-xl scrollable) avec pagination 20 lignes par page (boutons Précédent/Suivant + indicateur de plage). Le dropdown 'Nombre de mesures' est supprimé — par défaut 20 dernières, on navigue ensuite page par page.",
|
||||||
|
"database.html: ajout des boutons Senseair S88 dans les 3 cartes (Consulter / Télécharger par date / Télécharger toute la table), pointant sur data_S88. Le bouton MH-Z19 est renommé 'Mesures CO2 (MH-Z19)'."
|
||||||
|
],
|
||||||
|
"improvements": [
|
||||||
|
"sqlite/read.py + launcher.php endpoint table_mesure: support du paramètre OFFSET (utilisé par la pagination du modal)."
|
||||||
|
],
|
||||||
|
"fixes": [],
|
||||||
|
"compatibility": []
|
||||||
|
},
|
||||||
|
"notes": "Rétrocompatible: read.py accepte toujours les anciens appels à 2 arguments (offset par défaut = 0). L'endpoint table_mesure accepte un offset optionnel."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "1.9.16",
|
"version": "1.9.16",
|
||||||
"date": "2026-06-01",
|
"date": "2026-06-01",
|
||||||
|
|||||||
@@ -65,24 +65,17 @@
|
|||||||
<div class="card text-dark bg-light h-100">
|
<div class="card text-dark bg-light h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title" data-i18n="database.viewDatabase">Consulter la base de donnée</h5>
|
<h5 class="card-title" data-i18n="database.viewDatabase">Consulter la base de donnée</h5>
|
||||||
<!-- Dropdown to select number of records -->
|
<p class="text-muted small">Ouvre les 20 dernières mesures, navigation page par page.</p>
|
||||||
<div class="d-flex align-items-center mb-3">
|
<button class="btn btn-primary mb-2" onclick="openTableModal('data_NPM','Mesures PM')" data-i18n="database.pmMeasures">Mesures PM</button>
|
||||||
<label for="records_limit" class="form-label me-2" data-i18n="database.numberOfMeasures">Nombre de mesures:</label>
|
<button class="btn btn-primary mb-2" onclick="openTableModal('data_BME280','Mesures Temp/Hum')" data-i18n="database.tempHumMeasures">Mesures Temp/Hum</button>
|
||||||
<select id="records_limit" class="form-select w-auto">
|
<button class="btn btn-primary mb-2" onclick="openTableModal('data_NPM_5channels','Mesures PM (5 canaux)')" data-i18n="database.pm5Channels">Mesures PM (5 canaux)</button>
|
||||||
<option value="10" selected data-i18n="database.last10">10 dernières</option>
|
<button class="btn btn-primary mb-2" onclick="openTableModal('data_envea','Sonde Cairsens')" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
|
||||||
<option value="20" data-i18n="database.last20">20 dernières</option>
|
<button class="btn btn-primary mb-2" onclick="openTableModal('data_NOISE','Sonde bruit')" data-i18n="database.noiseProbe">Sonde bruit</button>
|
||||||
<option value="30" data-i18n="database.last30">30 dernières</option>
|
<button class="btn btn-primary mb-2" onclick="openTableModal('data_WIND','Sonde Vent')" data-i18n="database.windProbe">Sonde Vent</button>
|
||||||
</select>
|
<button class="btn btn-primary mb-2" onclick="openTableModal('data_MPPT','Batterie')" data-i18n="database.battery">Batterie</button>
|
||||||
</div>
|
<button class="btn btn-primary mb-2" onclick="openTableModal('data_MHZ19','Mesures CO2 (MH-Z19)')">Mesures CO2 (MH-Z19)</button>
|
||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NPM',getSelectedLimit(),false)" data-i18n="database.pmMeasures">Mesures PM</button>
|
<button class="btn btn-primary mb-2" onclick="openTableModal('data_S88','Mesures CO2 (Senseair S88)')">Mesures CO2 (Senseair S88)</button>
|
||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_BME280',getSelectedLimit(),false)" data-i18n="database.tempHumMeasures">Mesures Temp/Hum</button>
|
<button class="btn btn-warning mb-2" onclick="openTableModal('timestamp_table','Timestamp Table')" data-i18n="database.timestampTable">Timestamp Table</button>
|
||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NPM_5channels',getSelectedLimit(),false)" data-i18n="database.pm5Channels">Mesures PM (5 canaux)</button>
|
|
||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_envea',getSelectedLimit(),false)" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
|
|
||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NOISE',getSelectedLimit(),false)" data-i18n="database.noiseProbe">Sonde bruit</button>
|
|
||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_WIND',getSelectedLimit(),false)" data-i18n="database.windProbe">Sonde Vent</button>
|
|
||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_MPPT',getSelectedLimit(),false)" data-i18n="database.battery">Batterie</button>
|
|
||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_MHZ19',getSelectedLimit(),false)">Mesures CO2</button>
|
|
||||||
<button class="btn btn-warning mb-2" onclick="get_data_sqlite('timestamp_table',getSelectedLimit(),false)" data-i18n="database.timestampTable">Timestamp Table</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,7 +98,8 @@
|
|||||||
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_envea')" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
|
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_envea')" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
|
||||||
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_NOISE')" data-i18n="database.noiseProbe">Sonde Bruit</button>
|
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_NOISE')" data-i18n="database.noiseProbe">Sonde Bruit</button>
|
||||||
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_mppt')" data-i18n="database.battery">Batterie</button>
|
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_mppt')" data-i18n="database.battery">Batterie</button>
|
||||||
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_MHZ19')">Mesures CO2</button>
|
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_MHZ19')">Mesures CO2 (MH-Z19)</button>
|
||||||
|
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_S88')">Mesures CO2 (Senseair S88)</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,7 +115,8 @@
|
|||||||
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_envea')" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
|
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_envea')" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
|
||||||
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_NOISE')" data-i18n="database.noiseProbe">Sonde Bruit</button>
|
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_NOISE')" data-i18n="database.noiseProbe">Sonde Bruit</button>
|
||||||
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_MPPT')" data-i18n="database.battery">Batterie</button>
|
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_MPPT')" data-i18n="database.battery">Batterie</button>
|
||||||
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_MHZ19')">Mesures CO2</button>
|
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_MHZ19')">Mesures CO2 (MH-Z19)</button>
|
||||||
|
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_S88')">Mesures CO2 (Senseair S88)</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -167,6 +162,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal pour consultation des mesures avec pagination -->
|
||||||
|
<div class="modal fade" id="tableModal" tabindex="-1" aria-labelledby="tableModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="tableModalLabel">Mesures</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id="tableModalContent">
|
||||||
|
<div class="text-center py-3">
|
||||||
|
<div class="spinner-border" role="status"></div>
|
||||||
|
<span class="ms-2">Chargement...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer justify-content-between">
|
||||||
|
<div class="text-muted small" id="tableModalRange">—</div>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-outline-primary" id="tableModalPrev" onclick="tableModalChangePage(-1)" disabled>← Précédent</button>
|
||||||
|
<button type="button" class="btn btn-outline-primary" id="tableModalNext" onclick="tableModalChangePage(1)">Suivant →</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- JAVASCRIPT -->
|
<!-- JAVASCRIPT -->
|
||||||
|
|
||||||
<!-- Link Ajax locally -->
|
<!-- Link Ajax locally -->
|
||||||
@@ -286,218 +308,136 @@ window.onload = function() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// TABLE PM
|
// Build the <th> header cells for a given table
|
||||||
function get_data_sqlite(table, limit, download , startDate = "", endDate = "") {
|
function buildTableHeader(table) {
|
||||||
console.log(`Getting data for table: ${table}, limit: ${limit}, download: ${download}, start: ${startDate}, end: ${endDate}`);
|
const headers = {
|
||||||
// Construct URL parameters dynamically
|
data_NPM: ['Timestamp','PM1','PM2.5','PM10','Temperature (°C)','Humidity (%)','Status'],
|
||||||
let url = `launcher.php?type=table_mesure&table=${table}&limit=${limit}&download=${download}`;
|
data_BME280: ['Timestamp','Temperature (°C)','Humidity (%)','Pressure (hPa)'],
|
||||||
|
data_NPM_5channels: ['Timestamp','PM_ch1 (nb/L)','PM_ch2 (nb/L)','PM_ch3 (nb/L)','PM_ch4 (nb/L)','PM_ch5 (nb/L)'],
|
||||||
|
data_envea: ['Timestamp','NO2','H2S','NH3','CO','O3'],
|
||||||
|
timestamp_table: ['Timestamp'],
|
||||||
|
data_WIND: ['Timestamp','speed (km/h)','Direction (V)'],
|
||||||
|
data_MPPT: ['Timestamp','Battery Voltage','Battery Current','solar_voltage','solar_power','charger_status'],
|
||||||
|
data_NOISE: ['Timestamp','Curent LEQ','DB_A_value','Status'],
|
||||||
|
data_MHZ19: ['Timestamp','CO2 (ppm)'],
|
||||||
|
data_S88: ['Timestamp','CO2 (ppm)']
|
||||||
|
};
|
||||||
|
return (headers[table] || ['Data']).map(h => `<th>${h}</th>`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
// Add date parameters if downloading
|
// Build the <td> cells for one row of a given table
|
||||||
if (download) {
|
function buildTableRow(table, columns) {
|
||||||
url += `&start_date=${startDate}&end_date=${endDate}`;
|
if (table === "data_NPM") {
|
||||||
}
|
const statusVal = parseInt(columns[6]) || 0;
|
||||||
|
const statusBadge = statusVal === 0
|
||||||
console.log(url);
|
? '<span class="badge text-bg-success">OK</span>'
|
||||||
|
: `<span class="badge text-bg-warning">0x${statusVal.toString(16).toUpperCase().padStart(2,'0')}</span>`;
|
||||||
$.ajax({
|
return `<td>${columns[0]}</td><td>${columns[1]}</td><td>${columns[2]}</td><td>${columns[3]}</td><td>${columns[4]}</td><td>${columns[5]}</td><td>${statusBadge}</td>`;
|
||||||
url: url,
|
|
||||||
dataType: 'text', // Specify that you expect a JSON response
|
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
|
||||||
success: function(response) {
|
|
||||||
console.log(response);
|
|
||||||
|
|
||||||
// If download is true, generate and trigger CSV download
|
|
||||||
if (download) {
|
|
||||||
downloadCSV(response, table);
|
|
||||||
return; // Exit function after triggering download
|
|
||||||
}
|
|
||||||
|
|
||||||
let rows = response.trim().split("\n");
|
|
||||||
// Generate Bootstrap table
|
|
||||||
|
|
||||||
let tableHTML = `<table class="table table-striped table-bordered">
|
|
||||||
<thead class="table-dark"><tr>`;
|
|
||||||
|
|
||||||
// Define column headers dynamically based on the table type
|
|
||||||
if (table === "data_NPM") {
|
|
||||||
tableHTML += `
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>PM1</th>
|
|
||||||
<th>PM2.5</th>
|
|
||||||
<th>PM10</th>
|
|
||||||
<th>Temperature (°C)</th>
|
|
||||||
<th>Humidity (%)</th>
|
|
||||||
<th>Status</th>
|
|
||||||
`;
|
|
||||||
} else if (table === "data_BME280") {
|
|
||||||
tableHTML += `
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>Temperature (°C)</th>
|
|
||||||
<th>Humidity (%)</th>
|
|
||||||
<th>Pressure (hPa)</th>
|
|
||||||
`;
|
|
||||||
} else if (table === "data_NPM_5channels") {
|
|
||||||
tableHTML += `
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>PM_ch1 (nb/L)</th>
|
|
||||||
<th>PM_ch2 (nb/L)</th>
|
|
||||||
<th>PM_ch3 (nb/L)</th>
|
|
||||||
<th>PM_ch4 (nb/L)</th>
|
|
||||||
<th>PM_ch5 (nb/L)</th>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}else if (table === "data_envea") {
|
|
||||||
tableHTML += `
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>NO2</th>
|
|
||||||
<th>H2S</th>
|
|
||||||
<th>NH3</th>
|
|
||||||
<th>CO</th>
|
|
||||||
<th>O3</th>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}else if (table === "timestamp_table") {
|
|
||||||
tableHTML += `
|
|
||||||
<th>Timestamp</th>
|
|
||||||
`;
|
|
||||||
}else if (table === "data_WIND") {
|
|
||||||
tableHTML += `
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>speed (km/h)</th>
|
|
||||||
<th>Direction (V)</th>
|
|
||||||
`;
|
|
||||||
}else if (table === "data_MPPT") {
|
|
||||||
tableHTML += `
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>Battery Voltage</th>
|
|
||||||
<th>Battery Current</th>
|
|
||||||
<th> solar_voltage</th>
|
|
||||||
<th> solar_power</th>
|
|
||||||
<th> charger_status</th>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}else if (table === "data_NOISE") {
|
|
||||||
tableHTML += `
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>Curent LEQ</th>
|
|
||||||
<th>DB_A_value</th>
|
|
||||||
<th>Status</th>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}else if (table === "data_MHZ19") {
|
|
||||||
tableHTML += `
|
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>CO2 (ppm)</th>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
tableHTML += `</tr></thead><tbody>`;
|
|
||||||
|
|
||||||
// Loop through rows and create table rows
|
|
||||||
rows.forEach((row, index) => {
|
|
||||||
let columns = row.replace(/[()]/g, "").split(", "); // Remove parentheses and split
|
|
||||||
// Add special class to first row (most recent data)
|
|
||||||
const rowClass = index === 0 ? ' class="most-recent-row"' : '';
|
|
||||||
tableHTML += `<tr${rowClass}>`;
|
|
||||||
|
|
||||||
if (table === "data_NPM") {
|
|
||||||
const statusVal = parseInt(columns[6]) || 0;
|
|
||||||
const statusBadge = statusVal === 0
|
|
||||||
? '<span class="badge text-bg-success">OK</span>'
|
|
||||||
: `<span class="badge text-bg-warning">0x${statusVal.toString(16).toUpperCase().padStart(2,'0')}</span>`;
|
|
||||||
tableHTML += `
|
|
||||||
<td>${columns[0]}</td>
|
|
||||||
<td>${columns[1]}</td>
|
|
||||||
<td>${columns[2]}</td>
|
|
||||||
<td>${columns[3]}</td>
|
|
||||||
<td>${columns[4]}</td>
|
|
||||||
<td>${columns[5]}</td>
|
|
||||||
<td>${statusBadge}</td>
|
|
||||||
`;
|
|
||||||
} else if (table === "data_BME280") {
|
|
||||||
tableHTML += `
|
|
||||||
<td>${columns[0]}</td>
|
|
||||||
<td>${columns[1]}</td>
|
|
||||||
<td>${columns[2]}</td>
|
|
||||||
<td>${columns[3]}</td>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
else if (table === "data_NPM_5channels") {
|
|
||||||
tableHTML += `
|
|
||||||
<td>${columns[0]}</td>
|
|
||||||
<td>${columns[1]}</td>
|
|
||||||
<td>${columns[2]}</td>
|
|
||||||
<td>${columns[3]}</td>
|
|
||||||
<td>${columns[4]}</td>
|
|
||||||
<td>${columns[5]}</td>
|
|
||||||
|
|
||||||
`;
|
|
||||||
} else if (table === "data_envea") {
|
|
||||||
tableHTML += `
|
|
||||||
<td>${columns[0]}</td>
|
|
||||||
<td>${columns[1]}</td>
|
|
||||||
<td>${columns[2]}</td>
|
|
||||||
<td>${columns[3]}</td>
|
|
||||||
<td>${columns[4]}</td>
|
|
||||||
<td>${columns[5]}</td>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}else if (table === "timestamp_table") {
|
|
||||||
tableHTML += `
|
|
||||||
<td>${columns[1]}</td>
|
|
||||||
`;
|
|
||||||
}else if (table === "data_WIND") {
|
|
||||||
tableHTML += `
|
|
||||||
<td>${columns[0]}</td>
|
|
||||||
<td>${columns[1]}</td>
|
|
||||||
<td>${columns[2]}</td>
|
|
||||||
`;
|
|
||||||
}else if (table === "data_MPPT") {
|
|
||||||
tableHTML += `
|
|
||||||
<td>${columns[0]}</td>
|
|
||||||
<td>${columns[1]}</td>
|
|
||||||
<td>${columns[2]}</td>
|
|
||||||
<td>${columns[3]}</td>
|
|
||||||
<td>${columns[4]}</td>
|
|
||||||
<td>${columns[5]}</td>
|
|
||||||
`;
|
|
||||||
}else if (table === "data_NOISE") {
|
|
||||||
const nStatus = parseInt(columns[3]) || 0;
|
|
||||||
const nStatusLabel = nStatus === 255 ? '❌ Déconnecté' : '✅ OK';
|
|
||||||
tableHTML += `
|
|
||||||
<td>${columns[0]}</td>
|
|
||||||
<td>${columns[1]}</td>
|
|
||||||
<td>${columns[2]}</td>
|
|
||||||
<td>${nStatusLabel}</td>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}else if (table === "data_MHZ19") {
|
|
||||||
tableHTML += `
|
|
||||||
<td>${columns[0]}</td>
|
|
||||||
<td>${columns[1]}</td>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
tableHTML += "</tr>";
|
|
||||||
});
|
|
||||||
|
|
||||||
tableHTML += `</tbody></table>`;
|
|
||||||
|
|
||||||
// Update the #table_data div with the generated table
|
|
||||||
document.getElementById("table_data").innerHTML = tableHTML;
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
}
|
||||||
});
|
if (table === "data_NOISE") {
|
||||||
|
const nStatus = parseInt(columns[3]) || 0;
|
||||||
|
const nStatusLabel = nStatus === 255 ? '❌ Déconnecté' : '✅ OK';
|
||||||
|
return `<td>${columns[0]}</td><td>${columns[1]}</td><td>${columns[2]}</td><td>${nStatusLabel}</td>`;
|
||||||
|
}
|
||||||
|
if (table === "timestamp_table") {
|
||||||
|
return `<td>${columns[1]}</td>`;
|
||||||
|
}
|
||||||
|
// Default: render all available columns
|
||||||
|
const colCount = { data_BME280: 4, data_NPM_5channels: 6, data_envea: 6, data_WIND: 3, data_MPPT: 6, data_MHZ19: 2, data_S88: 2 };
|
||||||
|
const n = colCount[table] || columns.length;
|
||||||
|
return columns.slice(0, n).map(c => `<td>${c}</td>`).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getSelectedLimit() {
|
// Modal pagination state
|
||||||
return document.getElementById("records_limit").value;
|
const TABLE_MODAL_PAGE_SIZE = 20;
|
||||||
|
let tableModalState = { table: null, title: null, page: 0 };
|
||||||
|
|
||||||
|
function openTableModal(table, title) {
|
||||||
|
tableModalState = { table, title, page: 0 };
|
||||||
|
document.getElementById('tableModalLabel').textContent = title;
|
||||||
|
const modalEl = document.getElementById('tableModal');
|
||||||
|
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||||
|
modal.show();
|
||||||
|
loadTableModalPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
function tableModalChangePage(delta) {
|
||||||
|
tableModalState.page = Math.max(0, tableModalState.page + delta);
|
||||||
|
loadTableModalPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadTableModalPage() {
|
||||||
|
const { table, page } = tableModalState;
|
||||||
|
const offset = page * TABLE_MODAL_PAGE_SIZE;
|
||||||
|
const url = `launcher.php?type=table_mesure&table=${table}&limit=${TABLE_MODAL_PAGE_SIZE}&offset=${offset}&download=false`;
|
||||||
|
|
||||||
|
document.getElementById('tableModalContent').innerHTML =
|
||||||
|
'<div class="text-center py-3"><div class="spinner-border" role="status"></div><span class="ms-2">Chargement…</span></div>';
|
||||||
|
document.getElementById('tableModalPrev').disabled = true;
|
||||||
|
document.getElementById('tableModalNext').disabled = true;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
dataType: 'text',
|
||||||
|
method: 'GET',
|
||||||
|
success: function(response) {
|
||||||
|
const lines = response.trim().split('\n').filter(l => l.length > 0);
|
||||||
|
|
||||||
|
if (lines.length === 0) {
|
||||||
|
if (page === 0) {
|
||||||
|
document.getElementById('tableModalContent').innerHTML =
|
||||||
|
'<div class="text-center py-4 text-muted">Aucune donnée disponible dans cette table.</div>';
|
||||||
|
document.getElementById('tableModalRange').textContent = '—';
|
||||||
|
} else {
|
||||||
|
// Past the end — step back
|
||||||
|
tableModalState.page--;
|
||||||
|
loadTableModalPage();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = `<table class="table table-striped table-bordered mb-0"><thead class="table-dark sticky-top"><tr>${buildTableHeader(table)}</tr></thead><tbody>`;
|
||||||
|
lines.forEach((line, idx) => {
|
||||||
|
const columns = line.replace(/[()]/g, '').split(', ');
|
||||||
|
const rowClass = (idx === 0 && page === 0) ? ' class="most-recent-row"' : '';
|
||||||
|
html += `<tr${rowClass}>${buildTableRow(table, columns)}</tr>`;
|
||||||
|
});
|
||||||
|
html += '</tbody></table>';
|
||||||
|
|
||||||
|
document.getElementById('tableModalContent').innerHTML = html;
|
||||||
|
document.getElementById('tableModalRange').textContent = `Lignes ${offset + 1} – ${offset + lines.length}`;
|
||||||
|
document.getElementById('tableModalPrev').disabled = page === 0;
|
||||||
|
// If we got fewer rows than the page size, we hit the end of the table
|
||||||
|
document.getElementById('tableModalNext').disabled = lines.length < TABLE_MODAL_PAGE_SIZE;
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
document.getElementById('tableModalContent').innerHTML =
|
||||||
|
`<div class="text-danger">Erreur de chargement: ${error}</div>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Legacy: still used by downloadByDate() to fetch CSV via the same endpoint
|
||||||
|
function get_data_sqlite(table, limit, download, startDate = "", endDate = "") {
|
||||||
|
let url = `launcher.php?type=table_mesure&table=${table}&limit=${limit}&download=${download}`;
|
||||||
|
if (download) {
|
||||||
|
url += `&start_date=${startDate}&end_date=${endDate}`;
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
dataType: 'text',
|
||||||
|
method: 'GET',
|
||||||
|
success: function(response) {
|
||||||
|
if (download) {
|
||||||
|
downloadCSV(response, table);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('AJAX request failed:', status, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStartDate() {
|
function getStartDate() {
|
||||||
@@ -542,6 +482,9 @@ function downloadCSV(response, table) {
|
|||||||
else if (table === "data_MHZ19") {
|
else if (table === "data_MHZ19") {
|
||||||
csvContent += "TimestampUTC,CO2_ppm\n";
|
csvContent += "TimestampUTC,CO2_ppm\n";
|
||||||
}
|
}
|
||||||
|
else if (table === "data_S88") {
|
||||||
|
csvContent += "TimestampUTC,CO2_ppm\n";
|
||||||
|
}
|
||||||
|
|
||||||
// Format rows as CSV
|
// Format rows as CSV
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
@@ -568,7 +511,9 @@ const tableDisplayNames = {
|
|||||||
'data_envea': 'Gaz (Cairsens)',
|
'data_envea': 'Gaz (Cairsens)',
|
||||||
'data_WIND': 'Vent',
|
'data_WIND': 'Vent',
|
||||||
'data_MPPT': 'Batterie (MPPT)',
|
'data_MPPT': 'Batterie (MPPT)',
|
||||||
'data_NOISE': 'Bruit'
|
'data_NOISE': 'Bruit',
|
||||||
|
'data_MHZ19': 'CO2 (MH-Z19)',
|
||||||
|
'data_S88': 'CO2 (Senseair S88)'
|
||||||
};
|
};
|
||||||
|
|
||||||
function loadDbStats() {
|
function loadDbStats() {
|
||||||
|
|||||||
@@ -1020,10 +1020,11 @@ if ($type == "s88") {
|
|||||||
if ($type == "table_mesure") {
|
if ($type == "table_mesure") {
|
||||||
$table=$_GET['table'];
|
$table=$_GET['table'];
|
||||||
$limit=$_GET['limit'];
|
$limit=$_GET['limit'];
|
||||||
|
$offset=(int)($_GET['offset'] ?? 0);
|
||||||
$download=$_GET['download'];
|
$download=$_GET['download'];
|
||||||
|
|
||||||
if ($download==="false") {
|
if ($download==="false") {
|
||||||
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/read.py '.$table.' '.$limit;
|
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/read.py '.$table.' '.$limit.' '.$offset;
|
||||||
$output = shell_exec($command);
|
$output = shell_exec($command);
|
||||||
echo $output;
|
echo $output;
|
||||||
} else{
|
} else{
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ parameter = sys.argv[1:] # Exclude the script name
|
|||||||
#print("Parameters received:")
|
#print("Parameters received:")
|
||||||
table_name=parameter[0]
|
table_name=parameter[0]
|
||||||
limit_num=parameter[1]
|
limit_num=parameter[1]
|
||||||
|
offset_num=parameter[2] if len(parameter) > 2 else "0"
|
||||||
|
|
||||||
# Connect to the SQLite database
|
# Connect to the SQLite database
|
||||||
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
||||||
@@ -36,8 +37,8 @@ if table_name == "timestamp_table":
|
|||||||
cursor.execute("SELECT * FROM timestamp_table")
|
cursor.execute("SELECT * FROM timestamp_table")
|
||||||
else:
|
else:
|
||||||
# Order by ROWID DESC to get most recently inserted rows first
|
# Order by ROWID DESC to get most recently inserted rows first
|
||||||
query = f"SELECT * FROM {table_name} ORDER BY ROWID DESC LIMIT ?"
|
query = f"SELECT * FROM {table_name} ORDER BY ROWID DESC LIMIT ? OFFSET ?"
|
||||||
cursor.execute(query, (limit_num,))
|
cursor.execute(query, (limit_num, offset_num))
|
||||||
|
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
# Keep DESC order - most recently inserted data first
|
# Keep DESC order - most recently inserted data first
|
||||||
|
|||||||
Reference in New Issue
Block a user