Update AppImage

This commit is contained in:
MacRimi
2025-11-13 16:58:45 +01:00
parent 3e0ae709d9
commit 1d47ad0c4b
3 changed files with 129 additions and 4 deletions

View File

@@ -80,6 +80,7 @@ echo "📋 Copying Flask server..."
cp "$SCRIPT_DIR/flask_server.py" "$APP_DIR/usr/bin/" cp "$SCRIPT_DIR/flask_server.py" "$APP_DIR/usr/bin/"
cp "$SCRIPT_DIR/flask_auth_routes.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ flask_auth_routes.py not found" cp "$SCRIPT_DIR/flask_auth_routes.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ flask_auth_routes.py not found"
cp "$SCRIPT_DIR/auth_manager.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ auth_manager.py not found" cp "$SCRIPT_DIR/auth_manager.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ auth_manager.py not found"
cp "$SCRIPT_DIR/jwt_middleware.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ jwt_middleware.py not found"
cp "$SCRIPT_DIR/health_monitor.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ health_monitor.py not found" cp "$SCRIPT_DIR/health_monitor.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ health_monitor.py not found"
cp "$SCRIPT_DIR/health_persistence.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ health_persistence.py not found" cp "$SCRIPT_DIR/health_persistence.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ health_persistence.py not found"
cp "$SCRIPT_DIR/flask_health_routes.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ flask_health_routes.py not found" cp "$SCRIPT_DIR/flask_health_routes.py" "$APP_DIR/usr/bin/" 2>/dev/null || echo "⚠️ flask_health_routes.py not found"

View File

@@ -35,6 +35,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from flask_auth_routes import auth_bp from flask_auth_routes import auth_bp
from flask_proxmenux_routes import proxmenux_bp from flask_proxmenux_routes import proxmenux_bp
from jwt_middleware import require_auth
app = Flask(__name__) app = Flask(__name__)
CORS(app) # Enable CORS for Next.js frontend CORS(app) # Enable CORS for Next.js frontend
@@ -1740,6 +1741,7 @@ def get_proxmox_storage():
# END OF CHANGES FOR get_proxmox_storage # END OF CHANGES FOR get_proxmox_storage
@app.route('/api/storage/summary', methods=['GET']) @app.route('/api/storage/summary', methods=['GET'])
@require_auth
def api_storage_summary(): def api_storage_summary():
"""Get storage summary without SMART data (optimized for Overview page)""" """Get storage summary without SMART data (optimized for Overview page)"""
try: try:
@@ -4519,6 +4521,7 @@ def get_hardware_info():
@app.route('/api/system', methods=['GET']) @app.route('/api/system', methods=['GET'])
@require_auth
def api_system(): def api_system():
"""Get system information including CPU, memory, and temperature""" """Get system information including CPU, memory, and temperature"""
try: try:
@@ -4575,21 +4578,25 @@ def api_system():
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/storage', methods=['GET']) @app.route('/api/storage', methods=['GET'])
@require_auth
def api_storage(): def api_storage():
"""Get storage information""" """Get storage information"""
return jsonify(get_storage_info()) return jsonify(get_storage_info())
@app.route('/api/proxmox-storage', methods=['GET']) @app.route('/api/proxmox-storage', methods=['GET'])
@require_auth
def api_proxmox_storage(): def api_proxmox_storage():
"""Get Proxmox storage information""" """Get Proxmox storage information"""
return jsonify(get_proxmox_storage()) return jsonify(get_proxmox_storage())
@app.route('/api/network', methods=['GET']) @app.route('/api/network', methods=['GET'])
@require_auth
def api_network(): def api_network():
"""Get network information""" """Get network information"""
return jsonify(get_network_info()) return jsonify(get_network_info())
@app.route('/api/network/summary', methods=['GET']) @app.route('/api/network/summary', methods=['GET'])
@require_auth
def api_network_summary(): def api_network_summary():
"""Optimized network summary endpoint - returns basic network info without detailed analysis""" """Optimized network summary endpoint - returns basic network info without detailed analysis"""
try: try:
@@ -4668,6 +4675,7 @@ def api_network_summary():
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/network/<interface_name>/metrics', methods=['GET']) @app.route('/api/network/<interface_name>/metrics', methods=['GET'])
@require_auth
def api_network_interface_metrics(interface_name): def api_network_interface_metrics(interface_name):
"""Get historical metrics (RRD data) for a specific network interface""" """Get historical metrics (RRD data) for a specific network interface"""
try: try:
@@ -4750,12 +4758,13 @@ def api_network_interface_metrics(interface_name):
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/vms', methods=['GET']) @app.route('/api/vms', methods=['GET'])
@require_auth
def api_vms(): def api_vms():
"""Get virtual machine information""" """Get virtual machine information"""
return jsonify(get_proxmox_vms()) return jsonify(get_proxmox_vms())
# Add the new api_vm_metrics endpoint here
@app.route('/api/vms/<int:vmid>/metrics', methods=['GET']) @app.route('/api/vms/<int:vmid>/metrics', methods=['GET'])
@require_auth
def api_vm_metrics(vmid): def api_vm_metrics(vmid):
"""Get historical metrics (RRD data) for a specific VM/LXC""" """Get historical metrics (RRD data) for a specific VM/LXC"""
try: try:
@@ -4822,6 +4831,7 @@ def api_vm_metrics(vmid):
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/node/metrics', methods=['GET']) @app.route('/api/node/metrics', methods=['GET'])
@require_auth
def api_node_metrics(): def api_node_metrics():
"""Get historical metrics (RRD data) for the node""" """Get historical metrics (RRD data) for the node"""
try: try:
@@ -4865,6 +4875,7 @@ def api_node_metrics():
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/logs', methods=['GET']) @app.route('/api/logs', methods=['GET'])
@require_auth
def api_logs(): def api_logs():
"""Get system logs""" """Get system logs"""
try: try:
@@ -4942,6 +4953,7 @@ def api_logs():
}) })
@app.route('/api/logs/download', methods=['GET']) @app.route('/api/logs/download', methods=['GET'])
@require_auth
def api_logs_download(): def api_logs_download():
"""Download system logs as a text file""" """Download system logs as a text file"""
try: try:
@@ -5000,6 +5012,7 @@ def api_logs_download():
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/notifications', methods=['GET']) @app.route('/api/notifications', methods=['GET'])
@require_auth
def api_notifications(): def api_notifications():
"""Get Proxmox notification history""" """Get Proxmox notification history"""
try: try:
@@ -5116,6 +5129,7 @@ def api_notifications():
}) })
@app.route('/api/notifications/download', methods=['GET']) @app.route('/api/notifications/download', methods=['GET'])
@require_auth
def api_notifications_download(): def api_notifications_download():
"""Download complete log for a specific notification""" """Download complete log for a specific notification"""
try: try:
@@ -5171,6 +5185,7 @@ def api_notifications_download():
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/backups', methods=['GET']) @app.route('/api/backups', methods=['GET'])
@require_auth
def api_backups(): def api_backups():
"""Get list of all backup files from Proxmox storage""" """Get list of all backup files from Proxmox storage"""
try: try:
@@ -5259,6 +5274,7 @@ def api_backups():
}) })
@app.route('/api/events', methods=['GET']) @app.route('/api/events', methods=['GET'])
@require_auth
def api_events(): def api_events():
"""Get recent Proxmox events and tasks""" """Get recent Proxmox events and tasks"""
try: try:
@@ -5335,6 +5351,7 @@ def api_events():
}) })
@app.route('/api/task-log/<path:upid>') @app.route('/api/task-log/<path:upid>')
@require_auth
def get_task_log(upid): def get_task_log(upid):
"""Get complete task log from Proxmox using UPID""" """Get complete task log from Proxmox using UPID"""
try: try:
@@ -5432,6 +5449,7 @@ def get_task_log(upid):
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/health', methods=['GET']) @app.route('/api/health', methods=['GET'])
@require_auth
def api_health(): def api_health():
"""Health check endpoint""" """Health check endpoint"""
return jsonify({ return jsonify({
@@ -5441,6 +5459,7 @@ def api_health():
}) })
@app.route('/api/prometheus', methods=['GET']) @app.route('/api/prometheus', methods=['GET'])
@require_auth
def api_prometheus(): def api_prometheus():
"""Export metrics in Prometheus format""" """Export metrics in Prometheus format"""
try: try:
@@ -5697,11 +5716,12 @@ def api_prometheus():
@app.route('/api/info', methods=['GET']) @app.route('/api/info', methods=['GET'])
@require_auth
def api_info(): def api_info():
"""Root endpoint with API information""" """Root endpoint with API information"""
return jsonify({ return jsonify({
'name': 'ProxMenux Monitor API', 'name': 'ProxMenux Monitor API',
'version': '1.0.0', 'version': '1.0.1',
'endpoints': [ 'endpoints': [
'/api/system', '/api/system',
'/api/system-info', '/api/system-info',
@@ -5725,6 +5745,7 @@ def api_info():
}) })
@app.route('/api/hardware', methods=['GET']) @app.route('/api/hardware', methods=['GET'])
@require_auth
def api_hardware(): def api_hardware():
"""Get hardware information""" """Get hardware information"""
try: try:
@@ -5761,6 +5782,7 @@ def api_hardware():
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/gpu/<slot>/realtime', methods=['GET']) @app.route('/api/gpu/<slot>/realtime', methods=['GET'])
@require_auth
def api_gpu_realtime(slot): def api_gpu_realtime(slot):
"""Get real-time GPU monitoring data for a specific GPU""" """Get real-time GPU monitoring data for a specific GPU"""
try: try:
@@ -5823,6 +5845,7 @@ def api_gpu_realtime(slot):
# CHANGE: Modificar el endpoint para incluir la información completa de IPs # CHANGE: Modificar el endpoint para incluir la información completa de IPs
@app.route('/api/vms/<int:vmid>', methods=['GET']) @app.route('/api/vms/<int:vmid>', methods=['GET'])
@require_auth
def get_vm_config(vmid): def get_vm_config(vmid):
"""Get detailed configuration for a specific VM/LXC""" """Get detailed configuration for a specific VM/LXC"""
try: try:
@@ -5919,6 +5942,7 @@ def get_vm_config(vmid):
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/vms/<int:vmid>/logs', methods=['GET']) @app.route('/api/vms/<int:vmid>/logs', methods=['GET'])
@require_auth
def api_vm_logs(vmid): def api_vm_logs(vmid):
"""Download real logs for a specific VM/LXC (not task history)""" """Download real logs for a specific VM/LXC (not task history)"""
try: try:
@@ -5968,6 +5992,7 @@ def api_vm_logs(vmid):
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/vms/<int:vmid>/control', methods=['POST']) @app.route('/api/vms/<int:vmid>/control', methods=['POST'])
@require_auth
def api_vm_control(vmid): def api_vm_control(vmid):
"""Control VM/LXC (start, stop, shutdown, reboot)""" """Control VM/LXC (start, stop, shutdown, reboot)"""
try: try:
@@ -6020,6 +6045,7 @@ def api_vm_control(vmid):
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/vms/<int:vmid>/config', methods=['PUT']) @app.route('/api/vms/<int:vmid>/config', methods=['PUT'])
@require_auth
def api_vm_config_update(vmid): def api_vm_config_update(vmid):
"""Update VM/LXC configuration (description/notes)""" """Update VM/LXC configuration (description/notes)"""
try: try:

View File

@@ -0,0 +1,98 @@
"""
JWT Middleware Module
Provides decorator to protect Flask routes with JWT authentication
Automatically checks auth status and validates tokens
"""
from flask import request, jsonify
from functools import wraps
from auth_manager import load_auth_config, verify_token
def require_auth(f):
"""
Decorator to protect Flask routes with JWT authentication
Behavior:
- If auth is disabled or declined: Allow access (no token required)
- If auth is enabled: Require valid JWT token in Authorization header
- Returns 401 if auth required but token missing/invalid
Usage:
@app.route('/api/protected')
@require_auth
def protected_route():
return jsonify({"data": "secret"})
"""
@wraps(f)
def decorated_function(*args, **kwargs):
# Check if authentication is enabled
config = load_auth_config()
# If auth is disabled or declined, allow access
if not config.get("enabled", False) or config.get("declined", False):
return f(*args, **kwargs)
# Auth is enabled, require token
auth_header = request.headers.get('Authorization')
if not auth_header:
return jsonify({
"error": "Authentication required",
"message": "No authorization header provided"
}), 401
# Extract token from "Bearer <token>" format
parts = auth_header.split()
if len(parts) != 2 or parts[0].lower() != 'bearer':
return jsonify({
"error": "Invalid authorization header",
"message": "Authorization header must be in format: Bearer <token>"
}), 401
token = parts[1]
# Verify token
username = verify_token(token)
if not username:
return jsonify({
"error": "Invalid or expired token",
"message": "Please log in again"
}), 401
# Token is valid, allow access
return f(*args, **kwargs)
return decorated_function
def optional_auth(f):
"""
Decorator for routes that can optionally use auth
Passes username if authenticated, None otherwise
Usage:
@app.route('/api/optional')
@optional_auth
def optional_route(username=None):
if username:
return jsonify({"message": f"Hello {username}"})
return jsonify({"message": "Hello guest"})
"""
@wraps(f)
def decorated_function(*args, **kwargs):
config = load_auth_config()
username = None
if config.get("enabled", False):
auth_header = request.headers.get('Authorization')
if auth_header:
parts = auth_header.split()
if len(parts) == 2 and parts[0].lower() == 'bearer':
username = verify_token(parts[1])
# Inject username into kwargs
kwargs['username'] = username
return f(*args, **kwargs)
return decorated_function