Extra features
This commit is contained in:
155
api.py
155
api.py
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user