diff --git a/html/database.html b/html/database.html index f4de053..97d42a8 100755 --- a/html/database.html +++ b/html/database.html @@ -108,6 +108,17 @@ +
+
+
+
Zone dangereuse
+

Attention: Cette action est irréversible!

+ + Note: Les tables de configuration et horodatage seront préservées. +
+
+
+
@@ -441,6 +452,74 @@ function downloadCSV(response, table) { document.body.removeChild(a); } +// Function to empty all sensor tables +function emptySensorTables() { + // Show confirmation dialog + const confirmed = confirm( + "WARNING: This will permanently delete ALL sensor data from the database!\n\n" + + "The following tables will be emptied:\n" + + "- data_NPM\n" + + "- data_NPM_5channels\n" + + "- data_BME280\n" + + "- data_envea\n" + + "- data_WIND\n" + + "- data_MPPT\n" + + "- data_NOISE\n\n" + + "Configuration and timestamp tables will be preserved.\n\n" + + "Are you absolutely sure you want to continue?" + ); + + if (!confirmed) { + console.log("Empty sensor tables operation cancelled by user"); + return; + } + + // Show loading message + const tableDataDiv = document.getElementById("table_data"); + tableDataDiv.innerHTML = '
Emptying sensor tables... Please wait...
'; + + // Make AJAX request to empty tables + $.ajax({ + url: 'launcher.php?type=empty_sensor_tables', + dataType: 'json', + method: 'GET', + success: function(response) { + console.log("Empty sensor tables response:", response); + + if (response.success) { + // Show success message + let message = '
'; + message += '
Success!
'; + message += '

' + response.message + '

'; + + if (response.tables_processed && response.tables_processed.length > 0) { + message += '

Tables emptied:

    '; + response.tables_processed.forEach(table => { + message += `
  • ${table.name}: ${table.deleted} records deleted
  • `; + }); + message += '
'; + } + + message += '
'; + tableDataDiv.innerHTML = message; + } else { + // Show error message + tableDataDiv.innerHTML = `
+
Error!
+

${response.message || response.error || 'Unknown error occurred'}

+
`; + } + }, + error: function(xhr, status, error) { + console.error('AJAX request failed:', status, error); + tableDataDiv.innerHTML = `
+
Error!
+

Failed to empty sensor tables: ${error}

+
`; + } + }); +} + diff --git a/html/lang/en.json b/html/lang/en.json index b28f838..12dd96d 100644 --- a/html/lang/en.json +++ b/html/lang/en.json @@ -88,7 +88,11 @@ "timestampTable": "Timestamp Table", "downloadData": "Download Data", "startDate": "Start date:", - "endDate": "End date:" + "endDate": "End date:", + "dangerZone": "Danger Zone", + "dangerWarning": "Warning: This action is irreversible!", + "emptyAllTables": "Empty all sensor tables", + "emptyTablesNote": "Note: Configuration and timestamp tables will be preserved." }, "logs": { "title": "The Log", diff --git a/html/lang/fr.json b/html/lang/fr.json index 7d1717b..e48b065 100644 --- a/html/lang/fr.json +++ b/html/lang/fr.json @@ -88,7 +88,11 @@ "timestampTable": "Timestamp Table", "downloadData": "Télécharger les données", "startDate": "Date de début:", - "endDate": "Date de fin:" + "endDate": "Date de fin:", + "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." }, "logs": { "title": "Le journal", diff --git a/html/launcher.php b/html/launcher.php index 1449535..2a67916 100755 --- a/html/launcher.php +++ b/html/launcher.php @@ -1270,13 +1270,68 @@ if ($type == "toggle_systemd_service") { } } +// Empty all sensor tables (preserve config and timestamp tables) +if ($type == "empty_sensor_tables") { + try { + // Execute the empty sensor tables script + $command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/empty_sensor_tables.py 2>&1'; + $output = shell_exec($command); + + // Try to extract JSON result from output + $json_start = strpos($output, '[JSON_RESULT]'); + if ($json_start !== false) { + $json_data = substr($output, $json_start + strlen('[JSON_RESULT]')); + $json_data = trim($json_data); + + // Find the first { and last } + $first_brace = strpos($json_data, '{'); + $last_brace = strrpos($json_data, '}'); + + if ($first_brace !== false && $last_brace !== false) { + $json_data = substr($json_data, $first_brace, $last_brace - $first_brace + 1); + $result = json_decode($json_data, true); + + if ($result !== null) { + echo json_encode($result); + } else { + // JSON decode failed, return raw output + echo json_encode([ + 'success' => true, + 'message' => 'Tables emptied', + 'output' => $output + ]); + } + } else { + echo json_encode([ + 'success' => true, + 'message' => 'Tables emptied', + 'output' => $output + ]); + } + } else { + // No JSON marker found, return raw output + echo json_encode([ + 'success' => true, + 'message' => 'Tables emptied', + 'output' => $output + ]); + } + + } catch (Exception $e) { + echo json_encode([ + 'success' => false, + 'error' => 'Script execution failed: ' . $e->getMessage() + ]); + } +} + /* - _____ ____ _ _ _ - | ____|_ ____ _____ __ _ | _ \ ___| |_ ___ ___| |_(_) ___ _ __ - | _| | '_ \ \ / / _ \/ _` | | | | |/ _ \ __/ _ \/ __| __| |/ _ \| '_ \ + _____ ____ _ _ _ + | ____|_ ____ _____ __ _ | _ \ ___| |_ ___ ___| |_(_) ___ _ __ + | _| | '_ \ \ / / _ \/ _` | | | | |/ _ \ __/ _ \/ __| __| |/ _ \| '_ \ | |___| | | \ V / __/ (_| | | |_| | __/ || __/ (__| |_| | (_) | | | | |_____|_| |_|\_/ \___|\__,_| |____/ \___|\__\___|\___|\__|_|\___/|_| |_| - + */ // Detect Envea devices on specified port diff --git a/sqlite/empty_sensor_tables.py b/sqlite/empty_sensor_tables.py new file mode 100644 index 0000000..65d7edc --- /dev/null +++ b/sqlite/empty_sensor_tables.py @@ -0,0 +1,204 @@ +''' + ____ ___ _ _ _ + / ___| / _ \| | (_) |_ ___ + \___ \| | | | | | | __/ _ \ + ___) | |_| | |___| | || __/ + |____/ \__\_\_____|_|\__\___| + +Script to empty (delete all data from) sensor tables in the SQLite database +This script empties sensor data tables but preserves: +- timestamp_table +- config_table +- envea_sondes_table +- config_scripts_table + +/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/empty_sensor_tables.py +''' + +import sqlite3 +import sys +import json + +def table_exists(cursor, table_name): + """Check if a table exists in the database""" + try: + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,)) + return cursor.fetchone() is not None + except sqlite3.Error as e: + print(f"[ERROR] Failed to check if table '{table_name}' exists: {e}") + return False + + +def get_table_count(cursor, table_name): + """Get the number of records in a table""" + try: + cursor.execute(f"SELECT COUNT(*) FROM {table_name}") + return cursor.fetchone()[0] + except sqlite3.Error as e: + print(f"[WARNING] Could not get count for table '{table_name}': {e}") + return 0 + + +def empty_table(cursor, table_name): + """Delete all records from a specific table""" + try: + # Get record count before deletion + initial_count = get_table_count(cursor, table_name) + + if initial_count == 0: + print(f"[INFO] Table '{table_name}' is already empty") + return True, 0 + + # Delete all records + cursor.execute(f"DELETE FROM {table_name}") + deleted_count = cursor.rowcount + + print(f"[SUCCESS] Deleted {deleted_count} records from '{table_name}'") + return True, deleted_count + + except sqlite3.Error as e: + print(f"[ERROR] Failed to empty table '{table_name}': {e}") + return False, 0 + + +def main(): + result = { + 'success': False, + 'message': '', + 'tables_processed': [], + 'total_deleted': 0 + } + + try: + # Connect to the SQLite database + print("[INFO] Connecting to database...") + conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db") + cursor = conn.cursor() + + # Check database connection + cursor.execute("SELECT sqlite_version()") + version = cursor.fetchone()[0] + print(f"[INFO] Connected to SQLite version: {version}") + + # List of sensor tables to empty (EXCLUDING timestamp_table and config tables) + sensor_tables = [ + "data_NPM", + "data_NPM_5channels", + "data_BME280", + "data_envea", + "data_WIND", + "data_MPPT", + "data_NOISE", + "modem_status" + ] + + # Tables to PRESERVE (not empty) + preserved_tables = [ + "timestamp_table", + "config_table", + "envea_sondes_table", + "config_scripts_table" + ] + + print(f"[INFO] Will empty the following sensor tables: {', '.join(sensor_tables)}") + print(f"[INFO] Will preserve the following tables: {', '.join(preserved_tables)}") + + # Check which tables actually exist + existing_tables = [] + missing_tables = [] + + for table in sensor_tables: + if table_exists(cursor, table): + existing_tables.append(table) + record_count = get_table_count(cursor, table) + print(f"[INFO] Table '{table}' exists with {record_count} records") + else: + missing_tables.append(table) + print(f"[WARNING] Table '{table}' does not exist - skipping") + + if missing_tables: + print(f"[INFO] Missing tables: {', '.join(missing_tables)}") + + if not existing_tables: + result['success'] = True + result['message'] = "No sensor tables found to empty" + print("[WARNING] No sensor tables found to empty!") + print(json.dumps(result)) + return True + + # Loop through existing tables and empty them + successful_operations = 0 + failed_operations = 0 + total_deleted = 0 + + for table in existing_tables: + success, deleted = empty_table(cursor, table) + if success: + successful_operations += 1 + total_deleted += deleted + result['tables_processed'].append({ + 'name': table, + 'deleted': deleted + }) + else: + failed_operations += 1 + + # Commit changes + print("[INFO] Committing changes...") + conn.commit() + print("[SUCCESS] Changes committed successfully!") + + # Run VACUUM to optimize database space + if total_deleted > 0: + print("[INFO] Running VACUUM to optimize database space...") + try: + cursor.execute("VACUUM") + print("[SUCCESS] Database optimized successfully!") + except sqlite3.Error as e: + print(f"[WARNING] VACUUM failed: {e}") + + # Summary + print(f"\n[SUMMARY]") + print(f"Tables emptied successfully: {successful_operations}") + print(f"Tables with errors: {failed_operations}") + print(f"Tables skipped (missing): {len(missing_tables)}") + print(f"Total records deleted: {total_deleted}") + + result['success'] = True + result['message'] = f"Successfully emptied {successful_operations} sensor tables. Total records deleted: {total_deleted}" + result['total_deleted'] = total_deleted + + if failed_operations == 0: + print("[SUCCESS] All sensor tables emptied successfully!") + else: + result['message'] = f"Partial success: {successful_operations} tables emptied, {failed_operations} failed" + print("[WARNING] Some operations failed - check logs above") + + # Output JSON result for web interface + print("\n[JSON_RESULT]") + print(json.dumps(result)) + + return failed_operations == 0 + + except sqlite3.Error as e: + result['message'] = f"Database error: {e}" + print(f"[ERROR] Database error: {e}") + print("\n[JSON_RESULT]") + print(json.dumps(result)) + return False + except Exception as e: + result['message'] = f"Unexpected error: {e}" + print(f"[ERROR] Unexpected error: {e}") + print("\n[JSON_RESULT]") + print(json.dumps(result)) + return False + finally: + # Always close the database connection + if 'conn' in locals(): + conn.close() + print("[INFO] Database connection closed") + + +if __name__ == "__main__": + success = main() + sys.exit(0 if success else 1)