Files
project-panel/routes_cron.py

254 lines
11 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
"""
Cron 管理 API 路由
"""
from flask import jsonify, request
from database import get_db
from utils import (
log_message, sync_crontab_from_db, get_system_crons,
cron_expression_to_human, get_next_run_time, guess_cron_name, guess_cron_category
)
def register_cron_routes(app):
"""注册 Cron 相关路由"""
@app.route('/api/cron/tasks')
def api_cron_tasks():
"""获取数据库中的 Cron 任务列表"""
db = get_db()
c = db.cursor()
c.execute("SELECT * FROM cron_tasks ORDER BY created_at DESC")
tasks = [dict(row) for row in c.fetchall()]
# 添加人类可读描述和下次执行时间
for task in tasks:
task['human_time'] = cron_expression_to_human(task['expression'])
task['next_run'] = get_next_run_time(task['expression'])
return jsonify({'tasks': tasks})
@app.route('/api/cron/tasks', methods=['POST'])
def api_cron_add():
"""添加新的 Cron 任务"""
data = request.json
if not data.get('name') or not data.get('expression') or not data.get('command'):
return jsonify({'error': '缺少必要字段'}), 400
db = get_db()
c = db.cursor()
# 插入任务
c.execute('''INSERT INTO cron_tasks (name, expression, command, description, category, enabled)
VALUES (?, ?, ?, ?, ?, ?)''',
(data['name'], data['expression'], data['command'], data.get('description', ''), data.get('category', 'other'), 1))
task_id = c.lastrowid
db.commit()
# 记录历史版本1
c.execute('''INSERT INTO cron_history (task_id, version, name, expression, command, description, category, enabled, operation)
VALUES (?, 1, ?, ?, ?, ?, ?, ?, 'create')''',
(task_id, data['name'], data['expression'], data['command'], data.get('description', ''), data.get('category', 'other'), 1))
db.commit()
# 同步到系统 crontab
sync_crontab_from_db(db)
log_message(f"添加 Cron 任务: {data['name']} ({data['expression']})")
return jsonify({'message': '任务添加成功', 'task_id': task_id})
@app.route('/api/cron/tasks/<int:task_id>', methods=['PUT'])
def api_cron_update(task_id):
"""更新 Cron 任务"""
data = request.json
db = get_db()
c = db.cursor()
# 获取当前任务
c.execute("SELECT * FROM cron_tasks WHERE id = ?", (task_id,))
old_task = c.fetchone()
if not old_task:
return jsonify({'error': '任务不存在'}), 404
# 获取当前版本号
c.execute("SELECT MAX(version) FROM cron_history WHERE task_id = ?", (task_id,))
max_version = c.fetchone()[0] or 0
new_version = max_version + 1
# 更新任务
update_fields = []
update_values = []
for field in ['name', 'expression', 'command', 'description', 'category', 'enabled']:
if field in data:
update_fields.append(f"{field} = ?")
update_values.append(data[field])
if update_fields:
update_fields.append("updated_at = CURRENT_TIMESTAMP")
c.execute(f"UPDATE cron_tasks SET {','.join(update_fields)} WHERE id = ?", update_values + [task_id])
db.commit()
# 记录历史
c.execute('''INSERT INTO cron_history (task_id, version, name, expression, command, description, category, enabled, operation)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'update')''',
(task_id, new_version, data.get('name', old_task['name']), data.get('expression', old_task['expression']),
data.get('command', old_task['command']), data.get('description', old_task['description']),
data.get('category', old_task['category']), data.get('enabled', old_task['enabled'])))
db.commit()
# 同步到系统 crontab
sync_crontab_from_db(db)
log_message(f"更新 Cron 任务 ID={task_id}: 版本 {new_version}")
return jsonify({'message': '任务更新成功', 'version': new_version})
@app.route('/api/cron/tasks/<int:task_id>', methods=['DELETE'])
def api_cron_delete(task_id):
"""删除 Cron 任务"""
db = get_db()
c = db.cursor()
c.execute("SELECT * FROM cron_tasks WHERE id = ?", (task_id,))
task = c.fetchone()
if not task:
return jsonify({'error': '任务不存在'}), 404
# 记录删除历史
c.execute("SELECT MAX(version) FROM cron_history WHERE task_id = ?", (task_id,))
max_version = c.fetchone()[0] or 0
c.execute('''INSERT INTO cron_history (task_id, version, name, expression, command, description, category, enabled, operation)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'delete')''',
(task_id, max_version + 1, task['name'], task['expression'], task['command'], task['description'], task['category'], task['enabled']))
db.commit()
# 删除任务
c.execute("DELETE FROM cron_tasks WHERE id = ?", (task_id,))
db.commit()
# 同步到系统 crontab
sync_crontab_from_db(db)
log_message(f"删除 Cron 任务 ID={task_id}: {task['name']}")
return jsonify({'message': '任务删除成功'})
@app.route('/api/cron/tasks/<int:task_id>/toggle', methods=['POST'])
def api_cron_toggle(task_id):
"""启用/禁用 Cron 任务"""
db = get_db()
c = db.cursor()
c.execute("SELECT enabled FROM cron_tasks WHERE id = ?", (task_id,))
task = c.fetchone()
if not task:
return jsonify({'error': '任务不存在'}), 404
new_enabled = 0 if task['enabled'] == 1 else 1
c.execute("UPDATE cron_tasks SET enabled = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?", (new_enabled, task_id))
db.commit()
# 同步到系统 crontab
sync_crontab_from_db(db)
log_message(f"切换 Cron 任务 ID={task_id} 状态: {new_enabled}")
return jsonify({'message': '状态已切换', 'enabled': new_enabled})
@app.route('/api/cron/tasks/<int:task_id>/history')
def api_cron_history(task_id):
"""获取任务历史版本"""
db = get_db()
c = db.cursor()
c.execute("SELECT * FROM cron_history WHERE task_id = ? ORDER BY version DESC", (task_id,))
history = [dict(row) for row in c.fetchall()]
for h in history:
if h['expression']:
h['human_time'] = cron_expression_to_human(h['expression'])
return jsonify({'history': history})
@app.route('/api/cron/tasks/<int:task_id>/rollback/<int:version>', methods=['POST'])
def api_cron_rollback(task_id, version):
"""回退到指定版本"""
db = get_db()
c = db.cursor()
c.execute("SELECT * FROM cron_history WHERE task_id = ? AND version = ?", (task_id, version))
history = c.fetchone()
if not history:
return jsonify({'error': '版本不存在'}), 404
# 更新任务到历史版本
c.execute('''UPDATE cron_tasks SET name = ?, expression = ?, command = ?, description = ?, category = ?, enabled = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?''',
(history['name'], history['expression'], history['command'], history['description'], history['category'], history['enabled'], task_id))
db.commit()
# 同步到系统 crontab
sync_crontab_from_db(db)
log_message(f"回退 Cron 任务 ID={task_id} 到版本 {version}")
return jsonify({'message': f'已回退到版本 {version}'})
@app.route('/api/cron/tasks/<int:task_id>/logs')
def api_cron_logs(task_id):
"""获取任务执行日志"""
db = get_db()
c = db.cursor()
c.execute("SELECT * FROM cron_logs WHERE task_id = ? ORDER BY execution_time DESC LIMIT 100", (task_id,))
logs = [dict(row) for row in c.fetchall()]
return jsonify({'logs': logs})
@app.route('/api/cron/categories')
def api_cron_categories():
"""获取任务分类列表"""
categories = [
{'id': 'monitor', 'name': '系统监控', 'icon': 'ri-heart-pulse-line', 'color': 'blue'},
{'id': 'backup', 'name': '数据备份', 'icon': 'ri-archive-line', 'color': 'green'},
{'id': 'cleanup', 'name': '清理任务', 'icon': 'ri-delete-bin-line', 'color': 'yellow'},
{'id': 'report', 'name': '报表生成', 'icon': 'ri-file-chart-line', 'color': 'purple'},
{'id': 'sync', 'name': '数据同步', 'icon': 'ri-refresh-line', 'color': 'cyan'},
{'id': 'notification', 'name': '通知提醒', 'icon': 'ri-notification-line', 'color': 'orange'},
{'id': 'other', 'name': '其他任务', 'icon': 'ri-more-line', 'color': 'gray'},
]
return jsonify({'categories': categories})
@app.route('/api/cron/sync', methods=['POST'])
def api_cron_sync():
"""从系统 crontab 同步到数据库(清空旧的,只保留当前系统实际存在的)"""
crons = get_system_crons()
user_crons = [c for c in crons if c['source'] == 'user']
db = get_db()
c = db.cursor()
# 清空数据库中的旧任务
c.execute("DELETE FROM cron_history WHERE task_id IN (SELECT id FROM cron_tasks)")
c.execute("DELETE FROM cron_logs WHERE task_id IN (SELECT id FROM cron_tasks)")
c.execute("DELETE FROM cron_tasks")
synced_count = 0
for cron in user_crons:
# 尝试从命令推断分类和名称
name = guess_cron_name(cron['command'])
category = guess_cron_category(cron['command'])
c.execute('''INSERT INTO cron_tasks (name, expression, command, description, category, enabled)
VALUES (?, ?, ?, ?, ?, 1)''',
(name, cron['expression'], cron['command'], '', category))
synced_count += 1
db.commit()
log_message(f"从系统 crontab 同步了 {synced_count} 个任务(已清空旧任务)")
return jsonify({'message': f'同步完成,共 {synced_count} 个任务', 'count': synced_count})