From 6974e3cd1af5f1a791de3f43f57eb7e1ecf08a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AE=D1=80=D0=B8=D0=B9=20=D0=A7=D0=B5=D1=80=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE?= Date: Thu, 23 Apr 2026 11:53:46 +0300 Subject: [PATCH] =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=83=D1=87=D0=B5=D1=82=D0=BA=D0=B8=20=D0=B2=20=D0=B1?= =?UTF-8?q?=D0=B0=D0=B7=D0=B5=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 113 +++++++++++++++++++++++++++++++++- templates/admin_db_index.html | 72 +++++++++++++++++----- 2 files changed, 169 insertions(+), 16 deletions(-) diff --git a/app.py b/app.py index 492f5e3..30be8e3 100644 --- a/app.py +++ b/app.py @@ -90,6 +90,7 @@ from repositories.match_event_repository import ( from repositories.auth_repository import get_user_by_username from services.auth_service import ( create_auth_session, + hash_password, verify_password, get_current_user_from_request, build_not_authenticated_response, @@ -730,7 +731,14 @@ def close_session(session_token: str): return RedirectResponse(url="/admin/matches", status_code=303) -def render_admin_db_index(request: Request, parser_output: str | None = None, parser_name: str | None = None, parser_success: bool | None = None): +def render_admin_db_index( + request: Request, + parser_output: str | None = None, + parser_name: str | None = None, + parser_success: bool | None = None, + account_output: str | None = None, + account_success: bool | None = None, +): return templates.TemplateResponse( name="admin_db_index.html", request=request, @@ -739,6 +747,8 @@ def render_admin_db_index(request: Request, parser_output: str | None = None, pa "parser_output": parser_output, "parser_name": parser_name, "parser_success": parser_success, + "account_output": account_output, + "account_success": account_success, }, ) @@ -792,6 +802,107 @@ def admin_db_run_parser(request: Request, parser_name: str = Form(...)): ) +@app.post("/admin/db/create-account", response_class=HTMLResponse) +def admin_db_create_account( + request: Request, + username: str = Form(...), + password: str = Form(...), + role: str = Form(default="operator"), +): + current_user = getattr(request.state, "current_user", None) + if not current_user or current_user.get("role") != "admin": + return RedirectResponse(url="/login", status_code=303) + + username = username.strip() + password = password.strip() + role = (role or "operator").strip().lower() + + if not username: + return render_admin_db_index( + request, + account_output="Логин обязателен.", + account_success=False, + ) + + if not password: + return render_admin_db_index( + request, + account_output="Пароль обязателен.", + account_success=False, + ) + + if role not in {"admin", "operator"}: + return render_admin_db_index( + request, + account_output="Роль должна быть admin или operator.", + account_success=False, + ) + + password_hash = hash_password(password) + conn = get_connection() + success = True + messages: list[str] = [] + + try: + with conn: + with conn.cursor() as cur: + try: + cur.execute( + """ + INSERT INTO admin_users (username, password_hash, role, is_active) + VALUES (%s, %s, %s, TRUE) + ON CONFLICT (username) DO NOTHING + RETURNING id; + """, + (username, password_hash, role), + ) + except Exception: + conn.rollback() + with conn: + with conn.cursor() as fallback_cur: + fallback_cur.execute( + """ + INSERT INTO admin_users (username, password_hash, is_active) + VALUES (%s, %s, TRUE) + ON CONFLICT (username) DO NOTHING + RETURNING id; + """, + (username, password_hash), + ) + row = fallback_cur.fetchone() + if row: + messages.append(f"Пользователь создан: {username} (id={row[0]})") + messages.append("Примечание: колонка role в БД не найдена, роль не сохранена.") + else: + success = False + messages.append(f"Пользователь '{username}' уже существует.") + return render_admin_db_index( + request, + account_output="\n".join(messages), + account_success=success, + ) + + row = cur.fetchone() + if row: + messages.append(f"Пользователь создан: {username} (id={row[0]})") + messages.append(f"Роль: {role}") + else: + success = False + messages.append(f"Пользователь '{username}' уже существует.") + except Exception: + success = False + messages.append("Ошибка при создании аккаунта:") + messages.append(traceback.format_exc()) + finally: + conn.close() + + return render_admin_db_index( + request, + account_output="\n".join(messages).strip(), + account_success=success, + ) + + @app.get("/admin/db/players", response_class=HTMLResponse) def admin_db_players(request: Request, q: str = Query(default="")): # denied = require_role(request, {"admin"}) diff --git a/templates/admin_db_index.html b/templates/admin_db_index.html index 4143b25..63b56a1 100644 --- a/templates/admin_db_index.html +++ b/templates/admin_db_index.html @@ -11,8 +11,8 @@ --border: #2b3240; --text: #e8ecf3; --muted: #9aa4b2; - --accent: #28c76f; - --accent-hover: #20b15f; + --accent: #00c773; + --accent-hover: #00a861; --success-bg: rgba(46, 204, 113, 0.12); --success-border: rgba(46, 204, 113, 0.35); --danger-bg: rgba(255, 99, 99, 0.12); @@ -162,7 +162,7 @@ width: min(90vw, 560px); padding: 28px 24px; border-radius: 18px; - border: 1px solid rgba(0, 255, 136, 0.35); + border: 1px solid rgba(0, 255, 136, 0.30); background: rgba(12, 17, 26, 0.88); box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45); text-align: center; @@ -172,7 +172,7 @@ .matrix-title { font-size: 28px; font-weight: 700; - color: #d8ffe9; + color: #dfffee; margin-bottom: 12px; } @@ -215,6 +215,32 @@ +
+
Создать аккаунт
+
+ + +
+
+ {% if parser_output %}
Итог парсинга{% if parser_name %}: {{ parser_name }}{% endif %}
@@ -229,6 +255,21 @@
{{ parser_output }}
{% endif %} + + {% if account_output %} +
+
Создание аккаунта
+
+ Статус: + {% if account_success %} + успешно + {% else %} + ошибка + {% endif %} +
+
{{ account_output }}
+
+ {% endif %} @@ -248,9 +289,12 @@ const ctx = canvas ? canvas.getContext("2d") : null; const parserForms = document.querySelectorAll(".action-form"); let drops = []; + let speeds = []; let fontSize = 18; let columns = 0; let matrixAnimationId = null; + let lastFrameAt = 0; + const frameDelay = 75; const chars = "01アイウエオカキクケコサシスセソタチツテトABCDEFGHIJKLMNOPQRSTUVWXYZ"; function resizeCanvas() { @@ -259,13 +303,10 @@ canvas.height = window.innerHeight; columns = Math.max(1, Math.floor(canvas.width / fontSize)); drops = Array.from({ length: columns }, () => Math.random() * canvas.height / fontSize); + speeds = Array.from({ length: columns }, () => 0.45 + Math.random() * 0.35); } - let lastFrameTime = 0; - const matrixFps = 15; - const matrixFrameInterval = 1000 / matrixFps; - - function drawMatrix() { + function drawMatrixFrame() { if (!canvas || !ctx) return; ctx.fillStyle = "rgba(3, 8, 15, 0.08)"; ctx.fillRect(0, 0, canvas.width, canvas.height); @@ -279,16 +320,17 @@ ctx.fillText(text, x, y); if (y > canvas.height && Math.random() > 0.985) { drops[i] = 0; + speeds[i] = 0.45 + Math.random() * 0.35; } - drops[i] += 0.65; + drops[i] += speeds[i]; } } - function animateMatrix(timestamp) { + function animateMatrix(timestamp = 0) { if (!matrixAnimationId) return; - if (!lastFrameTime || timestamp - lastFrameTime >= matrixFrameInterval) { - drawMatrix(); - lastFrameTime = timestamp; + if (!lastFrameAt || timestamp - lastFrameAt >= frameDelay) { + drawMatrixFrame(); + lastFrameAt = timestamp; } matrixAnimationId = requestAnimationFrame(animateMatrix); } @@ -301,7 +343,7 @@ loader.setAttribute("aria-hidden", "false"); if (!matrixAnimationId) { resizeCanvas(); - lastFrameTime = 0; + lastFrameAt = 0; matrixAnimationId = requestAnimationFrame(animateMatrix); } }