Extra features

This commit is contained in:
Aodhan Collins
2025-07-27 01:58:39 +01:00
parent ab949897f5
commit cf761decd8
2 changed files with 185 additions and 11 deletions

155
api.py
View File

@@ -8,6 +8,8 @@ import json
import subprocess import subprocess
import time import time
import logging import logging
import platform
import glob
from datetime import datetime from datetime import datetime
from flask import Flask, jsonify, render_template, request, send_from_directory from flask import Flask, jsonify, render_template, request, send_from_directory
from flask_cors import CORS from flask_cors import CORS
@@ -136,28 +138,109 @@ class DockerService:
def get_system_info(self): def get_system_info(self):
"""Get system information""" """Get system information"""
try: try:
# CPU usage
cpu_percent = psutil.cpu_percent(interval=1)
# Memory usage
memory = psutil.virtual_memory() 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 { return {
'cpu_percent': psutil.cpu_percent(), 'cpu': {
'percent': cpu_percent,
'cores': psutil.cpu_count(),
'load_avg': load_avg
},
'memory': { 'memory': {
'total': memory.total, 'total': memory.total,
'available': memory.available, 'available': memory.available,
'used': memory.used,
'percent': memory.percent, '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': { 'disk': {
'total': disk.total, 'total': disk_info.total,
'used': disk.used, 'used': disk_info.used,
'free': disk.free, 'free': disk_info.free,
'percent': (disk.used / disk.total) * 100 '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: except Exception as e:
logging.error(f"Error getting system info: {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): def _get_system_uptime(self):
"""Get system uptime""" """Get system uptime"""
@@ -388,5 +471,59 @@ def sync_applications():
except Exception as e: except Exception as e:
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@app.route('/api/containers/<container_id>/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/<container_id>/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/<container_id>/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__': if __name__ == '__main__':
app.run(host=HOST, port=PORT, debug=DEBUG) app.run(host=HOST, port=PORT, debug=DEBUG)

View File

@@ -223,11 +223,23 @@
<span class="text-white">${app.uptime || 'N/A'}</span> <span class="text-white">${app.uptime || 'N/A'}</span>
</div> </div>
<div class="flex space-x-2"> <div class="flex flex-col space-y-2">
${app.url ? `<a href="${app.url}" target="_blank" class="flex-1 bg-indigo-600 hover:bg-indigo-700 text-white text-sm py-1 px-3 rounded text-center"> ${app.url ? `<a href="${app.url}" target="_blank" class="bg-indigo-600 hover:bg-indigo-700 text-white text-sm py-1 px-3 rounded text-center">
<i class="fas fa-external-link-alt mr-1"></i>Open <i class="fas fa-external-link-alt mr-1"></i>Open
</a>` : ''} </a>` : ''}
<div class="grid grid-cols-3 gap-1">
<button onclick="manageContainer('${app.container_id}', 'start')" class="bg-green-600 hover:bg-green-700 text-white text-xs py-1 px-2 rounded" ${app.status === 'running' ? 'disabled' : ''}>
<i class="fas fa-play"></i>
</button>
<button onclick="manageContainer('${app.container_id}', 'stop')" class="bg-red-600 hover:bg-red-700 text-white text-xs py-1 px-2 rounded" ${app.status !== 'running' ? 'disabled' : ''}>
<i class="fas fa-stop"></i>
</button>
<button onclick="manageContainer('${app.container_id}', 'restart')" class="bg-yellow-600 hover:bg-yellow-700 text-white text-xs py-1 px-2 rounded">
<i class="fas fa-redo"></i>
</button>
</div>
<button onclick="editApplication('${app.container_id}')" class="bg-gray-600 hover:bg-gray-500 text-white text-sm py-1 px-3 rounded"> <button onclick="editApplication('${app.container_id}')" class="bg-gray-600 hover:bg-gray-500 text-white text-sm py-1 px-3 rounded">
<i class="fas fa-edit mr-1"></i>Edit <i class="fas fa-edit mr-1"></i>Edit
</button> </button>
@@ -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 // Sync applications with containers
async function syncApplications() { async function syncApplications() {
try { try {