228 lines
7.4 KiB
Python
228 lines
7.4 KiB
Python
"""
|
|
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, "内部服务器错误") |