modified: web/blueprints/group_admin.py
modified: web/blueprints/site_admin.py modified: web/roles.py modified: web/templates/admin/group_members.html
This commit is contained in:
@@ -9,10 +9,13 @@ from flask import Blueprint, render_template, request, redirect, url_for, sessio
|
||||
from config import Config
|
||||
from mailer import send_mail
|
||||
import panel_db as db
|
||||
from roles import GROUP_MANAGEMENT_ROLES, GROUP_ROLE_OPTIONS, GROUP_ROLE_SET, role_label
|
||||
from roles import GROUP_MANAGEMENT_ROLES, GROUP_ROLE_OPTIONS, GROUP_ROLE_SET, OWNER_ONLY_ROLES, role_label
|
||||
|
||||
group_admin = Blueprint("group_admin", __name__, url_prefix="/group-admin")
|
||||
|
||||
# Role options that group admins are allowed to assign (owner excluded)
|
||||
_NON_OWNER_ROLE_OPTIONS = [(r, l) for r, l in GROUP_ROLE_OPTIONS if r not in OWNER_ONLY_ROLES]
|
||||
|
||||
ALL_PERMISSIONS = [
|
||||
("view_dashboard", "Dashboard"),
|
||||
("view_players", "Players"),
|
||||
@@ -74,7 +77,7 @@ def members():
|
||||
return render_template("group_admin/members.html",
|
||||
group=group, members=members, non_members=non_members, pending_invites=pending_invites,
|
||||
all_permissions=ALL_PERMISSIONS,
|
||||
role_options=GROUP_ROLE_OPTIONS,
|
||||
role_options=_NON_OWNER_ROLE_OPTIONS,
|
||||
role_label=role_label)
|
||||
|
||||
|
||||
@@ -84,6 +87,9 @@ def member_add():
|
||||
group_id = session["group_id"]
|
||||
user_id = request.form.get("user_id", type=int)
|
||||
role = request.form.get("role", "viewer")
|
||||
if role in OWNER_ONLY_ROLES:
|
||||
flash("The Group Owner role can only be assigned by a Site Admin.", "danger")
|
||||
return redirect(url_for("group_admin.members"))
|
||||
if role not in GROUP_ROLE_SET:
|
||||
flash("Invalid role selected.", "danger")
|
||||
return redirect(url_for("group_admin.members"))
|
||||
@@ -113,6 +119,10 @@ def member_invite():
|
||||
flash("Invalid role selected.", "danger")
|
||||
return redirect(url_for("group_admin.members"))
|
||||
|
||||
if role in OWNER_ONLY_ROLES:
|
||||
flash("The Group Owner role can only be assigned by a Site Admin.", "danger")
|
||||
return redirect(url_for("group_admin.members"))
|
||||
|
||||
if db.count_active_group_invites(group_id) >= Config.INVITE_MAX_ACTIVE_PER_GROUP:
|
||||
flash("Active invite limit reached for this group. Revoke old invites or wait for expiry.", "danger")
|
||||
return redirect(url_for("group_admin.members"))
|
||||
@@ -222,6 +232,9 @@ def member_edit(user_id):
|
||||
|
||||
if request.method == "POST":
|
||||
role = request.form.get("role", "viewer")
|
||||
if role in OWNER_ONLY_ROLES:
|
||||
flash("The Group Owner role can only be assigned by a Site Admin.", "danger")
|
||||
return redirect(url_for("group_admin.members"))
|
||||
if role not in GROUP_ROLE_SET:
|
||||
flash("Invalid role selected.", "danger")
|
||||
return redirect(url_for("group_admin.members"))
|
||||
@@ -233,7 +246,7 @@ def member_edit(user_id):
|
||||
return render_template("group_admin/member_edit.html",
|
||||
group=group, user=user, member=member,
|
||||
current_perms=current_perms, all_permissions=ALL_PERMISSIONS,
|
||||
role_options=GROUP_ROLE_OPTIONS,
|
||||
role_options=_NON_OWNER_ROLE_OPTIONS,
|
||||
role_label=role_label)
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ MCLogger – Site-Admin-Bereich
|
||||
Verwaltet alle Gruppen und Nutzer global.
|
||||
"""
|
||||
from functools import wraps
|
||||
from datetime import datetime, timedelta
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, session, flash
|
||||
from config import Config
|
||||
from mailer import send_mail
|
||||
import panel_db as db
|
||||
from roles import GROUP_MANAGEMENT_ROLES, GROUP_ROLE_OPTIONS, GROUP_ROLE_SET, role_label
|
||||
@@ -187,11 +189,13 @@ def group_delete(group_id):
|
||||
def group_members(group_id):
|
||||
group = db.get_group_by_id(group_id)
|
||||
members = db.get_group_members(group_id)
|
||||
pending_invites = db.list_active_group_invites(group_id)
|
||||
all_users = db.list_all_users()
|
||||
member_ids = {m["id"] for m in members}
|
||||
non_members = [u for u in all_users if u["id"] not in member_ids]
|
||||
return render_template("admin/group_members.html",
|
||||
group=group, members=members, non_members=non_members,
|
||||
pending_invites=pending_invites,
|
||||
role_options=GROUP_ROLE_OPTIONS,
|
||||
role_label=role_label,
|
||||
management_roles=GROUP_MANAGEMENT_ROLES)
|
||||
@@ -235,6 +239,112 @@ def group_member_set_role(group_id, user_id):
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
|
||||
|
||||
@site_admin.route("/groups/<int:group_id>/members/invite", methods=["POST"])
|
||||
@admin_required
|
||||
def group_member_invite(group_id):
|
||||
group = db.get_group_by_id(group_id)
|
||||
if not group:
|
||||
flash("Group not found.", "danger")
|
||||
return redirect(url_for("site_admin.groups"))
|
||||
|
||||
username = request.form.get("username", "").strip()
|
||||
email = request.form.get("email", "").strip()
|
||||
role = request.form.get("role", "viewer")
|
||||
|
||||
if not username or not email:
|
||||
flash("Username and email are required.", "danger")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
if "@" not in email:
|
||||
flash("Please provide a valid email address.", "danger")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
if role not in GROUP_ROLE_SET:
|
||||
flash("Invalid role selected.", "danger")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
if db.count_active_group_invites(group_id) >= Config.INVITE_MAX_ACTIVE_PER_GROUP:
|
||||
flash("Active invite limit reached for this group.", "danger")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
if db.get_user_by_username(username):
|
||||
flash("Username already exists.", "danger")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
if db.get_active_invite_by_username(group_id, username):
|
||||
flash("There is already an active invitation for this username.", "danger")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
if db.get_user_by_email(email):
|
||||
flash("Email address is already in use.", "danger")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
if db.get_active_invite_by_email(group_id, email):
|
||||
flash("There is already an active invitation for this email.", "danger")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
|
||||
token = db.create_group_invite(group_id, username, email, role, session["user_id"])
|
||||
invite = db.get_invite_by_token(token)
|
||||
invite_url = url_for("auth.accept_invite", token=token, _external=True)
|
||||
mail_settings = db.get_site_mail_settings()
|
||||
|
||||
if mail_settings:
|
||||
subject = f"Invitation to join {group['name']}"
|
||||
text_body = (
|
||||
f"Hello {username},\n\n"
|
||||
f"You have been invited to join the group '{group['name']}' on MCLogger as {role_label(role)}.\n"
|
||||
f"Open this link to create your account:\n\n{invite_url}\n\n"
|
||||
f"This invite expires in {Config.INVITE_EXPIRY_HOURS} hours.\n"
|
||||
)
|
||||
try:
|
||||
send_mail(mail_settings, email, subject, text_body)
|
||||
if invite:
|
||||
db.mark_group_invite_sent(invite["id"], group_id)
|
||||
flash(f"Invitation email sent to '{email}'.", "success")
|
||||
except Exception:
|
||||
flash(f"Invitation created, but email delivery failed. Share this link manually: {invite_url}", "warning")
|
||||
else:
|
||||
flash(f"Invitation created for '{username}'. Share this link: {invite_url}", "success")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
|
||||
|
||||
@site_admin.route("/groups/<int:group_id>/invites/<int:invite_id>/revoke", methods=["POST"])
|
||||
@admin_required
|
||||
def group_invite_revoke(group_id, invite_id):
|
||||
db.revoke_group_invite(invite_id, group_id)
|
||||
flash("Invitation revoked.", "success")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
|
||||
|
||||
@site_admin.route("/groups/<int:group_id>/invites/<int:invite_id>/resend", methods=["POST"])
|
||||
@admin_required
|
||||
def group_invite_resend(group_id, invite_id):
|
||||
group = db.get_group_by_id(group_id)
|
||||
invite = db.get_group_invite_by_id(invite_id, group_id)
|
||||
if not invite:
|
||||
flash("Invitation not found.", "danger")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
if invite.get("accepted_at") or invite.get("revoked_at") or invite["expires_at"] <= datetime.utcnow():
|
||||
flash("Invitation is no longer active.", "danger")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
last_sent = invite.get("last_sent_at")
|
||||
if last_sent and (datetime.utcnow() - last_sent) < timedelta(seconds=Config.INVITE_RESEND_COOLDOWN_SECONDS):
|
||||
flash("Please wait before resending this invite again.", "warning")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
mail_settings = db.get_site_mail_settings()
|
||||
if not mail_settings:
|
||||
flash("No SMTP settings configured.", "danger")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
invite_url = url_for("auth.accept_invite", token=invite["token"], _external=True)
|
||||
subject = f"Invitation to join {group['name']}"
|
||||
text_body = (
|
||||
f"Hello {invite['invited_username']},\n\n"
|
||||
f"You have been invited to join the group '{group['name']}' on MCLogger as {role_label(invite['role'])}.\n"
|
||||
f"Open this link to create your account:\n\n{invite_url}\n\n"
|
||||
f"This invite expires on {invite['expires_at']}.\n"
|
||||
)
|
||||
try:
|
||||
send_mail(mail_settings, invite["invited_email"], subject, text_body)
|
||||
db.mark_group_invite_sent(invite_id, group_id)
|
||||
flash("Invitation email resent.", "success")
|
||||
except Exception:
|
||||
flash("Resend failed. Please verify SMTP settings and try again.", "danger")
|
||||
return redirect(url_for("site_admin.group_members", group_id=group_id))
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Nutzer verwalten
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user