from flask import Blueprint, render_template, request, redirect, url_for, session, flash, jsonify from web.config import Config from web.auth import admin_required from web.database import query_db from web.services.etl_service import EtlService import hmac bp = Blueprint('admin', __name__, url_prefix='/admin') @bp.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': token = request.form.get('token') or '' if hmac.compare_digest(token, Config.ADMIN_TOKEN): session['is_admin'] = True return redirect(url_for('admin.dashboard')) else: flash('Invalid Token', 'error') return render_template('admin/login.html') @bp.route('/logout') def logout(): session.pop('is_admin', None) return redirect(url_for('main.index')) @bp.route('/') @admin_required def dashboard(): return render_template('admin/dashboard.html') @bp.route('/data-integrity') @admin_required def data_integrity(): from web.services.integrity_service import IntegrityService report = IntegrityService.build_report() if request.args.get('format') == 'json': return jsonify(report) return render_template('admin/data_integrity.html', report=report) @bp.route('/trigger_etl', methods=['POST']) @admin_required def trigger_etl(): from database.job_store import JobStore store = JobStore(Config.DB_WEB_PATH) job_id = store.create_job( 'manual_pipeline', created_by='admin', ) try: pid = EtlService.start_pipeline(job_id) except Exception as exc: store.finish_job(job_id, False, str(exc), 0) return jsonify({'success': False, 'error': str(exc)}), 500 return jsonify({'success': True, 'job_id': job_id, 'pid': pid}), 202 @bp.route('/import-match', methods=['GET', 'POST']) @admin_required def import_match(): from database.job_store import JobStore from web.services.import_service import ( DuplicateMatchError, ImportValidationError, MatchImportService, ) store = JobStore(Config.DB_WEB_PATH) if request.method == 'POST': upload = request.files.get('capture') if not upload or not upload.filename: flash('请选择 iframe_network.json 文件。', 'error') return redirect(url_for('admin.import_match')) prepared = None try: prepared = MatchImportService.prepare_import( upload.read(), upload.filename, created_by='admin', replace=request.form.get('replace') == '1', ) EtlService.start_pipeline( prepared['job_id'], match_id=prepared['match_id'], replace=prepared['replace'], ) flash( f"比赛 {prepared['match_id']} 已进入导入队列。", 'success', ) return redirect(url_for( 'admin.import_match', job_id=prepared['job_id'], )) except (DuplicateMatchError, ImportValidationError) as exc: flash(str(exc), 'warning') except Exception as exc: if prepared: store.finish_job( prepared['job_id'], False, f'Failed to start pipeline: {exc}', 0, ) flash(f'启动导入失败:{exc}', 'error') selected_job = None selected_job_id = request.args.get('job_id', type=int) if selected_job_id: selected_job = store.get_job(selected_job_id) return render_template( 'admin/import_match.html', jobs=store.list_jobs(30), selected_job=selected_job, ) @bp.route('/api/jobs/') @admin_required def api_job(job_id): from database.job_store import JobStore job = JobStore(Config.DB_WEB_PATH).get_job(job_id) if not job: return jsonify({'error': 'Job not found'}), 404 return jsonify(job) @bp.route('/sql', methods=['GET', 'POST']) @admin_required def sql_runner(): result = None error = None query = "" db_name = "l2" if request.method == 'POST': query = (request.form.get('query') or '').strip() db_name = request.form.get('db_name', 'l2') statement = query.rstrip(';').strip() if db_name not in {'l2', 'l3', 'web'}: error = "Unknown database." elif not statement.upper().startswith('SELECT '): error = "Only SELECT queries are allowed." elif ';' in statement: error = "Only one SQL statement is allowed." else: try: query = statement if 'LIMIT' not in statement.upper(): query = f"{statement} LIMIT 50" rows = query_db(db_name, query) if rows: columns = rows[0].keys() result = {'columns': columns, 'rows': rows} else: result = {'columns': [], 'rows': []} except Exception as e: error = str(e) return render_template('admin/sql.html', result=result, error=error, query=query, db_name=db_name)