Files
Server_Monitorizare/server.py
Developer 2f05360d69 Fix: Initialize logs table on startup
- Add init_logs_table() function to create logs table schema
- Call init_logs_table() on application startup
- Fixes 'no such table: logs' error when accessing dashboard
- Tables now created automatically before routes are accessed
2025-12-18 15:46:56 +02:00

581 lines
21 KiB
Python

from flask import Flask, request, render_template, jsonify, redirect, url_for, session
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash
import sqlite3
from datetime import datetime
from urllib.parse import unquote
import requests
import threading
app = Flask(__name__)
app.secret_key = 'your-secret-key-change-this' # Change this to a random secret key
DATABASE = 'data/database.db' # Updated path for the database
# Initialize Flask-Login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
# User class for Flask-Login
class User(UserMixin):
def __init__(self, id, username):
self.id = id
self.username = username
@login_manager.user_loader
def load_user(user_id):
with sqlite3.connect(DATABASE) as conn:
cursor = conn.cursor()
cursor.execute('SELECT id, username FROM users WHERE id = ?', (user_id,))
user = cursor.fetchone()
if user:
return User(user[0], user[1])
return None
# Initialize users table if it doesn't exist
def init_users_table():
with sqlite3.connect(DATABASE) as conn:
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
created_at TEXT NOT NULL
)
''')
conn.commit()
# Create default admin user if no users exist
cursor.execute('SELECT COUNT(*) FROM users')
if cursor.fetchone()[0] == 0:
admin_password = generate_password_hash('admin123')
cursor.execute('''
INSERT INTO users (username, password, created_at)
VALUES (?, ?, ?)
''', ('admin', admin_password, datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
conn.commit()
print("Default admin user created - username: admin, password: admin123")
def init_logs_table():
"""Initialize the logs table if it doesn't exist"""
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("Logs table initialized")
# Login route
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if not username or not password:
return render_template('login.html', error='Username and password are required'), 400
with sqlite3.connect(DATABASE) as conn:
cursor = conn.cursor()
cursor.execute('SELECT id, username, password FROM users WHERE username = ?', (username,))
user_data = cursor.fetchone()
if user_data and check_password_hash(user_data[2], password):
user = User(user_data[0], user_data[1])
login_user(user)
return redirect(url_for('dashboard'))
else:
return render_template('login.html', error='Invalid username or password'), 401
return render_template('login.html')
# Logout route
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
# Redirect root to dashboard
@app.route('/')
def index():
if current_user.is_authenticated:
return redirect(url_for('dashboard'))
return redirect(url_for('login'))
# 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'])
@login_required
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'])
@login_required
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'])
@login_required
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'])
@login_required
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'])
@login_required
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'])
@login_required
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'])
@login_required
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'])
@login_required
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'])
@login_required
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'])
@login_required
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'])
@login_required
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'])
@login_required
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__':
init_users_table()
init_logs_table()
app.run(host='0.0.0.0', port=80)