From 8dfa886024df772b01c1ea900d2926dd4e1f6f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Casaj=C3=BAs?= Date: Fri, 2 Aug 2024 18:15:18 +0200 Subject: [PATCH] Admin panel improvements (#2179) --- app/admin_model.py | 107 +++++++++-- server.py | 2 + templates/admin/alias_search.html | 300 ++++++++++++++++++++++++++++++ 3 files changed, 393 insertions(+), 16 deletions(-) create mode 100644 templates/admin/alias_search.html diff --git a/app/admin_model.py b/app/admin_model.py index 0aa7edde..565e4b6b 100644 --- a/app/admin_model.py +++ b/app/admin_model.py @@ -2,6 +2,7 @@ from typing import Optional import arrow import sqlalchemy +from flask_admin import BaseView from flask_admin.form import SecureForm from flask_admin.model.template import EndpointLinkRowAction from markupsafe import Markup @@ -28,10 +29,24 @@ from app.models import ( Alias, Newsletter, PADDLE_SUBSCRIPTION_GRACE_DAYS, + Mailbox, ) from app.newsletter_utils import send_newsletter_to_user, send_newsletter_to_address +def _admin_action_formatter(view, context, model, name): + action_name = AuditLogActionEnum.get_name(model.action) + return "{} ({})".format(action_name, model.action) + + +def _admin_date_formatter(view, context, model, name): + return model.created_at.format() + + +def _user_upgrade_channel_formatter(view, context, model, name): + return Markup(model.upgrade_channel) + + class SLModelView(sqla.ModelView): column_default_sort = ("id", True) column_display_pk = True @@ -96,10 +111,6 @@ class SLAdminIndexView(AdminIndexView): return redirect("/admin/user") -def _user_upgrade_channel_formatter(view, context, model, name): - return Markup(model.upgrade_channel) - - class UserAdmin(SLModelView): form_base_class = SecureForm column_searchable_list = ["email", "id"] @@ -120,6 +131,8 @@ class UserAdmin(SLModelView): column_formatters = { "upgrade_channel": _user_upgrade_channel_formatter, + "created_at": _admin_date_formatter, + "updated_at": _admin_date_formatter, } @action( @@ -353,12 +366,22 @@ class EmailLogAdmin(SLModelView): can_edit = False can_create = False + column_formatters = { + "created_at": _admin_date_formatter, + "updated_at": _admin_date_formatter, + } + class AliasAdmin(SLModelView): form_base_class = SecureForm column_searchable_list = ["id", "user.email", "email", "mailbox.email"] column_filters = ["id", "user.email", "email", "mailbox.email"] + column_formatters = { + "created_at": _admin_date_formatter, + "updated_at": _admin_date_formatter, + } + @action( "disable_email_spoofing_check", "Disable email spoofing protection", @@ -385,6 +408,11 @@ class MailboxAdmin(SLModelView): column_searchable_list = ["id", "user.email", "email"] column_filters = ["id", "user.email", "email"] + column_formatters = { + "created_at": _admin_date_formatter, + "updated_at": _admin_date_formatter, + } + # class LifetimeCouponAdmin(SLModelView): # can_edit = True @@ -396,12 +424,22 @@ class CouponAdmin(SLModelView): can_edit = False can_create = True + column_formatters = { + "created_at": _admin_date_formatter, + "updated_at": _admin_date_formatter, + } + class ManualSubscriptionAdmin(SLModelView): form_base_class = SecureForm can_edit = True column_searchable_list = ["id", "user.email"] + column_formatters = { + "created_at": _admin_date_formatter, + "updated_at": _admin_date_formatter, + } + @action( "extend_1y", "Extend for 1 year", @@ -445,12 +483,22 @@ class CustomDomainAdmin(SLModelView): column_exclude_list = ["ownership_txt_token"] can_edit = False + column_formatters = { + "created_at": _admin_date_formatter, + "updated_at": _admin_date_formatter, + } + class ReferralAdmin(SLModelView): form_base_class = SecureForm column_searchable_list = ["id", "user.email", "code", "name"] column_filters = ["id", "user.email", "code", "name"] + column_formatters = { + "created_at": _admin_date_formatter, + "updated_at": _admin_date_formatter, + } + def scaffold_list_columns(self): ret = super().scaffold_list_columns() ret.insert(0, "nb_user") @@ -466,15 +514,6 @@ class ReferralAdmin(SLModelView): # can_delete = True -def _admin_action_formatter(view, context, model, name): - action_name = AuditLogActionEnum.get_name(model.action) - return "{} ({})".format(action_name, model.action) - - -def _admin_created_at_formatter(view, context, model, name): - return model.created_at.format() - - class AdminAuditLogAdmin(SLModelView): form_base_class = SecureForm column_searchable_list = ["admin.id", "admin.email", "model_id", "created_at"] @@ -487,7 +526,8 @@ class AdminAuditLogAdmin(SLModelView): column_formatters = { "action": _admin_action_formatter, - "created_at": _admin_created_at_formatter, + "created_at": _admin_date_formatter, + "updated_at": _admin_date_formatter, } @@ -516,8 +556,8 @@ class ProviderComplaintAdmin(SLModelView): can_delete = False column_formatters = { - "created_at": _admin_created_at_formatter, - "updated_at": _admin_created_at_formatter, + "created_at": _admin_date_formatter, + "updated_at": _admin_date_formatter, "state": _transactionalcomplaint_state_formatter, "phase": _transactionalcomplaint_phase_formatter, "refused_email": _transactionalcomplaint_refused_email_id_formatter, @@ -687,3 +727,38 @@ class InvalidMailboxDomainAdmin(SLModelView): form_base_class = SecureForm can_create = True can_delete = True + + +class EmailSearchAdmin(BaseView): + def is_accessible(self): + return current_user.is_authenticated and current_user.is_admin + + def inaccessible_callback(self, name, **kwargs): + # redirect to login page if user doesn't have access + flash("You don't have access to the admin page", "error") + return redirect(url_for("dashboard.index", next=request.url)) + + @expose("/", methods=["GET", "POST"]) + def index(self): + alias = None + user = None + mailbox = None + no_match = False + email = None + if request.form and request.form["email"]: + email = request.form["email"] + alias = Alias.get_by(email=email) + user = User.get_by(email=email) + mailbox = Mailbox.get_by(email=email) + if not alias and not user and not mailbox: + no_match = True + + return self.render( + "admin/alias_search.html", + email=email, + no_match=no_match, + alias=alias, + mailbox=mailbox, + user=user, + user_aliases=lambda user_id: Alias.filter_by(user_id=user_id).all(), + ) diff --git a/server.py b/server.py index ea4dcc7f..ce57e95d 100644 --- a/server.py +++ b/server.py @@ -45,6 +45,7 @@ from app.admin_model import ( DailyMetricAdmin, MetricAdmin, InvalidMailboxDomainAdmin, + EmailSearchAdmin, ) from app.api.base import api_bp from app.auth.base import auth_bp @@ -786,6 +787,7 @@ def init_admin(app): admin.add_view(UserAdmin(User, Session)) admin.add_view(AliasAdmin(Alias, Session)) admin.add_view(MailboxAdmin(Mailbox, Session)) + admin.add_view(EmailSearchAdmin(name="Email Search", endpoint="email_search")) admin.add_view(CouponAdmin(Coupon, Session)) admin.add_view(ManualSubscriptionAdmin(ManualSubscription, Session)) admin.add_view(CustomDomainAdmin(CustomDomain, Session)) diff --git a/templates/admin/alias_search.html b/templates/admin/alias_search.html new file mode 100644 index 00000000..159d7331 --- /dev/null +++ b/templates/admin/alias_search.html @@ -0,0 +1,300 @@ +{% extends 'admin/master.html' %} + +{% block body %} + +
+
+
+ + +
+ +
+
+ {% if no_match %} + + + {% endif %} + {% if alias %} + +
+

Alias {{ alias.email }} found

+
+
+ Alias id +
+
+ {{ alias.id }} +
+
+ Email +
+
+ {{ alias.email }} +
+
+ Created at +
+
+ {{ alias.created_at }} +
+
+ User +
+
+
+
+ User id +
+
+ {{ alias.user.id }} +
+
+ Email +
+
+ {{ alias.user.email }} +
+
+ Premium +
+
+ {{ alias.user.is_premium() }} +
+
+ Disabled +
+
+ {{ alias.user.disabled }} +
+
+ Crated At +
+
+ {{ alias.user.created_at }} +
+
+ Mailboxes +
+
+ {% for mailbox in alias.mailboxes %} + +
+
+ Mailbox id +
+
+ {{ mailbox.id }} +
+
+ Email +
+
+ {{ mailbox.email }} +
+
+ Verified +
+
+ {{ mailbox.verified }} +
+
+ Created At +
+
+ {{ mailbox.created_at }} +
+
+ {% endfor %} +
+
+
+
+
+ {% endif %} + {% if user %} + +
+

User {{ user.email }} found

+
+
+ User id +
+
+ {{ user.id }} +
+
+ Email +
+
+ {{ user.email }} +
+ {% if user.is_paid() %} + +
+ Paid +
+
+ Yes +
+
+ Subscription +
+
+ {{ user.get_active_subscription() }} +
+ {% else %} +
+ Paid +
+
+ No +
+ {% endif %} +
+ Created at +
+
+ {{ user.created_at }} +
+
+ Mailboxes +
+
+ {% for mailbox in user.mailboxes() %} + +
+
+ Mailbox id +
+
+ {{ mailbox.id }} +
+
+ Email +
+
+ {{ mailbox.email }} +
+
+ Verified +
+
+ {{ mailbox.verified }} +
+
+ Created At +
+
+ {{ mailbox.created_at }} +
+
+ {% endfor %} +
+
+ Aliases +
+
+ {% for mailbox in user_aliases(user.id) %} + +
+
+ Mailbox id +
+
+ {{ mailbox.id }} +
+
+ Email +
+
+ {{ mailbox.email }} +
+
+ Verified +
+
+ {{ mailbox.verified }} +
+
+ Created At +
+
+ {{ mailbox.created_at }} +
+
+ {% endfor %} +
+
+
+ {% endif %} + {% if mailbox %} + +
+

Mailbox {{ mailbox.email }} found

+
+
+ Mailbox id +
+
+ {{ mailbox.id }} +
+
+ Email +
+
+ {{ mailbox.email }} +
+
+ Created at +
+
+ {{ mailbox.created_at }} +
+
+ User +
+
+
+
+ User id +
+
+ {{ mailbox.user.id }} +
+
+ Email +
+
+ {{ mailbox.user.email }} +
+
+ Premium +
+
+ {{ mailbox.user.is_premium() }} +
+
+ Disabled +
+
+ {{ mailbox.user.disabled }} +
+
+ Crated At +
+
+ {{ mailbox.user.created_at }} +
+
+
+
+
+ {% endif %} +{% endblock %}