Files
project-panel/routes_project.py

295 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
项目管理 API 路由
"""
import json
from flask import jsonify, request
from config import PROJECTS_FILE
from utils import (
log_message, load_projects, load_projects_full, save_projects,
check_port, stop_process, run_command
)
def register_project_routes(app):
"""注册项目管理相关路由"""
@app.route('/api/projects')
def api_projects():
"""获取项目列表"""
projects = load_projects()
# 添加状态信息(结构化对象,前端期望 status.status 和 status.ports
for project in projects:
ports = project.get('ports', [])
port_statuses = {}
for p in ports:
port_statuses[str(p)] = {'running': check_port(p)}
running_count = sum(1 for ps in port_statuses.values() if ps['running'])
if running_count == len(ports) and len(ports) > 0:
overall = 'running'
elif running_count > 0:
overall = 'partial'
else:
overall = 'stopped'
project['status'] = {
'status': overall,
'ports': port_statuses
}
return jsonify({
'projects': projects,
'server': load_projects_full().get('server', {})
})
@app.route('/api/projects/<project_id>/start', methods=['POST'])
def api_start(project_id):
projects = load_projects()
project = next((p for p in projects if p['id'] == project_id), None)
if not project:
return jsonify({'error': '项目不存在'}), 404
if project['type'] != 'web':
return jsonify({'error': '只有Web服务支持启动'}), 400
cwd = project.get('directory', '')
if not cwd:
return jsonify({'error': '项目目录未配置'}), 400
from config import WORKSPACE_DIR
import os
cwd = os.path.join(WORKSPACE_DIR, cwd) if not cwd.startswith('/') else cwd
if not os.path.exists(cwd):
return jsonify({'error': f'目录不存在: {cwd}'}), 400
if project.get('start_cmds'):
for name, info in project['start_cmds'].items():
log_message(f"[API-START] 项目={project_id} 命令={info['cmd']} 目录={cwd}")
run_command(info['cmd'], cwd, project_id, f'start-{name}')
else:
cmd = project.get('start_cmd', 'python3 app.py')
log_message(f"[API-START] 项目={project_id} 命令={cmd} 目录={cwd}")
run_command(cmd, cwd, project_id, 'start')
return jsonify({'message': '服务启动中...'})
@app.route('/api/projects/<project_id>/stop', methods=['POST'])
def api_stop(project_id):
projects = load_projects()
project = next((p for p in projects if p['id'] == project_id), None)
if not project:
return jsonify({'error': '项目不存在'}), 404
if project['type'] != 'web':
return jsonify({'error': '只有Web服务支持停止'}), 400
ports = project.get('ports', [])
stopped = 0
for port in ports:
stopped += stop_process(port)
return jsonify({'message': f'已停止 {stopped} 个进程'})
@app.route('/api/projects/<project_id>/restart', methods=['POST'])
def api_restart(project_id):
"""重启服务"""
# 先停止
api_stop(project_id)
# 等待1秒
import time
time.sleep(1)
# 再启动
return api_start(project_id)
@app.route('/api/projects/add', methods=['POST'])
def api_add_project():
"""添加新项目"""
data = request.get_json()
if not data.get('id') or not data.get('name') or not data.get('ports'):
return jsonify({'error': '缺少必要字段'}), 400
projects = load_projects()
# 检查ID是否已存在
if any(p['id'] == data['id'] for p in projects):
return jsonify({'error': '项目ID已存在'}), 400
# 添加项目
project = {
'id': data['id'],
'name': data['name'],
'type': data.get('type', 'web'),
'ports': data['ports'],
'directory': data.get('directory', ''),
'start_cmd': data.get('start_cmd', ''),
'stop_cmd': data.get('stop_cmd', ''),
'log_file': data.get('log_file', ''),
'health_url': data.get('health_url', ''),
'admin_url': data.get('admin_url', ''),
'description': data.get('description', ''),
'git_repo': data.get('git_repo', ''),
'version': data.get('version', 'v1.0.0')
}
projects.append(project)
save_projects(projects)
log_message(f"添加项目: {project['name']}")
return jsonify({'message': '项目添加成功', 'project': project})
@app.route('/api/projects/<project_id>', methods=['PUT'])
def api_update_project(project_id):
"""更新项目配置"""
data = request.get_json()
projects = load_projects()
project = next((p for p in projects if p['id'] == project_id), None)
if not project:
return jsonify({'error': '项目不存在'}), 404
# 更新字段
for key in ['name', 'ports', 'directory', 'log_file', 'start_cmd', 'stop_cmd', 'health_url', 'admin_url', 'description', 'git_repo', 'version']:
if key in data:
project[key] = data[key]
save_projects(projects)
log_message(f"更新项目: {project['name']}")
return jsonify({'message': '项目更新成功'})
@app.route('/api/projects/<project_id>/log')
def api_project_log(project_id):
"""获取项目日志"""
projects = load_projects()
project = next((p for p in projects if p['id'] == project_id), None)
if not project:
return jsonify({'error': '项目不存在'}), 404
lines = request.args.get('lines', 200, type=int)
lines = max(1, min(lines, 5000)) # 限制 1-5000 行
log_file = project.get('log_file', '')
if not log_file:
# 默认:项目目录下的 logs/app.log
cwd = project.get('directory', '')
if cwd:
from config import WORKSPACE_DIR
import os
abs_dir = cwd if cwd.startswith('/') else os.path.join(WORKSPACE_DIR, cwd)
log_file = os.path.join(abs_dir, 'logs', 'app.log')
# 支持相对路径和绝对路径
import os
if not log_file.startswith('/'):
cwd = project.get('directory', '')
if cwd:
from config import WORKSPACE_DIR
abs_dir = cwd if cwd.startswith('/') else os.path.join(WORKSPACE_DIR, cwd)
log_file = os.path.join(abs_dir, log_file)
if not os.path.exists(log_file):
return jsonify({'log': f'日志文件不存在: {log_file}'})
try:
# 读取最后 N 行(高效方式)
with open(log_file, 'r', encoding='utf-8', errors='replace') as f:
all_lines = f.readlines()
tail_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
content = ''.join(tail_lines)
return jsonify({
'log': content if content else '(日志为空)',
'file': log_file,
'total_lines': len(all_lines),
'shown_lines': len(tail_lines)
})
except Exception as e:
return jsonify({'error': f'读取日志失败: {str(e)}'}), 500
@app.route('/api/projects/<project_id>', methods=['DELETE'])
def api_delete_project(project_id):
"""删除项目"""
projects = load_projects()
project = next((p for p in projects if p['id'] == project_id), None)
if not project:
return jsonify({'error': '项目不存在'}), 404
projects = [p for p in projects if p['id'] != project_id]
save_projects(projects)
log_message(f"删除项目: {project['name']}")
return jsonify({'message': '项目删除成功'})
@app.route('/api/external-services')
def api_external_services():
"""获取外部服务列表"""
data = load_projects_full()
services = data.get('external_services', [])
# 添加状态(结构化对象,前端期望 status.status 和 status.ports
for service in services:
ports = service.get('ports', [])
port_statuses = {}
for p in ports:
port_statuses[str(p)] = {'running': check_port(p)}
running_count = sum(1 for ps in port_statuses.values() if ps['running'])
if running_count == len(ports) and len(ports) > 0:
overall = 'running'
elif running_count > 0:
overall = 'partial'
else:
overall = 'stopped'
service['status'] = {
'status': overall,
'ports': port_statuses
}
return jsonify({'services': services})
@app.route('/api/external-services/add', methods=['POST'])
def api_add_external_service():
"""添加外部服务"""
data = request.get_json()
if not data.get('id') or not data.get('name') or not data.get('ports'):
return jsonify({'error': '缺少必要字段'}), 400
full_data = load_projects_full()
services = full_data.get('external_services', [])
# 检查ID是否已存在
if any(s['id'] == data['id'] for s in services):
return jsonify({'error': '服务ID已存在'}), 400
# 添加服务
service = {
'id': data['id'],
'name': data['name'],
'type': 'external',
'ports': data['ports'],
'health_url': data.get('health_url', ''),
'description': data.get('description', '')
}
services.append(service)
full_data['external_services'] = services
with open(PROJECTS_FILE, 'w', encoding='utf-8') as f:
json.dump(full_data, f, ensure_ascii=False, indent=2)
log_message(f"添加外部服务: {service['name']}")
return jsonify({'message': '服务添加成功', 'service': service})