#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 管理員 API Author: PANJIT IT Team Created: 2024-01-28 Modified: 2024-01-28 """ from datetime import datetime, timedelta from flask import Blueprint, request, jsonify, g from app.utils.decorators import admin_required from app.utils.validators import validate_pagination, validate_date_range from app.utils.helpers import create_response from app.utils.exceptions import ValidationError from app.utils.logger import get_logger from app.models.user import User from app.models.job import TranslationJob from app.models.stats import APIUsageStats from app.models.log import SystemLog from app.models.cache import TranslationCache from sqlalchemy import func, desc admin_bp = Blueprint('admin', __name__, url_prefix='/admin') logger = get_logger(__name__) @admin_bp.route('/stats', methods=['GET']) @admin_required def get_system_stats(): """取得系統統計資料(簡化版本)""" try: from app import db # 基本統計 overview = { 'total_jobs': TranslationJob.query.count(), 'completed_jobs': TranslationJob.query.filter_by(status='COMPLETED').count(), 'failed_jobs': TranslationJob.query.filter_by(status='FAILED').count(), 'pending_jobs': TranslationJob.query.filter_by(status='PENDING').count(), 'processing_jobs': TranslationJob.query.filter_by(status='PROCESSING').count(), 'total_users': User.query.count(), 'active_users_today': 0, # 簡化版本先設為0 'total_cost': 0.0 # 簡化版本先設為0 } # 簡化的用戶排行榜 - 按任務數排序 user_rankings = db.session.query( User.id, User.display_name, func.count(TranslationJob.id).label('job_count') ).outerjoin(TranslationJob).group_by( User.id, User.display_name ).order_by( func.count(TranslationJob.id).desc() ).limit(10).all() user_rankings_data = [] for ranking in user_rankings: user_rankings_data.append({ 'user_id': ranking.id, 'display_name': ranking.display_name, 'job_count': ranking.job_count or 0, 'total_cost': 0.0 # 簡化版本 }) # 簡化的每日統計 - 只返回空數組 daily_stats = [] return jsonify(create_response( success=True, data={ 'overview': overview, 'daily_stats': daily_stats, 'user_rankings': user_rankings_data, 'period': 'month', 'start_date': datetime.utcnow().isoformat(), 'end_date': datetime.utcnow().isoformat() } )) except Exception as e: logger.error(f"Get system stats error: {str(e)}") import traceback logger.error(f"Traceback: {traceback.format_exc()}") return jsonify(create_response( success=False, error='SYSTEM_ERROR', message='取得系統統計失敗' )), 500 @admin_bp.route('/jobs', methods=['GET']) @admin_required def get_all_jobs(): """取得所有使用者任務""" try: # 取得查詢參數 page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 50, type=int) user_id = request.args.get('user_id', type=int) status = request.args.get('status') # 驗證分頁參數 page, per_page = validate_pagination(page, min(per_page, 100)) # 建立查詢 query = TranslationJob.query # 使用者篩選 if user_id: query = query.filter_by(user_id=user_id) # 狀態篩選 if status and status != 'all': valid_statuses = ['PENDING', 'PROCESSING', 'COMPLETED', 'FAILED', 'RETRY'] if status.upper() in valid_statuses: query = query.filter_by(status=status.upper()) # 排序 query = query.order_by(TranslationJob.created_at.desc()) # 分頁 pagination = query.paginate( page=page, per_page=per_page, error_out=False ) jobs = pagination.items # 組合回應資料(包含使用者資訊) jobs_data = [] for job in jobs: job_data = job.to_dict() job_data['user'] = { 'id': job.user.id, 'username': job.user.username, 'display_name': job.user.display_name, 'email': job.user.email } jobs_data.append(job_data) return jsonify(create_response( success=True, data={ 'jobs': jobs_data, 'pagination': { 'page': page, 'per_page': per_page, 'total': pagination.total, 'pages': pagination.pages, 'has_prev': pagination.has_prev, 'has_next': pagination.has_next } } )) except ValidationError as e: return jsonify(create_response( success=False, error=e.error_code, message=str(e) )), 400 except Exception as e: logger.error(f"Get all jobs error: {str(e)}") return jsonify(create_response( success=False, error='SYSTEM_ERROR', message='取得任務列表失敗' )), 500 @admin_bp.route('/users', methods=['GET']) @admin_required def get_all_users(): """取得所有使用者(簡化版本)""" try: # 簡化版本 - 不使用分頁,直接返回所有用戶 users = User.query.order_by(User.created_at.desc()).limit(50).all() users_data = [] for user in users: # 直接構建基本用戶資料,不使用to_dict方法 users_data.append({ 'id': user.id, 'username': user.username, 'display_name': user.display_name, 'email': user.email, 'department': user.department or '', 'is_admin': user.is_admin, 'last_login': user.last_login.isoformat() if user.last_login else None, 'created_at': user.created_at.isoformat() if user.created_at else None, 'updated_at': user.updated_at.isoformat() if user.updated_at else None }) return jsonify(create_response( success=True, data={ 'users': users_data, 'pagination': { 'page': 1, 'per_page': 50, 'total': len(users_data), 'pages': 1, 'has_prev': False, 'has_next': False } } )) except Exception as e: logger.error(f"Get all users error: {str(e)}") import traceback logger.error(f"Traceback: {traceback.format_exc()}") return jsonify(create_response( success=False, error='SYSTEM_ERROR', message='取得使用者列表失敗' )), 500 @admin_bp.route('/logs', methods=['GET']) @admin_required def get_system_logs(): """取得系統日誌""" try: # 取得查詢參數 page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 100, type=int) level = request.args.get('level') module = request.args.get('module') start_date = request.args.get('start_date') end_date = request.args.get('end_date') # 驗證參數 page, per_page = validate_pagination(page, min(per_page, 500)) if start_date or end_date: start_date, end_date = validate_date_range(start_date, end_date) # 取得日誌 logs = SystemLog.get_logs( level=level, module=module, start_date=start_date, end_date=end_date, limit=per_page, offset=(page - 1) * per_page ) # 取得總數(簡化版本,不完全精確) total = len(logs) if len(logs) < per_page else (page * per_page) + 1 logs_data = [log.to_dict() for log in logs] return jsonify(create_response( success=True, data={ 'logs': logs_data, 'pagination': { 'page': page, 'per_page': per_page, 'total': total, 'has_more': len(logs) == per_page } } )) except ValidationError as e: return jsonify(create_response( success=False, error=e.error_code, message=str(e) )), 400 except Exception as e: logger.error(f"Get system logs error: {str(e)}") return jsonify(create_response( success=False, error='SYSTEM_ERROR', message='取得系統日誌失敗' )), 500 @admin_bp.route('/api-usage', methods=['GET']) @admin_required def get_api_usage(): """取得 API 使用統計(簡化版本)""" try: from app import db # 基本統計 total_calls = db.session.query(APIUsageStats).count() total_cost = db.session.query(func.sum(APIUsageStats.cost)).scalar() or 0.0 total_tokens = db.session.query(func.sum(APIUsageStats.total_tokens)).scalar() or 0 # 簡化版本返回基本數據 return jsonify(create_response( success=True, data={ 'daily_stats': [], # 簡化版本 'top_users': [], # 簡化版本 'endpoint_stats': [], # 簡化版本 'cost_trend': [], # 簡化版本 'period_days': 30, 'summary': { 'total_calls': total_calls, 'total_cost': float(total_cost), 'total_tokens': total_tokens } } )) except Exception as e: logger.error(f"Get API usage error: {str(e)}") import traceback logger.error(f"Traceback: {traceback.format_exc()}") return jsonify(create_response( success=False, error='SYSTEM_ERROR', message='取得API使用統計失敗' )), 500 @admin_bp.route('/cache/stats', methods=['GET']) @admin_required def get_cache_stats(): """取得翻譯快取統計""" try: cache_stats = TranslationCache.get_cache_statistics() return jsonify(create_response( success=True, data=cache_stats )) except Exception as e: logger.error(f"Get cache stats error: {str(e)}") return jsonify(create_response( success=False, error='SYSTEM_ERROR', message='取得快取統計失敗' )), 500 @admin_bp.route('/health', methods=['GET']) @admin_required def get_system_health(): """取得系統健康狀態(管理員專用)""" try: from datetime import datetime status = { 'timestamp': datetime.utcnow().isoformat(), 'status': 'healthy', 'services': {} } # 資料庫檢查 try: from app import db db.session.execute('SELECT 1') status['services']['database'] = {'status': 'healthy'} except Exception as e: status['services']['database'] = { 'status': 'unhealthy', 'error': str(e) } status['status'] = 'unhealthy' # 基本統計 try: total_jobs = TranslationJob.query.count() pending_jobs = TranslationJob.query.filter_by(status='PENDING').count() status['services']['translation_service'] = { 'status': 'healthy', 'total_jobs': total_jobs, 'pending_jobs': pending_jobs } except Exception as e: status['services']['translation_service'] = { 'status': 'unhealthy', 'error': str(e) } status['status'] = 'unhealthy' return jsonify(create_response( success=True, data=status )) except Exception as e: logger.error(f"Get system health error: {str(e)}") return jsonify({ 'timestamp': datetime.utcnow().isoformat(), 'status': 'error', 'error': str(e) }), 500 @admin_bp.route('/metrics', methods=['GET']) @admin_required def get_system_metrics(): """取得系統指標(管理員專用)""" try: from datetime import datetime, timedelta from app import db # 統計任務狀態 job_stats = db.session.query( TranslationJob.status, func.count(TranslationJob.id) ).group_by(TranslationJob.status).all() job_counts = {status: count for status, count in job_stats} # 最近24小時的統計 yesterday = datetime.utcnow() - timedelta(days=1) recent_jobs = db.session.query( TranslationJob.status, func.count(TranslationJob.id) ).filter( TranslationJob.created_at >= yesterday ).group_by(TranslationJob.status).all() recent_counts = {status: count for status, count in recent_jobs} metrics_data = { 'timestamp': datetime.utcnow().isoformat(), 'jobs': { 'pending': job_counts.get('PENDING', 0), 'processing': job_counts.get('PROCESSING', 0), 'completed': job_counts.get('COMPLETED', 0), 'failed': job_counts.get('FAILED', 0), 'retry': job_counts.get('RETRY', 0), 'total': sum(job_counts.values()) }, 'recent_24h': { 'pending': recent_counts.get('PENDING', 0), 'processing': recent_counts.get('PROCESSING', 0), 'completed': recent_counts.get('COMPLETED', 0), 'failed': recent_counts.get('FAILED', 0), 'retry': recent_counts.get('RETRY', 0), 'total': sum(recent_counts.values()) } } return jsonify(create_response( success=True, data=metrics_data )) except Exception as e: logger.error(f"Get system metrics error: {str(e)}") return jsonify(create_response( success=False, error='SYSTEM_ERROR', message='取得系統指標失敗' )), 500 @admin_bp.route('/maintenance/cleanup', methods=['POST']) @admin_required def cleanup_system(): """系統清理維護""" try: data = request.get_json() or {} # 清理選項 cleanup_logs = data.get('cleanup_logs', False) cleanup_cache = data.get('cleanup_cache', False) cleanup_files = data.get('cleanup_files', False) logs_days = data.get('logs_days', 30) cache_days = data.get('cache_days', 90) files_days = data.get('files_days', 7) cleanup_results = {} # 清理舊日誌 if cleanup_logs: deleted_logs = SystemLog.cleanup_old_logs(days_to_keep=logs_days) cleanup_results['logs'] = { 'deleted_count': deleted_logs, 'days_kept': logs_days } # 清理舊快取 if cleanup_cache: deleted_cache = TranslationCache.clear_old_cache(days_to_keep=cache_days) cleanup_results['cache'] = { 'deleted_count': deleted_cache, 'days_kept': cache_days } # 清理舊檔案(這裡會在檔案服務中實作) if cleanup_files: # from app.services.file_service import cleanup_old_files # deleted_files = cleanup_old_files(days_to_keep=files_days) cleanup_results['files'] = { 'message': 'File cleanup not implemented yet', 'days_kept': files_days } # 記錄維護日誌 SystemLog.info( 'admin.maintenance', f'System cleanup performed by {g.current_user.username}', user_id=g.current_user.id, extra_data={ 'cleanup_options': data, 'results': cleanup_results } ) logger.info(f"System cleanup performed by {g.current_user.username}") return jsonify(create_response( success=True, data=cleanup_results, message='系統清理完成' )) except Exception as e: logger.error(f"System cleanup error: {str(e)}") return jsonify(create_response( success=False, error='SYSTEM_ERROR', message='系統清理失敗' )), 500