/
ave6990
/
rst_orders
Обзор
Документация
Войти
/
ave6990
/
rst_orders
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
admin.py
189 строк
6 KB
dev
admin: панель обновления БД (логин, фоновые задачи, лог, отмена/удаление); ГРСИ извлекается только для записей без данных (registry_processed)
05 авг 2026, 13:34
05 авг 2026, 13:34
f9e771c
Код
Авторство
О чём код?
"""Админка: авторизация, запуск обновления БД с сайта, просмотр задач.""" import json import os import secrets from datetime import datetime from flask import ( Blueprint, abort, flash, jsonify, redirect, render_template, request, session, url_for, ) from admin_jobs import AdminJob, db_factory, get_active_job, start_job ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "") admin_bp = Blueprint("admin", __name__, url_prefix="/admin") def _get_db(): return db_factory() def _int_or(value, default, lo, hi): try: n = int(value) except (TypeError, ValueError): return default return max(lo, min(hi, n)) @admin_bp.before_request def _guard(): if request.endpoint == "admin.login": return if not session.get("admin"): return redirect(url_for("admin.login", next=request.path)) @admin_bp.route("/login", methods=["GET", "POST"]) def login(): if session.get("admin"): return redirect(url_for("admin.index")) if request.method == "POST": password = request.form.get("password", "") if ADMIN_PASSWORD and secrets.compare_digest(password, ADMIN_PASSWORD): session["admin"] = True nxt = request.args.get("next") return redirect(nxt or url_for("admin.index")) flash("Неверный пароль", "error") return render_template("admin/login.html", configured=bool(ADMIN_PASSWORD)) @admin_bp.route("/logout", methods=["POST"]) def logout(): session.pop("admin", None) return redirect(url_for("admin.login")) @admin_bp.route("/") def index(): db = _get_db() try: jobs = db.query(AdminJob).order_by(AdminJob.id.desc()).limit(50).all() finally: db.close() active = get_active_job(db_factory) return render_template( "admin/index.html", jobs=jobs, active=active, configured=bool(ADMIN_PASSWORD), ) @admin_bp.route("/update", methods=["POST"]) def start_update(): if not ADMIN_PASSWORD: abort(403) form = request.form params = { "fetch": form.get("fetch") == "on", "download_pdfs": form.get("download_pdfs") == "on", "download_missing": form.get("download_missing") == "on", "extract": form.get("extract") == "on", "grs_only": form.get("grs_only") == "on", "force_extract": form.get("force_extract") == "on", "archive": form.get("archive") == "on", "dry_run": form.get("dry_run") == "on", "source": form.get("source", "all"), "search": (form.get("search") or "").strip(), "date_from": (form.get("date_from") or "").strip(), "date_to": (form.get("date_to") or "").strip(), "year": _int_or(form.get("year"), None, 1900, 2200), "page_size": _int_or(form.get("page_size"), 100, 10, 1000), "workers": _int_or(form.get("workers"), 4, 1, 16), } if not (params["fetch"] or params["download_missing"] or params["extract"]): flash("Выберите хотя бы одну операцию", "error") return redirect(url_for("admin.index")) job_id, active_id = start_job(db_factory, params) if job_id is None: flash(f"Уже выполняется обновление (задача #{active_id})", "error") return redirect(url_for("admin.index")) return redirect(url_for("admin.job", job_id=job_id)) @admin_bp.route("/jobs/<int:job_id>") def job(job_id): db = _get_db() try: j = db.get(AdminJob, job_id) if not j: abort(404) params = json.loads(j.params or "{}") finally: db.close() return render_template("admin/job.html", job=j, params=params) @admin_bp.route("/jobs/<int:job_id>/log") def job_log(job_id): db = _get_db() try: j = db.get(AdminJob, job_id) if not j: abort(404) offset = request.args.get("offset", 0, type=int) full = j.log or "" chunk = full[offset:] if offset < len(full) else "" finally: db.close() return jsonify( { "id": job_id, "offset": offset + len(chunk), "log": chunk, "status": j.status, "stage": j.stage, "finished_at": j.finished_at.isoformat() if j.finished_at else None, "done": j.status not in ("queued", "running"), } ) @admin_bp.route("/jobs/<int:job_id>/cancel", methods=["POST"]) def cancel_job(job_id): db = _get_db() try: j = db.get(AdminJob, job_id) if not j: abort(404) if j.status in ("queued", "running"): j.status = "cancelled" j.stage = "Отменена" j.finished_at = datetime.now() db.commit() flash(f"Задача #{job_id} отменена", "success") else: flash(f"Задача #{job_id} уже завершена", "error") finally: db.close() return redirect(url_for("admin.job", job_id=job_id)) @admin_bp.route("/jobs/<int:job_id>/delete", methods=["POST"]) def delete_job(job_id): db = _get_db() try: j = db.get(AdminJob, job_id) if not j: abort(404) if j.status in ("queued", "running"): flash(f"Сначала отмените задачу #{job_id}", "error") return redirect(url_for("admin.job", job_id=job_id)) db.delete(j) db.commit() flash(f"Задача #{job_id} удалена", "success") return redirect(url_for("admin.index")) finally: db.close()