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
This commit is contained in:
462
server.py
Normal file
462
server.py
Normal file
@@ -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/<nume_masa>', 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/<hostname>', 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/<device_ip>', 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)
|
||||
Reference in New Issue
Block a user