diff --git a/api.py b/api.py index 83405b0..dd5a0d1 100644 --- a/api.py +++ b/api.py @@ -8,6 +8,8 @@ import json import subprocess import time import logging +import platform +import glob from datetime import datetime from flask import Flask, jsonify, render_template, request, send_from_directory from flask_cors import CORS @@ -136,28 +138,109 @@ class DockerService: def get_system_info(self): """Get system information""" try: + # CPU usage + cpu_percent = psutil.cpu_percent(interval=1) + + # Memory usage memory = psutil.virtual_memory() - disk = psutil.disk_usage('/') + + # Disk usage - try multiple paths for different systems + disk_info = None + disk_paths = ['/'] + + # Add common mount points for different Linux distributions + if os.path.exists('/home'): + disk_paths.append('/home') + if os.path.exists('/var'): + disk_paths.append('/var') + if os.path.exists('/opt'): + disk_paths.append('/opt') + + # Try each path until we find one that works + for path in disk_paths: + try: + disk_info = psutil.disk_usage(path) + break + except (OSError, IOError, PermissionError): + continue + + # Fallback to root directory + if disk_info is None: + try: + disk_info = psutil.disk_usage('/') + except (OSError, IOError) as e: + logging.error(f"Cannot get disk usage: {e}") + # Return zero values as fallback + disk_info = type('DiskInfo', (), { + 'total': 0, + 'used': 0, + 'free': 0 + })() + + # Get load average (works on both Debian and Fedora) + try: + import os + load_avg = os.getloadavg() + except (AttributeError, OSError): + load_avg = (0.0, 0.0, 0.0) + + # Get swap memory + swap = psutil.swap_memory() + + # Format disk usage for display + def format_bytes(bytes_value): + """Format bytes to human readable format""" + for unit in ['B', 'KB', 'MB', 'GB', 'TB']: + if bytes_value < 1024.0: + return f"{bytes_value:.1f} {unit}" + bytes_value /= 1024.0 + return f"{bytes_value:.1f} PB" return { - 'cpu_percent': psutil.cpu_percent(), + 'cpu': { + 'percent': cpu_percent, + 'cores': psutil.cpu_count(), + 'load_avg': load_avg + }, 'memory': { 'total': memory.total, 'available': memory.available, + 'used': memory.used, 'percent': memory.percent, - 'used': memory.used + 'formatted_total': format_bytes(memory.total), + 'formatted_used': format_bytes(memory.used), + 'formatted_available': format_bytes(memory.available) + }, + 'swap': { + 'total': swap.total, + 'used': swap.used, + 'percent': swap.percent }, 'disk': { - 'total': disk.total, - 'used': disk.used, - 'free': disk.free, - 'percent': (disk.used / disk.total) * 100 + 'total': disk_info.total, + 'used': disk_info.used, + 'free': disk_info.free, + 'percent': (disk_info.used / disk_info.total) * 100 if disk_info.total > 0 else 0, + 'formatted_total': format_bytes(disk_info.total), + 'formatted_used': format_bytes(disk_info.used), + 'formatted_free': format_bytes(disk_info.free) }, - 'uptime': self._get_system_uptime() + 'uptime': self._get_system_uptime(), + 'platform': { + 'system': platform.system(), + 'release': platform.release(), + 'machine': platform.machine() + } } except Exception as e: logging.error(f"Error getting system info: {e}") - return {} + return { + 'error': str(e), + 'cpu': {'percent': 0, 'cores': 1}, + 'memory': {'total': 0, 'available': 0, 'used': 0, 'percent': 0}, + 'disk': {'total': 0, 'used': 0, 'free': 0, 'percent': 0}, + 'uptime': 'Unknown' + } def _get_system_uptime(self): """Get system uptime""" @@ -388,5 +471,59 @@ def sync_applications(): except Exception as e: return jsonify({'error': str(e)}), 500 +@app.route('/api/containers//start', methods=['POST']) +def start_container(container_id): + """Start a specific container""" + try: + if docker_service.client is None: + return jsonify({'success': False, 'error': 'Docker client not available'}), 500 + + container = docker_service.client.containers.get(container_id) + container.start() + + # Update database with new status + db.update_application(container_id, {'status': 'running'}) + + return jsonify({'success': True, 'message': 'Container started successfully'}) + except Exception as e: + logging.error(f"Error starting container {container_id}: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + +@app.route('/api/containers//stop', methods=['POST']) +def stop_container(container_id): + """Stop a specific container""" + try: + if docker_service.client is None: + return jsonify({'success': False, 'error': 'Docker client not available'}), 500 + + container = docker_service.client.containers.get(container_id) + container.stop() + + # Update database with new status + db.update_application(container_id, {'status': 'exited'}) + + return jsonify({'success': True, 'message': 'Container stopped successfully'}) + except Exception as e: + logging.error(f"Error stopping container {container_id}: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + +@app.route('/api/containers//restart', methods=['POST']) +def restart_container(container_id): + """Restart a specific container""" + try: + if docker_service.client is None: + return jsonify({'success': False, 'error': 'Docker client not available'}), 500 + + container = docker_service.client.containers.get(container_id) + container.restart() + + # Update database with new status + db.update_application(container_id, {'status': 'running'}) + + return jsonify({'success': True, 'message': 'Container restarted successfully'}) + except Exception as e: + logging.error(f"Error restarting container {container_id}: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + if __name__ == '__main__': app.run(host=HOST, port=PORT, debug=DEBUG) diff --git a/template.html b/template.html index 0c1c63f..aebe2db 100644 --- a/template.html +++ b/template.html @@ -223,11 +223,23 @@ ${app.uptime || 'N/A'} -
- ${app.url ? ` +
+ ${app.url ? ` Open ` : ''} +
+ + + +
+ @@ -309,6 +321,31 @@ } } + // Manage container (start/stop/restart) + async function manageContainer(containerId, action) { + try { + showNotification(`${action.charAt(0).toUpperCase() + action.slice(1)}ing container...`, 'info'); + + const response = await fetch(`${API_BASE}/api/containers/${containerId}/${action}`, { + method: 'POST' + }); + const result = await response.json(); + + if (result.success) { + showNotification(`Container ${action}ed successfully`, 'success'); + // Refresh the applications to show updated status + setTimeout(() => { + loadApplications(); + }, 2000); + } else { + showNotification(`Failed to ${action} container: ${result.error}`, 'error'); + } + } catch (error) { + console.error(`Error ${action}ing container:`, error); + showNotification(`Error ${action}ing container`, 'error'); + } + } + // Sync applications with containers async function syncApplications() { try {