Files
project-panel/routes_docker.py
T

243 lines
9.2 KiB
Python
Raw Normal View History

2026-06-01 18:01:17 +08:00
#!/usr/bin/env python3
"""
Docker 监控 API 路由
"""
import subprocess
import json
from flask import jsonify, request
from utils import log_message
def _run_docker(cmd, timeout=10):
"""执行 docker 命令并返回解析结果"""
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
if result.returncode != 0:
return None, result.stderr.strip()
return result.stdout.strip(), None
except subprocess.TimeoutExpired:
return None, '命令超时'
except FileNotFoundError:
return None, 'Docker 未安装'
except Exception as e:
return None, str(e)
def register_docker_routes(app):
"""注册 Docker 监控相关路由"""
@app.route('/api/docker/containers')
def api_docker_containers():
"""获取所有容器列表(含资源使用)"""
# 获取容器列表
fmt = '{{.ID}}|{{.Names}}|{{.Image}}|{{.State}}|{{.Status}}|{{.Ports}}|{{.CreatedAt}}|{{.RunningFor}}'
out, err = _run_docker(['docker', 'ps', '-a', '--format', fmt, '--no-trunc'])
if out is None:
return jsonify({'error': err, 'containers': []})
containers = []
for line in out.split('\n'):
if not line.strip():
continue
parts = line.split('|')
if len(parts) < 8:
continue
cid = parts[0][:12] # 缩短ID
containers.append({
'id': cid,
'name': parts[1],
'image': parts[2],
'state': parts[3],
'status': parts[4],
'ports': parts[5],
'created': parts[6],
'runningFor': parts[7]
})
# 批量获取运行中容器的stats
running = [c for c in containers if c['state'] == 'running']
if running:
try:
stats_out, _ = _run_docker([
'docker', 'stats', '--no-stream', '--no-trunc',
'--format', '{{.Name}}|{{.CPUPerc}}|{{.MemPerc}}|{{.MemUsage}}|{{.NetIO}}|{{.BlockIO}}|{{.PIDs}}'
], timeout=15)
if stats_out:
stats_map = {}
for line in stats_out.split('\n'):
if not line.strip():
continue
sp = line.split('|')
if len(sp) >= 7:
stats_map[sp[0]] = {
'cpuPercent': sp[1],
'memPercent': sp[2],
'memUsage': sp[3],
'netIO': sp[4],
'blockIO': sp[5],
'pids': sp[6]
}
for c in containers:
if c['name'] in stats_map:
c['stats'] = stats_map[c['name']]
except:
pass
return jsonify({'containers': containers})
@app.route('/api/docker/container/<container_id>/stats')
def api_docker_container_stats(container_id):
"""获取单个容器实时资源统计"""
out, err = _run_docker([
'docker', 'stats', '--no-stream', '--no-trunc',
'--format', '{{.CPUPerc}}|{{.MemPerc}}|{{.MemUsage}}|{{.NetIO}}|{{.BlockIO}}|{{.PIDs}}',
container_id
], timeout=10)
if out is None:
return jsonify({'error': err}), 500
if not out:
return jsonify({'error': '容器不存在或未运行'}), 404
parts = out.split('|')
return jsonify({
'cpuPercent': parts[0] if len(parts) > 0 else '0%',
'memPercent': parts[1] if len(parts) > 1 else '0%',
'memUsage': parts[2] if len(parts) > 2 else '',
'netIO': parts[3] if len(parts) > 3 else '',
'blockIO': parts[4] if len(parts) > 4 else '',
'pids': parts[5] if len(parts) > 5 else '0'
})
@app.route('/api/docker/images')
def api_docker_images():
"""获取所有镜像列表"""
fmt = '{{.Repository}}|{{.Tag}}|{{.ID}}|{{.Size}}|{{.CreatedAt}}|{{.Containers}}'
out, err = _run_docker(['docker', 'images', '--format', fmt, '--no-trunc'])
if out is None:
return jsonify({'error': err, 'images': []})
images = []
for line in out.split('\n'):
if not line.strip():
continue
parts = line.split('|')
if len(parts) < 6:
continue
repo = parts[0] if parts[0] != '<none>' else ''
tag = parts[1] if parts[1] != '<none>' else ''
images.append({
'repository': repo,
'tag': tag,
'id': parts[2][:12],
'size': parts[3],
'created': parts[4],
'containers': parts[5],
'dangling': not repo and not tag
})
return jsonify({'images': images})
@app.route('/api/docker/system')
def api_docker_system():
"""获取 Docker 系统信息"""
# 版本信息
ver_out, _ = _run_docker(['docker', 'version', '--format', '{{.Server.Version}}|{{.Server.Os}}|{{.Server.Arch}}'], timeout=5)
# 磁盘使用
df_out, _ = _run_docker(['docker', 'system', 'df', '--format', '{{.Type}}|{{.TotalCount}}|{{.Active}}|{{.Size}}|{{.Reclaimable}}'], timeout=10)
system_info = {
'version': '',
'os': '',
'arch': '',
'diskUsage': []
}
if ver_out:
ver_parts = ver_out.split('|')
if len(ver_parts) >= 3:
system_info.update({
'version': ver_parts[0],
'os': ver_parts[1],
'arch': ver_parts[2]
})
if df_out:
for line in df_out.split('\n'):
if not line.strip():
continue
parts = line.split('|')
if len(parts) >= 5:
system_info['diskUsage'].append({
'type': parts[0],
'total': parts[1],
'active': parts[2],
'size': parts[3],
'reclaimable': parts[4]
})
return jsonify(system_info)
@app.route('/api/docker/container/<container_id>/logs')
def api_docker_container_logs(container_id):
"""获取容器日志(最近200行)"""
lines = int(request.args.get('lines', 200))
lines = max(10, min(1000, lines))
out, err = _run_docker(['docker', 'logs', '--tail', str(lines), container_id], timeout=10)
if out is None:
return jsonify({'error': err}), 500
return jsonify({'logs': out, 'lines': lines})
@app.route('/api/docker/container/<container_id>/start', methods=['POST'])
def api_docker_container_start(container_id):
"""启动容器"""
out, err = _run_docker(['docker', 'start', container_id])
if out is None and err:
return jsonify({'error': err}), 500
log_message(f"Docker 容器启动: {container_id}")
return jsonify({'message': f'容器 {container_id} 已启动'})
@app.route('/api/docker/container/<container_id>/stop', methods=['POST'])
def api_docker_container_stop(container_id):
"""停止容器"""
out, err = _run_docker(['docker', 'stop', container_id])
if out is None and err:
return jsonify({'error': err}), 500
log_message(f"Docker 容器停止: {container_id}")
return jsonify({'message': f'容器 {container_id} 已停止'})
@app.route('/api/docker/container/<container_id>/restart', methods=['POST'])
def api_docker_container_restart(container_id):
"""重启容器"""
out, err = _run_docker(['docker', 'restart', container_id])
if out is None and err:
return jsonify({'error': err}), 500
log_message(f"Docker 容器重启: {container_id}")
return jsonify({'message': f'容器 {container_id} 已重启'})
@app.route('/api/docker/prune', methods=['POST'])
def api_docker_prune():
"""清理Docker资源"""
target = request.json.get('target', 'all') if request.json else 'all'
results = {}
# 清理停止的容器
if target in ('all', 'containers'):
out, _ = _run_docker(['docker', 'container', 'prune', '-f'], timeout=30)
results['containers'] = out or '已清理'
# 清理悬空镜像
if target in ('all', 'images'):
out, _ = _run_docker(['docker', 'image', 'prune', '-f'], timeout=60)
results['images'] = out or '已清理'
# 清理构建缓存
if target in ('all', 'build'):
out, _ = _run_docker(['docker', 'builder', 'prune', '-f'], timeout=60)
results['build'] = out or '已清理'
log_message(f"Docker 资源清理完成: {target}")
return jsonify({'message': '清理完成', 'results': results})