Files
Homepage/api.py
Aodhan Collins ab949897f5 Template fix
2025-07-27 01:50:46 +01:00

393 lines
13 KiB
Python

#!/usr/bin/env python3
"""
Docker Dashboard API
Fetches container data from Docker instances and provides endpoints for the frontend
"""
import json
import subprocess
import time
import logging
from datetime import datetime
from flask import Flask, jsonify, render_template, request, send_from_directory
from flask_cors import CORS
import docker
import psutil
import requests
import os
from dotenv import load_dotenv
from portainer_integration import PortainerClient
from database import db
# Load environment variables
load_dotenv()
app = Flask(__name__)
CORS(app)
class DockerService:
def __init__(self):
try:
self.client = docker.from_env()
print("Docker client initialized successfully")
except Exception as e:
logging.error(f"Failed to initialize Docker client: {e}")
self.client = None
def get_containers(self):
"""Get all containers with their status"""
if not self.client:
return []
containers = []
try:
for container in self.client.containers.list(all=True):
container_data = {
'id': container.id[:12],
'name': container.name,
'status': container.status,
'image': container.image.tags[0] if container.image.tags else container.image.short_id,
'ports': self._get_container_ports(container),
'created': container.attrs['Created'],
'uptime': self._calculate_uptime(container),
'labels': container.labels,
'environment': container.attrs['Config'].get('Env', []),
'mounts': self._get_mounts(container),
'networks': self._get_networks(container)
}
containers.append(container_data)
except Exception as e:
logging.error(f"Error fetching containers: {e}")
return []
return containers
def _get_container_ports(self, container):
"""Extract port mappings from container"""
ports = []
try:
port_bindings = container.attrs['NetworkSettings']['Ports']
for container_port, host_configs in port_bindings.items():
if host_configs:
for host_config in host_configs:
ports.append({
'container_port': container_port,
'host_ip': host_config.get('HostIp', '0.0.0.0'),
'host_port': host_config.get('HostPort')
})
except Exception:
pass
return ports
def _calculate_uptime(self, container):
"""Calculate container uptime"""
if container.status != 'running':
return "Down"
try:
started_at = container.attrs['State']['StartedAt']
started_time = datetime.fromisoformat(started_at.replace('Z', '+00:00'))
uptime = datetime.now(started_time.tzinfo) - started_time
days = uptime.days
hours, remainder = divmod(uptime.seconds, 3600)
minutes, seconds = divmod(remainder, 60)
if days > 0:
return f"{days}d {hours}h"
elif hours > 0:
return f"{hours}h {minutes}m"
else:
return f"{minutes}m"
except Exception:
return "Unknown"
def _get_mounts(self, container):
"""Get container volume mounts"""
mounts = []
try:
for mount in container.attrs['Mounts']:
mounts.append({
'type': mount.get('Type'),
'source': mount.get('Source'),
'destination': mount.get('Destination'),
'mode': mount.get('Mode')
})
except Exception:
pass
return mounts
def _get_networks(self, container):
"""Get container network information"""
networks = []
try:
networks_config = container.attrs['NetworkSettings']['Networks']
for network_name, network_config in networks_config.items():
networks.append({
'name': network_name,
'ip_address': network_config.get('IPAddress'),
'gateway': network_config.get('Gateway'),
'mac_address': network_config.get('MacAddress')
})
except Exception:
pass
return networks
def get_system_info(self):
"""Get system information"""
try:
memory = psutil.virtual_memory()
disk = psutil.disk_usage('/')
return {
'cpu_percent': psutil.cpu_percent(),
'memory': {
'total': memory.total,
'available': memory.available,
'percent': memory.percent,
'used': memory.used
},
'disk': {
'total': disk.total,
'used': disk.used,
'free': disk.free,
'percent': (disk.used / disk.total) * 100
},
'uptime': self._get_system_uptime()
}
except Exception as e:
logging.error(f"Error getting system info: {e}")
return {}
def _get_system_uptime(self):
"""Get system uptime"""
try:
with open('/proc/uptime', 'r') as f:
uptime_seconds = float(f.readline().split()[0])
days = int(uptime_seconds // 86400)
hours = int((uptime_seconds % 86400) // 3600)
minutes = int((uptime_seconds % 3600) // 60)
return f"{days}d {hours}h {minutes}m"
except Exception:
return "Unknown"
def check_service_health(self, url, port=None):
"""Check if a service is responding"""
try:
if port:
url = f"http://{url}:{port}"
response = requests.get(url, timeout=5)
return response.status_code == 200
except Exception:
return False
# Configuration from environment variables
PORT = int(os.getenv('PORT', 5000))
HOST = os.getenv('HOST', '0.0.0.0')
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
REFRESH_INTERVAL = int(os.getenv('REFRESH_INTERVAL', 30))
# Initialize Docker service
docker_service = DockerService()
# Initialize Portainer client
portainer_client = None
portainer_url = os.getenv('PORTAINER_URL', 'http://localhost:9000')
portainer_username = os.getenv('PORTAINER_USERNAME')
portainer_password = os.getenv('PORTAINER_PASSWORD')
if portainer_username and portainer_password:
portainer_client = PortainerClient(portainer_url, portainer_username, portainer_password)
if portainer_client.authenticate():
logging.info("Portainer client authenticated successfully")
else:
logging.error("Portainer authentication failed - using standard Docker API")
portainer_client = None
@app.route('/')
def dashboard():
"""Serve the dashboard"""
return send_from_directory(os.path.dirname(os.path.abspath(__file__)), 'template.html')
@app.route('/api/containers')
def get_containers():
"""Get all containers"""
containers = docker_service.get_containers()
return jsonify(containers)
@app.route('/api/system')
def get_system_info():
"""Get system information"""
system_info = docker_service.get_system_info()
return jsonify(system_info)
@app.route('/api/health/<container_name>')
def check_container_health(container_name):
"""Check health of a specific container"""
try:
# Get application from database for custom health check URL
app = db.get_application(container_name)
if app and app.get('url') != '#':
url = app['url']
else:
url = f'http://{container_name}.local'
try:
response = requests.get(url, timeout=5)
return jsonify({
'healthy': response.status_code < 400,
'status_code': response.status_code,
'url': url
})
except requests.RequestException:
return jsonify({
'healthy': False,
'error': 'Service not responding',
'url': url
})
except Exception as e:
return jsonify({
'healthy': False,
'error': str(e)
}), 500
@app.route('/api/portainer/containers')
def get_portainer_containers():
"""Get containers from Portainer if available"""
try:
# This would need Portainer API configuration
# For now, return standard Docker containers
return get_containers()
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/portainer/containers')
def get_portainer_containers_list():
"""Get containers from Portainer"""
if not portainer_client:
return jsonify({'error': 'Portainer not configured'}), 503
try:
containers = portainer_client.get_containers()
return jsonify(containers)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/portainer/endpoints')
def get_portainer_endpoints():
"""Get Portainer endpoints"""
if not portainer_client:
return jsonify({'error': 'Portainer not configured'}), 503
try:
endpoints = portainer_client.get_endpoints()
return jsonify(endpoints)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/portainer/stacks')
def get_portainer_stacks():
"""Get Docker stacks from Portainer"""
if not portainer_client:
return jsonify({'error': 'Portainer not configured'}), 503
try:
stacks = portainer_client.get_stacks()
return jsonify(stacks)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/portainer/container/<action>/<container_id>', methods=['POST'])
def manage_portainer_container(action, container_id):
"""Manage containers via Portainer"""
if not portainer_client:
return jsonify({'error': 'Portainer not configured'}), 503
if action not in ['start', 'stop', 'restart']:
return jsonify({'error': 'Invalid action'}), 400
try:
success = False
if action == 'start':
success = portainer_client.start_container(container_id)
elif action == 'stop':
success = portainer_client.stop_container(container_id)
elif action == 'restart':
success = portainer_client.restart_container(container_id)
return jsonify({'success': success})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/applications')
def get_applications():
"""Get all applications from database"""
try:
applications = db.get_applications()
# Sync with current containers
containers = docker_service.get_containers()
container_map = {c['id']: c for c in containers}
# Update applications with live container data
for app in applications:
container = container_map.get(app['container_id'])
if container:
app.update({
'status': container.get('status', 'unknown'),
'uptime': container.get('uptime', 'unknown'),
'image': container.get('image', ''),
'ports': container.get('ports', [])
})
return jsonify({'applications': applications})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/applications/<container_id>', methods=['GET', 'PUT', 'DELETE'])
def manage_application(container_id):
"""Manage individual application"""
try:
if request.method == 'GET':
app = db.get_application(container_id)
if app:
return jsonify(app)
return jsonify({'error': 'Application not found'}), 404
elif request.method == 'PUT':
data = request.json
db.update_application(container_id, data)
return jsonify({'success': True, 'message': 'Application updated'})
elif request.method == 'DELETE':
db.delete_application(container_id)
return jsonify({'success': True, 'message': 'Application deleted'})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/applications/sync', methods=['POST'])
def sync_applications():
"""Sync applications with current containers"""
try:
containers = docker_service.get_containers()
# Sync each container with database
for container in containers:
db.upsert_application({
'id': container['id'],
'name': container['name'],
'description': container['image'],
'url': f"http://localhost:{container['ports'][0]['host_port']}" if container['ports'] else '#'
})
return jsonify({'success': True, 'message': 'Applications synced successfully'})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(host=HOST, port=PORT, debug=DEBUG)