""" Task Pool Blueprint Handles task pool management endpoints: listing tasks with pagination, pool statistics. """ from flask import Blueprint, request from pathlib import Path from ..shared import ( get_task_list, get_task_pool_stats, _format_response, log_performance, logger, task_status, TASK_STATUS_PENDING, TASK_STATUS_PROCESSING, TASK_STATUS_COMPLETED, TASK_STATUS_FAILED ) # Create blueprint task_pool_bp = Blueprint('task_pool', __name__, url_prefix='/tasks') def _build_simple_downloads_from_results(results: list[dict]) -> dict: """ Build direct download shortcuts for common files, based on task results. This is intentionally minimal and frontend-friendly. """ downloads: dict = {} def set_once(key: str, url: str): if key not in downloads and url: downloads[key] = url for item in results or []: if not isinstance(item, dict): continue rel_path = item.get('rel_path') if not rel_path: continue name_l = (item.get('name') or '').lower() url = f"/download/{rel_path}" if name_l.endswith('.xlsx'): set_once('data_xlsx', url) elif name_l.endswith('.xls'): set_once('data_xls', url) elif name_l.endswith('ch4_report.html'): set_once('report_ch4', url) elif name_l.endswith('co2_report.html'): set_once('report_co2', url) elif name_l.endswith(('.yaml', '.yml')): set_once('config', url) elif name_l.endswith('.json') and 'output_vars' in name_l: set_once('metadata', url) elif name_l.endswith('.html'): # fallback: any html report set_once('report_html', url) return downloads def _lean_task_summary(task_summary: dict) -> dict: """Return a minimal task representation for frontend consumption.""" task_id = task_summary.get('task_id') status = task_summary.get('status') lean = { 'task_id': task_id, 'status': status, 'message': task_summary.get('message'), 'updated_at': task_summary.get('updated_at'), } if status == TASK_STATUS_COMPLETED and task_id: full_task_info = task_status.get(task_id, {}) results = full_task_info.get('results', []) or [] downloads = _build_simple_downloads_from_results(results) if downloads: lean['downloads'] = downloads return lean @task_pool_bp.route('', methods=['GET']) @log_performance def list_tasks(): """Get paginated list of tasks with optional filtering.""" logger.debug(f"Task list request from IP {request.remote_addr}") try: # Parse query parameters status_filter = request.args.get('status') if status_filter: # Support comma-separated status values status_filter = status_filter.split(',') page = int(request.args.get('page', 1)) page_size = int(request.args.get('page_size', 20)) sort_by = request.args.get('sort_by', 'updated_at') sort_order = request.args.get('sort_order', 'desc') # Validate parameters if page < 1: return _format_response(400, "页码必须大于0") if page_size < 1 or page_size > 100: return _format_response(400, "每页数量必须在1-100之间") valid_sort_fields = ['created_at', 'updated_at', 'status'] if sort_by not in valid_sort_fields: return _format_response(400, f"排序字段必须是以下之一: {', '.join(valid_sort_fields)}") if sort_order.lower() not in ['asc', 'desc']: return _format_response(400, "排序顺序必须是 'asc' 或 'desc'") # Get task list result = get_task_list( status_filter=status_filter, page=page, page_size=page_size, sort_by=sort_by, sort_order=sort_order, cleanup=False ) # Slim response: only task status + downloads (completed only) result['tasks'] = [_lean_task_summary(t) for t in result.get('tasks', [])] logger.debug(f"Returning {len(result['tasks'])} tasks (page {page} of {result['total_pages']})") return _format_response(200, "任务列表查询成功", result) except ValueError as e: logger.warning(f"Invalid parameter in task list request: {str(e)}") return _format_response(400, "参数格式错误") except Exception as e: logger.error(f"Error listing tasks: {str(e)}", exc_info=True) return _format_response(500, "内部服务器错误") @task_pool_bp.route('/stats', methods=['GET']) @log_performance def get_pool_stats(): """Get task pool statistics.""" logger.debug(f"Task pool stats request from IP {request.remote_addr}") try: stats = get_task_pool_stats() logger.debug(f"Pool stats: {stats['total_tasks']} total tasks, " f"{stats['active_tasks']} active, {stats['queued_tasks']} queued") return _format_response(200, "任务池统计信息查询成功", stats) except Exception as e: logger.error(f"Error getting pool stats: {str(e)}", exc_info=True) return _format_response(500, "内部服务器错误") @task_pool_bp.route('/active', methods=['GET']) @log_performance def get_active_tasks(): """Get list of currently active (processing) tasks.""" logger.debug(f"Active tasks request from IP {request.remote_addr}") try: # Get all processing tasks, no pagination needed for active tasks result = get_task_list( status_filter=TASK_STATUS_PROCESSING, page=1, page_size=1000, # Large page size to get all active tasks sort_by='updated_at', sort_order='asc', # Oldest first cleanup=False ) active_tasks = result['tasks'] active_tasks = [_lean_task_summary(t) for t in active_tasks] logger.debug(f"Returning {len(active_tasks)} active tasks") return _format_response(200, "活跃任务查询成功", { 'active_tasks': active_tasks, 'count': len(active_tasks) }) except Exception as e: logger.error(f"Error getting active tasks: {str(e)}", exc_info=True) return _format_response(500, "内部服务器错误") @task_pool_bp.route('/queue', methods=['GET']) @log_performance def get_queued_tasks(): """Get list of queued (pending) tasks.""" logger.debug(f"Queued tasks request from IP {request.remote_addr}") try: # Get all pending tasks, sorted by creation time result = get_task_list( status_filter=TASK_STATUS_PENDING, page=1, page_size=1000, # Large page size to get all queued tasks sort_by='created_at', sort_order='asc', # Oldest first (FIFO) cleanup=False ) queued_tasks = result['tasks'] queued_tasks = [_lean_task_summary(t) for t in queued_tasks] logger.debug(f"Returning {len(queued_tasks)} queued tasks") return _format_response(200, "队列任务查询成功", { 'queued_tasks': queued_tasks, 'count': len(queued_tasks), 'queue_position_info': "任务按创建时间排序,较早的任务优先处理" }) except Exception as e: logger.error(f"Error getting queued tasks: {str(e)}", exc_info=True) return _format_response(500, "内部服务器错误")