Add first-run setup wizard, README, remove default users, add easter egg
This commit is contained in:
@@ -36,7 +36,6 @@ def create_app(config_class=Config):
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
_migrate_existing_data(app)
|
||||
_create_default_users(app)
|
||||
_load_smtp_settings(app)
|
||||
|
||||
return app
|
||||
@@ -197,37 +196,3 @@ def _migrate_existing_data(app):
|
||||
db.session.rollback()
|
||||
logger.warning(f"Migration skipped or failed: {e}")
|
||||
|
||||
|
||||
def _create_default_users(app):
|
||||
"""Создание начальных пользователей, если в базе нет ни одного."""
|
||||
from app.models import User
|
||||
|
||||
try:
|
||||
if User.query.first() is not None:
|
||||
return
|
||||
|
||||
admin = User(
|
||||
username=app.config.get("ADMIN_USERNAME", "admin"),
|
||||
role="admin",
|
||||
is_active=True,
|
||||
)
|
||||
admin.set_password(app.config.get("ADMIN_PASSWORD", "admin"))
|
||||
|
||||
staff = User(
|
||||
username=app.config.get("STAFF_USERNAME", "staff"),
|
||||
role="staff",
|
||||
is_active=True,
|
||||
)
|
||||
staff.set_password(app.config.get("STAFF_PASSWORD", "staff"))
|
||||
|
||||
db.session.add(admin)
|
||||
db.session.add(staff)
|
||||
db.session.commit()
|
||||
|
||||
if app.config.get("ADMIN_PASSWORD") == "admin" or app.config.get("STAFF_PASSWORD") == "staff":
|
||||
logger.warning("Default users created with default passwords. Change them in production!")
|
||||
else:
|
||||
logger.info("Default users 'admin' and 'staff' created from environment variables.")
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logger.warning(f"Default users creation skipped or failed: {e}")
|
||||
|
||||
@@ -147,9 +147,52 @@ def _guest_matches_search(guest, query):
|
||||
# Auth
|
||||
# ---------------------------
|
||||
|
||||
def _is_first_run():
|
||||
"""True, если в базе ещё нет ни одного пользователя."""
|
||||
return User.query.first() is None
|
||||
|
||||
|
||||
@main_bp.route("/setup", methods=["GET", "POST"])
|
||||
@limiter.limit("10 per minute", methods=["POST"])
|
||||
def setup():
|
||||
"""Первоначальная настройка: создание первого администратора."""
|
||||
if not _is_first_run():
|
||||
return redirect(url_for("main.login"))
|
||||
|
||||
if request.method == "POST":
|
||||
username = request.form.get("username", "").strip()
|
||||
password = request.form.get("password", "").strip()
|
||||
password_confirm = request.form.get("password_confirm", "").strip()
|
||||
|
||||
if not username or not password:
|
||||
flash("Логин и пароль обязательны", "danger")
|
||||
return redirect(url_for("main.setup"))
|
||||
|
||||
if password != password_confirm:
|
||||
flash("Пароли не совпадают", "danger")
|
||||
return redirect(url_for("main.setup"))
|
||||
|
||||
if User.query.filter_by(username=username).first():
|
||||
flash("Пользователь с таким логином уже существует", "danger")
|
||||
return redirect(url_for("main.setup"))
|
||||
|
||||
admin = User(username=username, role="admin", is_active=True)
|
||||
admin.set_password(password)
|
||||
db.session.add(admin)
|
||||
db.session.commit()
|
||||
|
||||
flash("Первый администратор создан. Войдите в систему.", "success")
|
||||
return redirect(url_for("main.login"))
|
||||
|
||||
return render_template("setup.html")
|
||||
|
||||
|
||||
@main_bp.route("/login", methods=["GET", "POST"])
|
||||
@limiter.limit("10 per minute", methods=["POST"])
|
||||
def login():
|
||||
if _is_first_run():
|
||||
return redirect(url_for("main.setup"))
|
||||
|
||||
if request.method == "POST":
|
||||
username = request.form.get("username", "").strip()
|
||||
password = request.form.get("password", "").strip()
|
||||
@@ -1341,6 +1384,9 @@ def api_guest_detail(token):
|
||||
|
||||
@main_bp.route("/")
|
||||
def index():
|
||||
if _is_first_run():
|
||||
return redirect(url_for("main.setup"))
|
||||
|
||||
user = current_user()
|
||||
if not user:
|
||||
return redirect(url_for("main.login"))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='images/inviteme_logo.png') }}">
|
||||
<!-- InviteMe — crafted for mycloud24.ru -->
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark">
|
||||
@@ -79,5 +80,9 @@
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
|
||||
<footer class="mt-auto py-4 text-center">
|
||||
<small class="text-muted" style="opacity: 0.25; font-size: 0.7rem;">mycloud24.ru</small>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Первоначальная настройка — InviteMe{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center align-items-center" style="min-height: 70vh;">
|
||||
<div class="col-md-5">
|
||||
<div class="card mt-5">
|
||||
<div class="card-body p-4">
|
||||
<div class="text-center mb-4">
|
||||
<img src="{{ url_for('static', filename='images/inviteme_logo.png') }}" alt="InviteMe" class="login-logo">
|
||||
</div>
|
||||
<h3 class="card-title text-center mb-3" style="color: var(--accent-color); text-shadow: 0 0 12px var(--accent-glow);">Добро пожаловать</h3>
|
||||
<p class="text-muted text-center mb-4">
|
||||
Создайте первого администратора, чтобы начать работу с InviteMe.
|
||||
</p>
|
||||
<form method="POST" action="{{ url_for('main.setup') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Логин администратора</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required autofocus>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Пароль</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="password_confirm" class="form-label">Повторите пароль</label>
|
||||
<input type="password" class="form-control" id="password_confirm" name="password_confirm" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Создать администратора</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user