commit 42989aa8df6cceb33c813308fae50636c2ce728a Author: ske087 Date: Thu Aug 14 15:53:00 2025 +0300 Initial server monitoring system with port 80 support Features: - Device management and monitoring dashboard - Remote command execution on devices via port 80 - Auto-update coordination for multiple devices - Database reset functionality with safety confirmations - Server logs filtering and dedicated logging interface - Device status monitoring and management - SQLite database for comprehensive logging - Web interface with Bootstrap styling - Comprehensive error handling and logging Key components: - server.py: Main Flask application with all routes - templates/: Complete web interface templates - data/database.db: SQLite database for device logs - UPDATE_SUMMARY.md: Development progress documentation diff --git a/UPDATE_SUMMARY.md b/UPDATE_SUMMARY.md new file mode 100644 index 0000000..a6e1bb0 --- /dev/null +++ b/UPDATE_SUMMARY.md @@ -0,0 +1,93 @@ +# Device Management System Update - Version 2.4 + +## Overview +This update adds remote command execution capabilities between the Server_Monitorizare and the prezenta devices, along with improved error handling for network connectivity issues. + +## New Features + +### 1. Robust Network Configuration (app.py) +- **File-based fallback system**: Device hostname and IP are saved to `./data/device_info.txt` +- **Automatic error recovery**: When socket.gaierror occurs, the system loads previously saved values +- **No external dependencies**: Uses only built-in Python modules +- **Graceful degradation**: App continues to run even with network issues + +### 2. Remote Command Execution System + +#### Server Side (server.py) +New endpoints added: +- `/device_management` - Web interface for managing devices +- `/execute_command` - Execute command on single device +- `/execute_command_bulk` - Execute command on multiple devices +- `/device_status/` - Get device status information + +#### Device Side (app.py) +New Flask server running on port 5000: +- `/execute_command` - Receive and execute system commands +- `/status` - Provide device status information + +### 3. Security Features +- **Whitelisted commands only**: Prevents execution of arbitrary commands +- **Command logging**: All command execution attempts are logged +- **Timeout protection**: Commands have 5-minute timeout limit +- **Error handling**: Comprehensive error reporting + +### 4. Web Interface Improvements +- **Device Management Dashboard**: Complete interface for managing all devices +- **Real-time status checking**: Check if devices are online/offline +- **Bulk operations**: Execute commands on multiple devices simultaneously +- **Search functionality**: Filter devices by hostname or IP +- **Command selection**: Dropdown menus for common system commands + +## Allowed Commands +For security, only these commands can be executed remotely: +- `sudo apt update` - Update package lists +- `sudo apt upgrade -y` - Upgrade packages +- `sudo apt autoremove -y` - Remove unused packages +- `sudo apt autoclean` - Clean package cache +- `sudo reboot` - Reboot device +- `sudo shutdown -h now` - Shutdown device +- `df -h` - Check disk space +- `free -m` - Check memory usage +- `uptime` - Check system uptime +- `systemctl status` - Check system services +- `sudo systemctl restart networking` - Restart network services +- `sudo systemctl restart ssh` - Restart SSH service + +## File Structure Changes + +### New Files: +- `Server_Monitorizare/templates/device_management.html` - Device management interface +- `prezenta/data/device_info.txt` - Device configuration cache (auto-created) + +### Modified Files: +- `prezenta/app.py` - Added command execution server and network error handling +- `Server_Monitorizare/server.py` - Added command execution endpoints +- `Server_Monitorizare/templates/dashboard.html` - Added device management link + +## Usage Instructions + +### Starting the Services +1. **Start the monitoring server** (port 80): + ```bash + cd Server_Monitorizare + sudo python3 server.py + ``` + +2. **Start the device app** (ports 5000 for commands + normal operation): + ```bash + cd prezenta + python3 app.py + ``` + +### Using the Web Interface +1. Navigate to `http://server-ip:80/dashboard` +2. Click "Device Management" to access the new interface +3. Use the dropdown menus to select commands +4. Execute on single devices or use bulk operations + +## Benefits +1. **Centralized device management** - Control all devices from one interface +2. **Automated maintenance** - Schedule updates and maintenance tasks +3. **Real-time monitoring** - Check device status and performance +4. **Improved reliability** - Network error handling prevents app crashes +5. **Security** - Controlled command execution with comprehensive logging diff --git a/Working_Files/Modul Logging /how_to_use_logging_module.txt b/Working_Files/Modul Logging /how_to_use_logging_module.txt new file mode 100644 index 0000000..bf1fb01 --- /dev/null +++ b/Working_Files/Modul Logging /how_to_use_logging_module.txt @@ -0,0 +1,93 @@ + +--- + +### `logging_module.py` +```python +import logging +import os +import socket +import requests +from datetime import datetime, timedelta + +# Configure logging +LOG_FILE_PATH = './data/log.txt' +logging.basicConfig(filename=LOG_FILE_PATH, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# Function to delete old logs +def delete_old_logs(): + log_dir = './data/' + log_file = 'log.txt' + log_path = os.path.join(log_dir, log_file) + + if os.path.exists(log_path): + file_mod_time = datetime.fromtimestamp(os.path.getmtime(log_path)) + if datetime.now() - file_mod_time > timedelta(days=10): + os.remove(log_path) + log_info_with_server(f"Deleted old log file: {log_file}") + else: + log_info_with_server(f"Log file is not older than 10 days: {log_file}") + else: + log_info_with_server(f"Log file does not exist: {log_file}") + +# Function to read the name (idmasa) from the file +def read_name_from_file(): + try: + with open("./data/idmasa.txt", "r") as file: + +### How to Use the Module in Another App + +1. Save the script as `logging_module.py` in a directory accessible to your other Python applications. + +2. Import the module and use its functions in your other app. For example: + +```python +# Import the logging module +from logging_module import log_info_with_server, delete_old_logs + +# Example usage +log_info_with_server("This is a test log message.") +delete_old_logs() +``` + +--- + +### Explanation of the Script: +1. **Encapsulation**: + - The script encapsulates all logging-related functionality, including: + - Deleting old logs ([`delete_old_logs`](command:_github.copilot.openSymbolFromReferences?%5B%22%22%2C%5B%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fapp.py%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A16%2C%22character%22%3A4%7D%7D%2C%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fapp.py%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A68%2C%22character%22%3A0%7D%7D%2C%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fapp.py%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A16%2C%22character%22%3A4%7D%7D%5D%2C%229495c72a-c9b2-498f-9787-1c43fb524282%22%5D "Go to definition")). + - Reading the [`idmasa`](command:_github.copilot.openSymbolFromReferences?%5B%22%22%2C%5B%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fapp.py%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A31%2C%22character%22%3A29%7D%7D%2C%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fapp.py%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A31%2C%22character%22%3A29%7D%7D%5D%2C%229495c72a-c9b2-498f-9787-1c43fb524282%22%5D "Go to definition") value from a file ([`read_name_from_file`](command:_github.copilot.openSymbolFromReferences?%5B%22%22%2C%5B%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fapp.py%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A32%2C%22character%22%3A4%7D%7D%2C%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fapp.py%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A32%2C%22character%22%3A4%7D%7D%5D%2C%229495c72a-c9b2-498f-9787-1c43fb524282%22%5D "Go to definition")). + - Sending logs to a remote server ([`send_log_to_server`](command:_github.copilot.openSymbolFromReferences?%5B%22%22%2C%5B%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fapp.py%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A42%2C%22character%22%3A4%7D%7D%2C%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fapp.py%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A42%2C%22character%22%3A4%7D%7D%5D%2C%229495c72a-c9b2-498f-9787-1c43fb524282%22%5D "Go to definition")). + - Logging messages locally and remotely ([`log_info_with_server`](command:_github.copilot.openSymbolFromReferences?%5B%22%22%2C%5B%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fapp.py%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A60%2C%22character%22%3A4%7D%7D%2C%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fapp.py%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A25%2C%22character%22%3A12%7D%7D%5D%2C%229495c72a-c9b2-498f-9787-1c43fb524282%22%5D "Go to definition")). + +2. **Reusability**: + - The script is designed to be imported and reused in other Python applications. + +3. **Configuration**: + - The log file path (`LOG_FILE_PATH`) and server URL ([`server_url`](command:_github.copilot.openSymbolFromReferences?%5B%22%22%2C%5B%7B%22uri%22%3A%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fapp.py%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%22pos%22%3A%7B%22line%22%3A52%2C%22character%22%3A8%7D%7D%5D%2C%229495c72a-c9b2-498f-9787-1c43fb524282%22%5D "Go to definition")) can be customized as needed. + +4. **Error Handling**: + - The script includes error handling for file operations and HTTP requests. + +--- + +### Testing the Module +1. Create a new Python file (e.g., `test_logging.py`) and import the module: + ```python + from logging_module import log_info_with_server, delete_old_logs + + # Test the logging functions + log_info_with_server("Testing logging from another app.") + delete_old_logs() + ``` + +2. Run the `test_logging.py` script: + ```bash + python3 test_logging.py + ``` + +3. Verify that: + - The log message is written to [`./data/log.txt`](command:_github.copilot.openRelativePath?%5B%7B%22scheme%22%3A%22file%22%2C%22authority%22%3A%22%22%2C%22path%22%3A%22%2Fhome%2Fpi%2FDesktop%2Fprezenta%2Fdata%2Flog.txt%22%2C%22query%22%3A%22%22%2C%22fragment%22%3A%22%22%7D%2C%229495c72a-c9b2-498f-9787-1c43fb524282%22%5D "/home/pi/Desktop/prezenta/data/log.txt"). + - The log message is sent to the remote server (if configured correctly). + - Old logs are deleted if they are older than 10 days. + +This approach ensures that the logging functionality is modular and reusable across multiple Python applications. \ No newline at end of file diff --git a/Working_Files/Modul Logging /logging_module.py b/Working_Files/Modul Logging /logging_module.py new file mode 100644 index 0000000..0ce6350 --- /dev/null +++ b/Working_Files/Modul Logging /logging_module.py @@ -0,0 +1,60 @@ +import logging +import os +import socket +import requests +from datetime import datetime, timedelta + +# Configure logging +LOG_FILE_PATH = './data/log.txt' +logging.basicConfig(filename=LOG_FILE_PATH, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +# Function to delete old logs +def delete_old_logs(): + log_dir = './data/' + log_file = 'log.txt' + log_path = os.path.join(log_dir, log_file) + + if os.path.exists(log_path): + file_mod_time = datetime.fromtimestamp(os.path.getmtime(log_path)) + if datetime.now() - file_mod_time > timedelta(days=10): + os.remove(log_path) + log_info_with_server(f"Deleted old log file: {log_file}") + else: + log_info_with_server(f"Log file is not older than 10 days: {log_file}") + else: + log_info_with_server(f"Log file does not exist: {log_file}") + +# Function to read the name (idmasa) from the file +def read_name_from_file(): + try: + with open("./data/idmasa.txt", "r") as file: + n_masa = file.readline().strip() + return n_masa + except FileNotFoundError: + logging.error("File ./data/idmasa.txt not found.") + return "unknown" + +# Function to send logs to a remote server +def send_log_to_server(log_message, n_masa): + try: + hostname = socket.gethostname() + device_ip = socket.gethostbyname(hostname) + log_data = { + "hostname": str(hostname), + "device_ip": str(device_ip), + "nume_masa": str(n_masa), + "log_message": str(log_message) + } + server_url = "http://rpi-ansible:80/logs" # Replace with your server's URL + print(log_data) # Debugging: Print log_data to verify its contents + response = requests.post(server_url, json=log_data, timeout=5) + response.raise_for_status() + logging.info("Log successfully sent to server: %s", log_message) + except requests.exceptions.RequestException as e: + logging.error("Failed to send log to server: %s", e) + +# Wrapper for logging.info to also send logs to the server +def log_info_with_server(message): + n_masa = read_name_from_file() # Read name (idmasa) from the file + logging.info(message) + send_log_to_server(message, n_masa) \ No newline at end of file diff --git a/Working_Files/drop.py b/Working_Files/drop.py new file mode 100644 index 0000000..5d33f7d --- /dev/null +++ b/Working_Files/drop.py @@ -0,0 +1,22 @@ +# acest script sterge informatia din baza de date pentru a avea un nou inceput . + +import sqlite3 + +# Define the database path +DATABASE = './data/database.db' + +def reset_database(): + # Connect to the database + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + + # Drop the existing logs table if it exists + cursor.execute('DELETE FROM logs;') + + + conn.commit() + + print("Database has been reset ") + +if __name__ == '__main__': + reset_database() \ No newline at end of file diff --git a/Working_Files/initalizedb.py b/Working_Files/initalizedb.py new file mode 100644 index 0000000..1c5a1d8 --- /dev/null +++ b/Working_Files/initalizedb.py @@ -0,0 +1,19 @@ +import sqlite3 +def initialize_database(): + DATABASE = "data/database.db" + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + hostname TEXT NOT NULL, + device_ip TEXT NOT NULL, + nume_masa TEXT NOT NULL, + timestamp TEXT NOT NULL, + event_description TEXT NOT NULL + ) + ''') + conn.commit() + print("Database initialized") + +initialize_database() \ No newline at end of file diff --git a/__pycache__/server.cpython-311.pyc b/__pycache__/server.cpython-311.pyc new file mode 100644 index 0000000..f9c428e Binary files /dev/null and b/__pycache__/server.cpython-311.pyc differ diff --git a/data/database.db b/data/database.db new file mode 100755 index 0000000..3cb2e15 Binary files /dev/null and b/data/database.db differ diff --git a/server.py b/server.py new file mode 100644 index 0000000..13ee1d0 --- /dev/null +++ b/server.py @@ -0,0 +1,462 @@ +from flask import Flask, request, render_template, jsonify, redirect, url_for +import sqlite3 +from datetime import datetime +from urllib.parse import unquote +import requests +import threading + +app = Flask(__name__) +DATABASE = 'data/database.db' # Updated path for the database +# Route to handle log submissions +@app.route('/logs', methods=['POST']) +@app.route('/log', methods=['POST']) +def log_event(): + try: + #print(f"Connecting to database at: {DATABASE}") + + # Get the JSON payload + data = request.json + if not data: + return {"error": "Invalid or missing JSON payload"}, 400 + + #print(f"Received request data: {data}") + + # Extract fields from the JSON payload + hostname = data.get('hostname') + device_ip = data.get('device_ip') + nume_masa = data.get('nume_masa') + log_message = data.get('log_message') + timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + # Validate required fields + if not hostname or not device_ip or not nume_masa or not log_message: + print("Validation failed: Missing required fields") + return {"error": "Missing required fields"}, 400 + + # Save the log to the database + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + cursor.execute(''' + INSERT INTO logs (hostname, device_ip, nume_masa, timestamp, event_description) + VALUES (?, ?, ?, ?, ?) + ''', (hostname, device_ip, nume_masa, timestamp, log_message)) + conn.commit() + print("Log saved successfully") + + return {"message": "Log saved successfully"}, 201 + + except sqlite3.Error as e: + print(f"Database error: {e}") + return {"error": f"Database connection failed: {e}"}, 500 + + except Exception as e: + print(f"Unexpected error: {e}") + return {"error": "An unexpected error occurred"}, 500 + +# Route to display the dashboard (excluding server logs) +@app.route('/dashboard', methods=['GET']) +def dashboard(): + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + # Fetch the last 60 logs excluding server logs, ordered by timestamp in descending order + cursor.execute(''' + SELECT hostname, device_ip, nume_masa, timestamp, event_description + FROM logs + WHERE hostname != 'SERVER' + ORDER BY timestamp DESC + LIMIT 60 + ''') + logs = cursor.fetchall() + return render_template('dashboard.html', logs=logs) +# Route to display logs for a specific device (excluding server logs) +@app.route('/device_logs/', methods=['GET']) +def device_logs(nume_masa): + nume_masa = unquote(nume_masa) # Decode URL-encoded value + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + # Order logs by timestamp in descending order, excluding server logs + cursor.execute(''' + SELECT hostname, nume_masa, timestamp, event_description + FROM logs + WHERE nume_masa = ? AND hostname != 'SERVER' + ORDER BY timestamp DESC + ''', (nume_masa,)) + logs = cursor.fetchall() + return render_template('device_logs.html', logs=logs, nume_masa=nume_masa) + +@app.route('/unique_devices', methods=['GET']) +def unique_devices(): + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + # Query to get unique devices with their most recent log (excluding server logs) + cursor.execute(''' + SELECT hostname, device_ip, MAX(timestamp) AS last_log, event_description + FROM logs + WHERE hostname != 'SERVER' + GROUP BY hostname, device_ip + ORDER BY last_log DESC + ''') + devices = cursor.fetchall() + return render_template('unique_devices.html', devices=devices) + +@app.route('/hostname_logs/', methods=['GET']) +def hostname_logs(hostname): + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + # Fetch logs for the specified hostname (excluding server logs) + cursor.execute(''' + SELECT hostname, nume_masa, timestamp, event_description + FROM logs + WHERE hostname = ? AND hostname != 'SERVER' + ORDER BY timestamp DESC + ''', (hostname,)) + logs = cursor.fetchall() + return render_template('hostname_logs.html', logs=logs, hostname=hostname) + +# Route to display server logs only +@app.route('/server_logs', methods=['GET']) +def server_logs(): + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + # Fetch only server logs, ordered by timestamp in descending order + cursor.execute(''' + SELECT hostname, device_ip, nume_masa, timestamp, event_description + FROM logs + WHERE hostname = 'SERVER' + ORDER BY timestamp DESC + ''') + logs = cursor.fetchall() + return render_template('server_logs.html', logs=logs) + +# Function to execute command on a remote device +def execute_command_on_device(device_ip, command): + """ + Send command to a device for execution + """ + try: + url = f"http://{device_ip}:80/execute_command" + payload = {"command": command} + + response = requests.post(url, json=payload, timeout=30) + + if response.status_code == 200: + result = response.json() + return {"success": True, "result": result} + else: + error_data = response.json() if response.content else {"error": "Unknown error"} + return {"success": False, "error": error_data} + + except requests.exceptions.RequestException as e: + return {"success": False, "error": f"Connection error: {str(e)}"} + except Exception as e: + return {"success": False, "error": f"Unexpected error: {str(e)}"} + +# Function to get device status +def get_device_status(device_ip): + """ + Get status information from a device + """ + try: + url = f"http://{device_ip}:80/status" + response = requests.get(url, timeout=10) + + if response.status_code == 200: + return {"success": True, "status": response.json()} + else: + return {"success": False, "error": "Failed to get device status"} + + except requests.exceptions.RequestException as e: + return {"success": False, "error": f"Connection error: {str(e)}"} + except Exception as e: + return {"success": False, "error": f"Unexpected error: {str(e)}"} + +# Route to display device management page (excluding server) +@app.route('/device_management', methods=['GET']) +def device_management(): + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + # Get unique devices excluding server + cursor.execute(''' + SELECT hostname, device_ip, MAX(timestamp) AS last_log + FROM logs + WHERE hostname != 'SERVER' + GROUP BY hostname, device_ip + ORDER BY last_log DESC + ''') + devices = cursor.fetchall() + return render_template('device_management.html', devices=devices) + +# Route to execute command on a specific device +@app.route('/execute_command', methods=['POST']) +def execute_command(): + try: + data = request.json + device_ip = data.get('device_ip') + command = data.get('command') + + if not device_ip or not command: + return jsonify({"error": "device_ip and command are required"}), 400 + + # Execute command on device + result = execute_command_on_device(device_ip, command) + + # Log the command execution attempt + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + status = "SUCCESS" if result['success'] else "FAILED" + log_message = f"Command '{command}' {status}" + if not result['success']: + log_message += f" - Error: {result['error']}" + + cursor.execute(''' + INSERT INTO logs (hostname, device_ip, nume_masa, timestamp, event_description) + VALUES (?, ?, ?, ?, ?) + ''', ("SERVER", device_ip, "COMMAND", datetime.now().strftime('%Y-%m-%d %H:%M:%S'), log_message)) + conn.commit() + + return jsonify(result), 200 if result['success'] else 400 + + except Exception as e: + return jsonify({"error": f"Server error: {str(e)}"}), 500 + +# Route to get device status +@app.route('/device_status/', methods=['GET']) +def device_status(device_ip): + result = get_device_status(device_ip) + return jsonify(result), 200 if result['success'] else 400 + +# Route to execute command on multiple devices +@app.route('/execute_command_bulk', methods=['POST']) +def execute_command_bulk(): + try: + data = request.json + device_ips = data.get('device_ips', []) + command = data.get('command') + + if not device_ips or not command: + return jsonify({"error": "device_ips and command are required"}), 400 + + results = {} + threads = [] + + def execute_on_device(ip): + results[ip] = execute_command_on_device(ip, command) + + # Execute commands in parallel + for ip in device_ips: + thread = threading.Thread(target=execute_on_device, args=(ip,)) + threads.append(thread) + thread.start() + + # Wait for all threads to complete + for thread in threads: + thread.join() + + # Log bulk command execution + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + for ip, result in results.items(): + status = "SUCCESS" if result['success'] else "FAILED" + log_message = f"Bulk command '{command}' {status}" + if not result['success']: + log_message += f" - Error: {result['error']}" + + cursor.execute(''' + INSERT INTO logs (hostname, device_ip, nume_masa, timestamp, event_description) + VALUES (?, ?, ?, ?, ?) + ''', ("SERVER", ip, "BULK_COMMAND", datetime.now().strftime('%Y-%m-%d %H:%M:%S'), log_message)) + conn.commit() + + return jsonify({"results": results}), 200 + + except Exception as e: + return jsonify({"error": f"Server error: {str(e)}"}), 500 + +@app.route('/auto_update_devices', methods=['POST']) +def auto_update_devices(): + """ + Trigger auto-update on selected devices + """ + try: + data = request.json + device_ips = data.get('device_ips', []) + + if not device_ips: + return jsonify({"error": "device_ips list is required"}), 400 + + results = [] + + for ip in device_ips: + try: + # Send auto-update command to device + response = requests.post( + f'http://{ip}:80/auto_update', + json={}, + timeout=10 + ) + + if response.status_code == 200: + result_data = response.json() + results.append({ + "device_ip": ip, + "success": True, + "status": result_data.get('status'), + "message": result_data.get('message'), + "old_version": result_data.get('old_version'), + "new_version": result_data.get('new_version') + }) + + # Log successful update + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + log_message = f"Auto-update: {result_data.get('message', 'Update initiated')}" + cursor.execute(''' + INSERT INTO logs (hostname, device_ip, nume_masa, timestamp, event_description) + VALUES (?, ?, ?, ?, ?) + ''', ("SERVER", ip, "AUTO_UPDATE", datetime.now().strftime('%Y-%m-%d %H:%M:%S'), log_message)) + conn.commit() + + else: + error_msg = f"HTTP {response.status_code}" + try: + error_data = response.json() + error_msg = error_data.get('error', error_msg) + except: + pass + + results.append({ + "device_ip": ip, + "success": False, + "error": error_msg + }) + + except requests.exceptions.Timeout: + results.append({ + "device_ip": ip, + "success": False, + "error": "Request timeout" + }) + except requests.exceptions.ConnectionError: + results.append({ + "device_ip": ip, + "success": False, + "error": "Connection failed - device may be offline" + }) + except Exception as e: + results.append({ + "device_ip": ip, + "success": False, + "error": str(e) + }) + + # Log failed update attempt + if not results[-1]['success']: + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + log_message = f"Auto-update failed: {results[-1]['error']}" + cursor.execute(''' + INSERT INTO logs (hostname, device_ip, nume_masa, timestamp, event_description) + VALUES (?, ?, ?, ?, ?) + ''', ("SERVER", ip, "AUTO_UPDATE", datetime.now().strftime('%Y-%m-%d %H:%M:%S'), log_message)) + conn.commit() + + return jsonify({"results": results}), 200 + + except Exception as e: + return jsonify({"error": f"Server error: {str(e)}"}), 500 + +# Route to clear and reset the database +@app.route('/reset_database', methods=['POST']) +def reset_database(): + """ + Clear all data from the database and reinitialize with fresh schema + """ + try: + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + + # Get the count of logs before deletion for logging + cursor.execute('SELECT COUNT(*) FROM logs') + log_count = cursor.fetchone()[0] + + # Drop the existing logs table + cursor.execute('DROP TABLE IF EXISTS logs') + + # Recreate the logs table with fresh schema + cursor.execute(''' + CREATE TABLE logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + hostname TEXT NOT NULL, + device_ip TEXT NOT NULL, + nume_masa TEXT NOT NULL, + timestamp TEXT NOT NULL, + event_description TEXT NOT NULL + ) + ''') + + # Insert a system log entry to mark the database reset + reset_timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + cursor.execute(''' + INSERT INTO logs (hostname, device_ip, nume_masa, timestamp, event_description) + VALUES (?, ?, ?, ?, ?) + ''', ("SERVER", "127.0.0.1", "SYSTEM", reset_timestamp, f"Database cleared and reinitialized - {log_count} logs deleted")) + + conn.commit() + + return jsonify({ + "success": True, + "message": "Database successfully cleared and reinitialized", + "timestamp": reset_timestamp, + "deleted_count": log_count + }), 200 + + except sqlite3.Error as e: + return jsonify({ + "success": False, + "error": f"Database error: {str(e)}" + }), 500 + except Exception as e: + return jsonify({ + "success": False, + "error": f"Unexpected error: {str(e)}" + }), 500 + +# Route to get database statistics +@app.route('/database_stats', methods=['GET']) +def database_stats(): + """ + Get database statistics including log count + """ + try: + with sqlite3.connect(DATABASE) as conn: + cursor = conn.cursor() + cursor.execute('SELECT COUNT(*) FROM logs') + total_logs = cursor.fetchone()[0] + + cursor.execute('SELECT COUNT(DISTINCT hostname) FROM logs') + unique_devices = cursor.fetchone()[0] + + cursor.execute('SELECT MIN(timestamp), MAX(timestamp) FROM logs') + date_range = cursor.fetchone() + + return jsonify({ + "success": True, + "total_logs": total_logs, + "unique_devices": unique_devices, + "earliest_log": date_range[0], + "latest_log": date_range[1] + }), 200 + + except sqlite3.Error as e: + return jsonify({ + "success": False, + "error": f"Database error: {str(e)}" + }), 500 + except Exception as e: + return jsonify({ + "success": False, + "error": f"Unexpected error: {str(e)}" + }), 500 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=80) \ No newline at end of file diff --git a/templates/dashboard.html b/templates/dashboard.html new file mode 100644 index 0000000..6897bc8 --- /dev/null +++ b/templates/dashboard.html @@ -0,0 +1,210 @@ + + + + + + Device Logs Dashboard + + + + + + +
+

Device Logs Dashboard

+
+
+ Time until refresh: 30 seconds +
+ +
+
+ + + + + + + + + + + + {% for log in logs %} + + + + + + + + {% endfor %} + +
HostnameDevice IPNume MasaTimestampEvent Description
{{ log[0] }}{{ log[1] }}{{ log[2] }}{{ log[3] }}{{ log[4] }}
+
+
+
+

© 2023 Device Logs Dashboard. All rights reserved.

+
+ + \ No newline at end of file diff --git a/templates/device_logs.html b/templates/device_logs.html new file mode 100644 index 0000000..20552e7 --- /dev/null +++ b/templates/device_logs.html @@ -0,0 +1,89 @@ + + + + + + Logs for Device: {{ nume_masa }} + + + + +
+

Logs for Device: {{ nume_masa }}

+ +
+ + + + + + + + + + + {% if logs %} + {% for log in logs %} + + + + + + + {% endfor %} + {% else %} + + + + {% endif %} + +
Device IDNume MasaTimestampEvent Description
{{ log[0] }}{{ log[1] }}{{ log[2] }}{{ log[3] }}
No logs found for this device.
+
+
+
+

© 2023 Device Logs Dashboard. All rights reserved.

+
+ + \ No newline at end of file diff --git a/templates/device_management.html b/templates/device_management.html new file mode 100644 index 0000000..14d5d07 --- /dev/null +++ b/templates/device_management.html @@ -0,0 +1,505 @@ + + + + + + Device Management + + + + + +
+

Device Management

+ + + + +
+
+ +
+
+ + +
+
+
Bulk Operations
+
+
+
+
+ + +
+
+ +
+ + + + +
+
+
+
+
+
+ + +
+ {% for device in devices %} +
+
+
+
+
+ + {{ device[0] }} ({{ device[1] }}) +
+
+
+ Last seen: {{ device[2] }} +
+
+ Checking... + +
+
+
+
+
+
+ +
+
+ + +
+
+ +
+
+
+ Loading... +
+ Executing command... +
+
+
+ {% endfor %} +
+
+ + + + + diff --git a/templates/hostname_logs.html b/templates/hostname_logs.html new file mode 100644 index 0000000..c47f813 --- /dev/null +++ b/templates/hostname_logs.html @@ -0,0 +1,89 @@ + + + + + + Logs for Hostname: {{ hostname }} + + + + +
+

Logs for Hostname: {{ hostname }}

+ +
+ + + + + + + + + + + {% if logs %} + {% for log in logs %} + + + + + + + {% endfor %} + {% else %} + + + + {% endif %} + +
Device IDNume MasaTimestampEvent Description
{{ log[0] }}{{ log[1] }}{{ log[2] }}{{ log[3] }}
No logs found for this hostname.
+
+
+
+

© 2023 Device Logs Dashboard. All rights reserved.

+
+ + \ No newline at end of file diff --git a/templates/server_logs.html b/templates/server_logs.html new file mode 100644 index 0000000..33d6edf --- /dev/null +++ b/templates/server_logs.html @@ -0,0 +1,230 @@ + + + + + + Server Logs - System Operations + + + + + + +
+
+

+ Server Operations Log +

+

System operations, auto-updates, database resets, and server commands

+
+ +
+
+ Auto-refresh: 30 seconds +
+ +
+ +
+ {% if logs %} + +
+
+
+
{{ logs|length }}
+ Total Operations +
+
+
+ {% set system_ops = logs|selectattr('2', 'equalto', 'SYSTEM')|list|length %} + {{ system_ops }} +
+ System Operations +
+
+
+ {% set auto_updates = 0 %} + {% for log in logs %} + {% if 'auto' in log[4]|lower or 'update' in log[4]|lower %} + {% set auto_updates = auto_updates + 1 %} + {% endif %} + {% endfor %} + {{ auto_updates }} +
+ Auto-Updates +
+
+
+ {% set resets = 0 %} + {% for log in logs %} + {% if 'reset' in log[4]|lower or 'clear' in log[4]|lower %} + {% set resets = resets + 1 %} + {% endif %} + {% endfor %} + {{ resets }} +
+ Database Resets +
+
+
+ + + + + + + + + + + + + {% for log in logs %} + + + + + + + + {% endfor %} + +
Source IP Address Operation Timestamp Description
{{ log[0] }}{{ log[1] }}{{ log[2] }}{{ log[3] }}{{ log[4] }}
+ {% else %} +
+ +

No Server Operations Found

+

No server operations have been logged yet.

+ + Back to Dashboard + +
+ {% endif %} +
+
+ +
+

© 2025 Server Operations Dashboard. All rights reserved.

+
+ + diff --git a/templates/unique_devices.html b/templates/unique_devices.html new file mode 100644 index 0000000..9953587 --- /dev/null +++ b/templates/unique_devices.html @@ -0,0 +1,157 @@ + + + + + + Unique Devices + + + + +
+

Unique Devices

+ + + +
+
+ +
+
+ +
+ + + + + + + + + + + {% for device in devices %} + + + + + + + + {% endfor %} + +
HostnameDevice IPLast LogEvent Description
+ {{ device[0] }} + {{ device[1] }} {{ device[2] }} {{ device[3] }}
+
+ No devices found matching your search criteria. +
+
+
+ + + + + + + \ No newline at end of file