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:
@@ -65,24 +65,17 @@
|
||||
<div class="card text-dark bg-light h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title" data-i18n="database.viewDatabase">Consulter la base de donnée</h5>
|
||||
<!-- Dropdown to select number of records -->
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<label for="records_limit" class="form-label me-2" data-i18n="database.numberOfMeasures">Nombre de mesures:</label>
|
||||
<select id="records_limit" class="form-select w-auto">
|
||||
<option value="10" selected data-i18n="database.last10">10 dernières</option>
|
||||
<option value="20" data-i18n="database.last20">20 dernières</option>
|
||||
<option value="30" data-i18n="database.last30">30 dernières</option>
|
||||
</select>
|
||||
</div>
|
||||
<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="get_data_sqlite('data_BME280',getSelectedLimit(),false)" data-i18n="database.tempHumMeasures">Mesures Temp/Hum</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>
|
||||
<p class="text-muted small">Ouvre les 20 dernières mesures, navigation page par page.</p>
|
||||
<button class="btn btn-primary mb-2" onclick="openTableModal('data_NPM','Mesures PM')" data-i18n="database.pmMeasures">Mesures PM</button>
|
||||
<button class="btn btn-primary mb-2" onclick="openTableModal('data_BME280','Mesures Temp/Hum')" data-i18n="database.tempHumMeasures">Mesures Temp/Hum</button>
|
||||
<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>
|
||||
<button class="btn btn-primary mb-2" onclick="openTableModal('data_envea','Sonde Cairsens')" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
|
||||
<button class="btn btn-primary mb-2" onclick="openTableModal('data_NOISE','Sonde bruit')" data-i18n="database.noiseProbe">Sonde bruit</button>
|
||||
<button class="btn btn-primary mb-2" onclick="openTableModal('data_WIND','Sonde Vent')" data-i18n="database.windProbe">Sonde Vent</button>
|
||||
<button class="btn btn-primary mb-2" onclick="openTableModal('data_MPPT','Batterie')" data-i18n="database.battery">Batterie</button>
|
||||
<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="openTableModal('data_S88','Mesures CO2 (Senseair S88)')">Mesures CO2 (Senseair S88)</button>
|
||||
<button class="btn btn-warning mb-2" onclick="openTableModal('timestamp_table','Timestamp Table')" data-i18n="database.timestampTable">Timestamp Table</button>
|
||||
</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_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_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>
|
||||
@@ -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_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_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>
|
||||
@@ -162,11 +157,38 @@
|
||||
<div class="row mt-2">
|
||||
<div id="table_data"></div>
|
||||
</div>
|
||||
|
||||
|
||||
</main>
|
||||
</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 -->
|
||||
|
||||
<!-- Link Ajax locally -->
|
||||
@@ -286,218 +308,136 @@ window.onload = function() {
|
||||
|
||||
|
||||
|
||||
// TABLE PM
|
||||
function get_data_sqlite(table, limit, download , startDate = "", endDate = "") {
|
||||
console.log(`Getting data for table: ${table}, limit: ${limit}, download: ${download}, start: ${startDate}, end: ${endDate}`);
|
||||
// Construct URL parameters dynamically
|
||||
let url = `launcher.php?type=table_mesure&table=${table}&limit=${limit}&download=${download}`;
|
||||
// Build the <th> header cells for a given table
|
||||
function buildTableHeader(table) {
|
||||
const headers = {
|
||||
data_NPM: ['Timestamp','PM1','PM2.5','PM10','Temperature (°C)','Humidity (%)','Status'],
|
||||
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
|
||||
if (download) {
|
||||
url += `&start_date=${startDate}&end_date=${endDate}`;
|
||||
}
|
||||
|
||||
console.log(url);
|
||||
|
||||
$.ajax({
|
||||
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);
|
||||
// Build the <td> cells for one row of a given table
|
||||
function buildTableRow(table, columns) {
|
||||
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>`;
|
||||
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>`;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
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() {
|
||||
return document.getElementById("records_limit").value;
|
||||
// Modal pagination state
|
||||
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() {
|
||||
@@ -542,6 +482,9 @@ function downloadCSV(response, table) {
|
||||
else if (table === "data_MHZ19") {
|
||||
csvContent += "TimestampUTC,CO2_ppm\n";
|
||||
}
|
||||
else if (table === "data_S88") {
|
||||
csvContent += "TimestampUTC,CO2_ppm\n";
|
||||
}
|
||||
|
||||
// Format rows as CSV
|
||||
rows.forEach(row => {
|
||||
@@ -568,7 +511,9 @@ const tableDisplayNames = {
|
||||
'data_envea': 'Gaz (Cairsens)',
|
||||
'data_WIND': 'Vent',
|
||||
'data_MPPT': 'Batterie (MPPT)',
|
||||
'data_NOISE': 'Bruit'
|
||||
'data_NOISE': 'Bruit',
|
||||
'data_MHZ19': 'CO2 (MH-Z19)',
|
||||
'data_S88': 'CO2 (Senseair S88)'
|
||||
};
|
||||
|
||||
function loadDbStats() {
|
||||
|
||||
Reference in New Issue
Block a user