2.0.0-rc2: Achievements Refactored & Admin improved.

This commit is contained in:
2026-08-09 01:34:20 +08:00
parent 3874bf57a9
commit 63a0751aba
29 changed files with 2143 additions and 355 deletions
+71 -14
View File
@@ -1,15 +1,27 @@
from flask import Blueprint, render_template, request, redirect, url_for, session, flash, jsonify
from flask import (
Blueprint,
flash,
jsonify,
redirect,
render_template,
request,
session,
url_for,
)
from web.config import Config
from web.auth import admin_required
from web.auth import admin_required, csrf_protected, validate_csrf
from web.database import query_db
from web.services.admin_service import AdminService
from web.services.etl_service import EtlService
import hmac
import time
bp = Blueprint('admin', __name__, url_prefix='/admin')
@bp.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
validate_csrf()
token = request.form.get('token') or ''
if hmac.compare_digest(token, Config.ADMIN_TOKEN):
session['is_admin'] = True
@@ -26,7 +38,10 @@ def logout():
@bp.route('/')
@admin_required
def dashboard():
return render_template('admin/dashboard.html')
return render_template(
'admin/dashboard.html',
**AdminService.get_overview(),
)
@bp.route('/data-integrity')
@admin_required
@@ -40,6 +55,7 @@ def data_integrity():
@bp.route('/trigger_etl', methods=['POST'])
@admin_required
@csrf_protected
def trigger_etl():
from database.job_store import JobStore
@@ -68,6 +84,7 @@ def import_match():
store = JobStore(Config.DB_WEB_PATH)
if request.method == 'POST':
validate_csrf()
upload = request.files.get('capture')
if not upload or not upload.filename:
flash('请选择 iframe_network.json 文件。', 'error')
@@ -91,7 +108,7 @@ def import_match():
'success',
)
return redirect(url_for(
'admin.import_match',
'admin.jobs',
job_id=prepared['job_id'],
))
except (DuplicateMatchError, ImportValidationError) as exc:
@@ -106,14 +123,34 @@ def import_match():
)
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,
jobs=store.list_jobs(5),
)
@bp.route('/jobs')
@admin_required
def jobs():
from database.job_store import JobStore
status = request.args.get('status') or None
job_type = request.args.get('type') or None
selected_job_id = request.args.get('job_id', type=int)
data = AdminService.get_jobs(status, job_type)
data['selected_job'] = (
JobStore(Config.DB_WEB_PATH).get_job(selected_job_id)
if selected_job_id else None
)
return render_template('admin/jobs.html', **data)
@bp.route('/system')
@admin_required
def system():
return render_template(
'admin/system.html',
**AdminService.get_system_status(),
)
@@ -133,9 +170,14 @@ def sql_runner():
result = None
error = None
query = ""
db_name = "l2"
db_name = request.args.get('db_name', 'l2')
if db_name not in {'l2', 'l3', 'web'}:
db_name = 'l2'
duration_ms = None
row_count = None
if request.method == 'POST':
validate_csrf()
query = (request.form.get('query') or '').strip()
db_name = request.form.get('db_name', 'l2')
@@ -151,8 +193,10 @@ def sql_runner():
query = statement
if 'LIMIT' not in statement.upper():
query = f"{statement} LIMIT 50"
started = time.perf_counter()
rows = query_db(db_name, query)
duration_ms = (time.perf_counter() - started) * 1000
row_count = len(rows)
if rows:
columns = rows[0].keys()
result = {'columns': columns, 'rows': rows}
@@ -160,5 +204,18 @@ def sql_runner():
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)
try:
catalog = AdminService.get_database_catalog(db_name)
except ValueError:
catalog = []
return render_template(
'admin/sql.html',
result=result,
error=error,
query=query,
db_name=db_name,
catalog=catalog,
duration_ms=duration_ms,
row_count=row_count,
)