|
10 | 10 | import jwt |
11 | 11 | import secrets |
12 | 12 | import uuid |
| 13 | +import logging |
| 14 | + |
| 15 | +logger = logging.getLogger(__name__) |
13 | 16 |
|
14 | 17 | app = Flask(__name__, static_folder='static') |
15 | 18 | CORS(app) # 启用跨域支持 |
@@ -933,6 +936,181 @@ def get_all_execution_logs(): |
933 | 936 | return jsonify({'error': str(e)}), 500 |
934 | 937 |
|
935 | 938 |
|
| 939 | +# 钩子管理相关API |
| 940 | +@app.route('/api/tasks/<int:task_id>/hooks', methods=['PUT']) |
| 941 | +@login_required |
| 942 | +def update_task_hooks(task_id): |
| 943 | + """更新任务钩子配置""" |
| 944 | + try: |
| 945 | + import json |
| 946 | + |
| 947 | + data = request.get_json() |
| 948 | + hooks_config = data.get('hooks_config', {}) |
| 949 | + |
| 950 | + # 验证钩子配置 |
| 951 | + valid_hook_types = ['before_execute', 'after_success', 'after_failure'] |
| 952 | + valid_script_types = ['python', 'shell'] |
| 953 | + |
| 954 | + for hook_type, config in hooks_config.items(): |
| 955 | + if hook_type not in valid_hook_types: |
| 956 | + return jsonify({'error': f'无效的钩子类型: {hook_type}'}), 400 |
| 957 | + |
| 958 | + if not isinstance(config, dict): |
| 959 | + continue |
| 960 | + |
| 961 | + script_type = config.get('script_type') |
| 962 | + if script_type and script_type not in valid_script_types: |
| 963 | + return jsonify({'error': f'无效的脚本类型: {script_type}'}), 400 |
| 964 | + |
| 965 | + timeout = config.get('timeout', 30) |
| 966 | + if not isinstance(timeout, (int, float)) or timeout < 1 or timeout > 300: |
| 967 | + return jsonify({'error': 'timeout 必须在 1-300 秒之间'}), 400 |
| 968 | + |
| 969 | + with get_db() as db: |
| 970 | + # 验证任务所属 |
| 971 | + task = db.query(NotifyTask).filter( |
| 972 | + NotifyTask.id == task_id, |
| 973 | + NotifyTask.user_id == request.current_user.id |
| 974 | + ).first() |
| 975 | + if not task: |
| 976 | + return jsonify({'error': '任务不存在'}), 404 |
| 977 | + |
| 978 | + # 更新钩子配置 |
| 979 | + task.hooks_config = json.dumps(hooks_config, ensure_ascii=False) |
| 980 | + db.commit() |
| 981 | + |
| 982 | + return jsonify({ |
| 983 | + 'success': True, |
| 984 | + 'message': '钩子配置已更新' |
| 985 | + }) |
| 986 | + except Exception as e: |
| 987 | + logger.error(f"更新钩子配置失败: {str(e)}") |
| 988 | + return jsonify({'error': str(e)}), 500 |
| 989 | + |
| 990 | + |
| 991 | +@app.route('/api/tasks/<int:task_id>/hooks/logs', methods=['GET']) |
| 992 | +@login_required |
| 993 | +def get_task_hook_logs(task_id): |
| 994 | + """获取任务钩子执行日志""" |
| 995 | + try: |
| 996 | + from models import HookExecutionLog |
| 997 | + |
| 998 | + with get_db() as db: |
| 999 | + # 验证任务所属 |
| 1000 | + task = db.query(NotifyTask).filter( |
| 1001 | + NotifyTask.id == task_id, |
| 1002 | + NotifyTask.user_id == request.current_user.id |
| 1003 | + ).first() |
| 1004 | + if not task: |
| 1005 | + return jsonify({'error': '任务不存在'}), 404 |
| 1006 | + |
| 1007 | + # 分页查询 |
| 1008 | + page = int(request.args.get('page', 1)) |
| 1009 | + page_size = int(request.args.get('page_size', 50)) |
| 1010 | + hook_type = request.args.get('hook_type') # 可选过滤 |
| 1011 | + status = request.args.get('status') # 可选过滤 |
| 1012 | + |
| 1013 | + logs_query = db.query(HookExecutionLog).filter( |
| 1014 | + HookExecutionLog.task_id == task_id |
| 1015 | + ) |
| 1016 | + |
| 1017 | + if hook_type: |
| 1018 | + logs_query = logs_query.filter(HookExecutionLog.hook_type == hook_type) |
| 1019 | + |
| 1020 | + if status: |
| 1021 | + logs_query = logs_query.filter(HookExecutionLog.status == status) |
| 1022 | + |
| 1023 | + logs_query = logs_query.order_by(HookExecutionLog.execution_start.desc()) |
| 1024 | + |
| 1025 | + total = logs_query.count() |
| 1026 | + logs = logs_query.offset((page - 1) * page_size).limit(page_size).all() |
| 1027 | + |
| 1028 | + return jsonify({ |
| 1029 | + 'total': total, |
| 1030 | + 'page': page, |
| 1031 | + 'page_size': page_size, |
| 1032 | + 'logs': [log.to_dict() for log in logs] |
| 1033 | + }) |
| 1034 | + except Exception as e: |
| 1035 | + logger.error(f"获取钩子日志失败: {str(e)}") |
| 1036 | + return jsonify({'error': str(e)}), 500 |
| 1037 | + |
| 1038 | + |
| 1039 | +@app.route('/api/tasks/<int:task_id>/hooks/test', methods=['POST']) |
| 1040 | +@login_required |
| 1041 | +def test_task_hook(task_id): |
| 1042 | + """测试钩子脚本""" |
| 1043 | + try: |
| 1044 | + import json |
| 1045 | + from hooks import execute_hook |
| 1046 | + |
| 1047 | + data = request.get_json() |
| 1048 | + hook_type = data.get('hook_type') |
| 1049 | + script_type = data.get('script_type', 'python') |
| 1050 | + script = data.get('script', '') |
| 1051 | + timeout = data.get('timeout', 30) |
| 1052 | + |
| 1053 | + if not hook_type or hook_type not in ['before_execute', 'after_success', 'after_failure']: |
| 1054 | + return jsonify({'error': '无效的钩子类型'}), 400 |
| 1055 | + |
| 1056 | + if not script: |
| 1057 | + return jsonify({'error': '脚本内容不能为空'}), 400 |
| 1058 | + |
| 1059 | + with get_db() as db: |
| 1060 | + # 验证任务所属 |
| 1061 | + task = db.query(NotifyTask).filter( |
| 1062 | + NotifyTask.id == task_id, |
| 1063 | + NotifyTask.user_id == request.current_user.id |
| 1064 | + ).first() |
| 1065 | + if not task: |
| 1066 | + return jsonify({'error': '任务不存在'}), 404 |
| 1067 | + |
| 1068 | + # 构造测试配置 |
| 1069 | + test_hooks_config = { |
| 1070 | + hook_type: { |
| 1071 | + 'enabled': True, |
| 1072 | + 'script_type': script_type, |
| 1073 | + 'script': script, |
| 1074 | + 'timeout': timeout |
| 1075 | + } |
| 1076 | + } |
| 1077 | + |
| 1078 | + # 临时保存原配置 |
| 1079 | + original_config = task.hooks_config |
| 1080 | + task.hooks_config = json.dumps(test_hooks_config, ensure_ascii=False) |
| 1081 | + |
| 1082 | + # 准备测试上下文 |
| 1083 | + test_context = {} |
| 1084 | + if hook_type == 'after_success': |
| 1085 | + test_context = {'send_results': {'test': {'status': 'sent', 'message': '测试执行'}}} |
| 1086 | + elif hook_type == 'after_failure': |
| 1087 | + test_context = {'error': '测试错误', 'traceback': '测试堆栈'} |
| 1088 | + |
| 1089 | + # 执行测试 |
| 1090 | + result = execute_hook( |
| 1091 | + hook_type=hook_type, |
| 1092 | + task_id=task_id, |
| 1093 | + task=task, |
| 1094 | + context=test_context, |
| 1095 | + db_session=db, |
| 1096 | + task_execution_log_id=None |
| 1097 | + ) |
| 1098 | + |
| 1099 | + # 恢复原配置 |
| 1100 | + task.hooks_config = original_config |
| 1101 | + db.commit() |
| 1102 | + |
| 1103 | + return jsonify({ |
| 1104 | + 'success': result.get('success', False), |
| 1105 | + 'output': result.get('output', ''), |
| 1106 | + 'error': result.get('error'), |
| 1107 | + 'data': result.get('data') |
| 1108 | + }) |
| 1109 | + except Exception as e: |
| 1110 | + logger.error(f"测试钩子失败: {str(e)}") |
| 1111 | + return jsonify({'error': str(e)}), 500 |
| 1112 | + |
| 1113 | + |
936 | 1114 | # 告警规则相关API |
937 | 1115 | @app.route('/api/alerts/rules', methods=['GET']) |
938 | 1116 | @login_required |
|
0 commit comments