diff --git a/html/database.html b/html/database.html
index dd63463..958491b 100755
--- a/html/database.html
+++ b/html/database.html
@@ -527,17 +527,22 @@ function loadDbStats() {
html += '
Entrées | ';
html += 'Plus ancienne | ';
html += 'Plus récente | ';
+ html += 'CSV | ';
html += '';
response.tables.forEach(function(t) {
const displayName = tableDisplayNames[t.name] || t.name;
const oldest = t.oldest ? t.oldest.substring(0, 16) : '-';
const newest = t.newest ? t.newest.substring(0, 16) : '-';
+ const downloadBtn = t.count > 0
+ ? ''
+ : '-';
html += '';
html += '| ' + displayName + ' | ';
html += '' + t.count.toLocaleString() + ' | ';
html += '' + oldest + ' | ';
html += '' + newest + ' | ';
+ html += '' + downloadBtn + ' | ';
html += '
';
});
diff --git a/html/lang/en.json b/html/lang/en.json
index 00b42c7..556043f 100644
--- a/html/lang/en.json
+++ b/html/lang/en.json
@@ -98,7 +98,8 @@
"statsTable": "Table",
"statsCount": "Entries",
"statsOldest": "Oldest",
- "statsNewest": "Newest"
+ "statsNewest": "Newest",
+ "statsDownload": "CSV"
},
"logs": {
"title": "The Log",
diff --git a/html/lang/fr.json b/html/lang/fr.json
index bf7ebba..b910dc2 100644
--- a/html/lang/fr.json
+++ b/html/lang/fr.json
@@ -98,7 +98,8 @@
"statsTable": "Table",
"statsCount": "Entrées",
"statsOldest": "Plus ancienne",
- "statsNewest": "Plus récente"
+ "statsNewest": "Plus récente",
+ "statsDownload": "CSV"
},
"logs": {
"title": "Le journal",
diff --git a/html/launcher.php b/html/launcher.php
index 74d80e0..df83e7c 100755
--- a/html/launcher.php
+++ b/html/launcher.php
@@ -584,6 +584,52 @@ if ($type == "db_table_stats") {
}
}
+if ($type == "download_full_table") {
+ $databasePath = '/var/www/nebuleair_pro_4g/sqlite/sensors.db';
+ $table = $_GET['table'] ?? '';
+
+ // Whitelist of allowed tables
+ $allowedTables = ['data_NPM', 'data_NPM_5channels', 'data_BME280', 'data_envea', 'data_WIND', 'data_MPPT', 'data_NOISE'];
+
+ if (!in_array($table, $allowedTables)) {
+ header('Content-Type: application/json');
+ echo json_encode(['error' => 'Invalid table name']);
+ exit;
+ }
+
+ // CSV headers per table
+ $csvHeaders = [
+ 'data_NPM' => 'TimestampUTC,PM1,PM2.5,PM10,Temperature_sensor,Humidity_sensor',
+ 'data_NPM_5channels' => 'TimestampUTC,PM_ch1,PM_ch2,PM_ch3,PM_ch4,PM_ch5',
+ 'data_BME280' => 'TimestampUTC,Temperature,Humidity,Pressure',
+ 'data_envea' => 'TimestampUTC,NO2,H2S,NH3,CO,O3,SO2',
+ 'data_WIND' => 'TimestampUTC,Wind_speed_kmh,Wind_direction_V',
+ 'data_MPPT' => 'TimestampUTC,Battery_voltage,Battery_current,Solar_voltage,Solar_power,Charger_status',
+ 'data_NOISE' => 'TimestampUTC,Current_LEQ,DB_A_value'
+ ];
+
+ try {
+ $db = new PDO("sqlite:$databasePath");
+ $rows = $db->query("SELECT * FROM $table ORDER BY timestamp ASC")->fetchAll(PDO::FETCH_NUM);
+
+ header('Content-Type: text/csv; charset=utf-8');
+ header('Content-Disposition: attachment; filename="' . $table . '_full.csv"');
+
+ $output = fopen('php://output', 'w');
+ // Write header
+ fputcsv($output, explode(',', $csvHeaders[$table]));
+ // Write data rows
+ foreach ($rows as $row) {
+ fputcsv($output, $row);
+ }
+ fclose($output);
+ } catch (PDOException $e) {
+ header('Content-Type: application/json');
+ echo json_encode(['error' => 'Database query failed: ' . $e->getMessage()]);
+ }
+ exit;
+}
+
if ($type == "linux_disk") {
$command = 'df -h /';
$output = shell_exec($command);