2.0.0 Beta : Self-hosted Release

This commit is contained in:
2026-08-09 02:03:29 +08:00
parent 63a0751aba
commit 37cb05eb70
33 changed files with 810 additions and 31 deletions
+42
View File
@@ -0,0 +1,42 @@
import hmac
from flask import (
Blueprint,
current_app,
flash,
redirect,
render_template,
request,
session,
url_for,
)
from web.auth import validate_csrf
bp = Blueprint('access', __name__, url_prefix='/access')
@bp.route('/', methods=['GET', 'POST'])
def login():
if current_app.config['SITE_VISIBILITY'] != 'private':
return redirect(url_for('main.index'))
if request.method == 'POST':
validate_csrf()
token = request.form.get('token') or ''
viewer_token = current_app.config['VIEWER_TOKEN']
if viewer_token and hmac.compare_digest(
token,
viewer_token,
):
session['viewer_access'] = True
target = request.args.get('next')
if not target or not target.startswith('/') or target.startswith('//'):
target = url_for('main.index')
return redirect(target)
flash('访问密码错误', 'error')
return render_template('access/login.html')
@bp.route('/logout')
def logout():
session.pop('viewer_access', None)
return redirect(url_for('access.login'))
+39
View File
@@ -13,6 +13,9 @@ 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
from web.services.team_context_service import TeamContextService
from web.services.web_service import WebService
from web.services.roster_version_service import RosterVersionService
import hmac
import time
@@ -38,11 +41,47 @@ def logout():
@bp.route('/')
@admin_required
def dashboard():
if not TeamContextService.get_active_roster_ids():
return redirect(url_for('admin.setup'))
return render_template(
'admin/dashboard.html',
**AdminService.get_overview(),
)
@bp.route('/setup', methods=['GET', 'POST'])
@admin_required
def setup():
if request.method == 'POST':
validate_csrf()
name = (request.form.get('name') or '').strip()
description = (request.form.get('description') or '').strip()
raw_ids = request.form.get('steam_ids') or ''
player_ids = []
for value in raw_ids.replace(',', '\n').splitlines():
steam_id = value.strip()
if steam_id and steam_id not in player_ids:
player_ids.append(steam_id)
if not name:
flash('请输入战队名称', 'error')
elif not player_ids:
flash('至少添加一名队员 Steam ID', 'error')
elif any(not value.isdigit() for value in player_ids):
flash('Steam ID 只能包含数字', 'error')
else:
lineup_id = WebService.save_lineup(
name,
description,
player_ids,
)
RosterVersionService.snapshot_roster(
player_ids,
name=f'{name} Initial Roster',
)
flash('战队初始化完成', 'success')
return redirect(url_for('admin.dashboard'))
return render_template('admin/setup.html')
@bp.route('/data-integrity')
@admin_required
def data_integrity():
+20
View File
@@ -1,8 +1,28 @@
from flask import Blueprint, render_template, request, jsonify
from web.database import query_db
from web.config import Config
from web.services.stats_service import StatsService
bp = Blueprint('main', __name__)
@bp.route('/healthz')
def healthz():
try:
for db_name in ('l2', 'l3', 'web'):
query_db(db_name, 'SELECT 1', one=True)
except Exception as exc:
return jsonify({
'status': 'unhealthy',
'error': str(exc),
'brand': Config.BRAND_PRIMARY,
}), 503
return jsonify({
'status': 'ok',
'version': '2.0.0-beta',
'brand': Config.BRAND_PRIMARY,
})
@bp.route('/')
def index():
recent_matches = StatsService.get_recent_matches(limit=5)