From 20c6a12251c73d9184a8bf833aed23fe2a59001e Mon Sep 17 00:00:00 2001 From: PaulVua Date: Mon, 16 Feb 2026 12:07:58 +0100 Subject: [PATCH] feat(ui): add database stats card on database page Show table info (entry count, oldest/newest dates, total DB size) in a new card on the database page, with auto-refresh and i18n support. Co-Authored-By: Claude Opus 4.6 --- html/database.html | 84 ++++++++++++++++++++++++++++++++++++++++++++-- html/lang/en.json | 8 ++++- html/lang/fr.json | 8 ++++- html/launcher.php | 54 +++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 4 deletions(-) diff --git a/html/database.html b/html/database.html index 89e6ee2..dd63463 100755 --- a/html/database.html +++ b/html/database.html @@ -108,7 +108,24 @@ -
+
+
+
+
Informations sur la base
+
+
+
+ Chargement... +
+
+
+
+
+ +
+ +
+
Zone dangereuse
@@ -118,7 +135,6 @@
-
@@ -224,6 +240,9 @@ window.onload = function() { }); //end ajax + // Get database table stats + loadDbStats(); + //get local RTC $.ajax({ url: 'launcher.php?type=RTC_time', @@ -478,6 +497,67 @@ function downloadCSV(response, table) { document.body.removeChild(a); } +// Table display names +const tableDisplayNames = { + 'data_NPM': 'PM (NextPM)', + 'data_NPM_5channels': 'PM 5 canaux', + 'data_BME280': 'Temp/Hum (BME280)', + 'data_envea': 'Gaz (Cairsens)', + 'data_WIND': 'Vent', + 'data_MPPT': 'Batterie (MPPT)', + 'data_NOISE': 'Bruit' +}; + +function loadDbStats() { + $.ajax({ + url: 'launcher.php?type=db_table_stats', + dataType: 'json', + method: 'GET', + success: function(response) { + if (!response.success) { + document.getElementById('db_stats_content').innerHTML = + '
' + (response.error || 'Erreur') + '
'; + return; + } + + let html = '

Taille totale: ' + response.size_mb + ' MB

'; + html += '
'; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + 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) : '-'; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + }); + + html += '
TableEntréesPlus anciennePlus récente
' + displayName + '' + t.count.toLocaleString() + '' + oldest + '' + newest + '
'; + html += ''; + + document.getElementById('db_stats_content').innerHTML = html; + + // Re-apply translations if i18n is loaded + if (typeof i18n !== 'undefined' && i18n.translations && Object.keys(i18n.translations).length > 0) { + i18n.applyTranslations(); + } + }, + error: function(xhr, status, error) { + document.getElementById('db_stats_content').innerHTML = + '
Erreur: ' + error + '
'; + } + }); +} + // Function to empty all sensor tables function emptySensorTables() { // Show confirmation dialog diff --git a/html/lang/en.json b/html/lang/en.json index 12dd96d..00b42c7 100644 --- a/html/lang/en.json +++ b/html/lang/en.json @@ -92,7 +92,13 @@ "dangerZone": "Danger Zone", "dangerWarning": "Warning: This action is irreversible!", "emptyAllTables": "Empty all sensor tables", - "emptyTablesNote": "Note: Configuration and timestamp tables will be preserved." + "emptyTablesNote": "Note: Configuration and timestamp tables will be preserved.", + "statsTitle": "Database Information", + "statsDbSize": "Total size:", + "statsTable": "Table", + "statsCount": "Entries", + "statsOldest": "Oldest", + "statsNewest": "Newest" }, "logs": { "title": "The Log", diff --git a/html/lang/fr.json b/html/lang/fr.json index e48b065..bf7ebba 100644 --- a/html/lang/fr.json +++ b/html/lang/fr.json @@ -92,7 +92,13 @@ "dangerZone": "Zone dangereuse", "dangerWarning": "Attention: Cette action est irréversible!", "emptyAllTables": "Vider toutes les tables de capteurs", - "emptyTablesNote": "Note: Les tables de configuration et horodatage seront préservées." + "emptyTablesNote": "Note: Les tables de configuration et horodatage seront préservées.", + "statsTitle": "Informations sur la base", + "statsDbSize": "Taille totale:", + "statsTable": "Table", + "statsCount": "Entrées", + "statsOldest": "Plus ancienne", + "statsNewest": "Plus récente" }, "logs": { "title": "Le journal", diff --git a/html/launcher.php b/html/launcher.php index c926a2f..74d80e0 100755 --- a/html/launcher.php +++ b/html/launcher.php @@ -530,6 +530,60 @@ if ($type == "database_size") { } +if ($type == "db_table_stats") { + $databasePath = '/var/www/nebuleair_pro_4g/sqlite/sensors.db'; + + if (file_exists($databasePath)) { + try { + $db = new PDO("sqlite:$databasePath"); + + // Database file size + $fileSizeBytes = filesize($databasePath); + $fileSizeMB = round($fileSizeBytes / (1024 * 1024), 2); + + // Sensor data tables to inspect + $tables = ['data_NPM', 'data_NPM_5channels', 'data_BME280', 'data_envea', 'data_WIND', 'data_MPPT', 'data_NOISE']; + + $tableStats = []; + foreach ($tables as $tableName) { + // Check if table exists + $check = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='$tableName'"); + if ($check->fetch()) { + $countResult = $db->query("SELECT COUNT(*) as cnt FROM $tableName")->fetch(); + $count = (int)$countResult['cnt']; + + $oldest = null; + $newest = null; + if ($count > 0) { + $oldestResult = $db->query("SELECT MIN(timestamp) as ts FROM $tableName")->fetch(); + $newestResult = $db->query("SELECT MAX(timestamp) as ts FROM $tableName")->fetch(); + $oldest = $oldestResult['ts']; + $newest = $newestResult['ts']; + } + + $tableStats[] = [ + 'name' => $tableName, + 'count' => $count, + 'oldest' => $oldest, + 'newest' => $newest + ]; + } + } + + echo json_encode([ + 'success' => true, + 'size_mb' => $fileSizeMB, + 'size_bytes' => $fileSizeBytes, + 'tables' => $tableStats + ]); + } catch (PDOException $e) { + echo json_encode(['success' => false, 'error' => 'Database query failed: ' . $e->getMessage()]); + } + } else { + echo json_encode(['success' => false, 'error' => 'Database file not found']); + } +} + if ($type == "linux_disk") { $command = 'df -h /'; $output = shell_exec($command);