Admin panel improvements (#2179)

This commit is contained in:
Adrià Casajús 2024-08-02 18:15:18 +02:00 committed by GitHub
parent ab26dd3cb4
commit 8dfa886024
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 393 additions and 16 deletions

View File

@ -2,6 +2,7 @@ from typing import Optional
import arrow import arrow
import sqlalchemy import sqlalchemy
from flask_admin import BaseView
from flask_admin.form import SecureForm from flask_admin.form import SecureForm
from flask_admin.model.template import EndpointLinkRowAction from flask_admin.model.template import EndpointLinkRowAction
from markupsafe import Markup from markupsafe import Markup
@ -28,10 +29,24 @@ from app.models import (
Alias, Alias,
Newsletter, Newsletter,
PADDLE_SUBSCRIPTION_GRACE_DAYS, PADDLE_SUBSCRIPTION_GRACE_DAYS,
Mailbox,
) )
from app.newsletter_utils import send_newsletter_to_user, send_newsletter_to_address 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): class SLModelView(sqla.ModelView):
column_default_sort = ("id", True) column_default_sort = ("id", True)
column_display_pk = True column_display_pk = True
@ -96,10 +111,6 @@ class SLAdminIndexView(AdminIndexView):
return redirect("/admin/user") return redirect("/admin/user")
def _user_upgrade_channel_formatter(view, context, model, name):
return Markup(model.upgrade_channel)
class UserAdmin(SLModelView): class UserAdmin(SLModelView):
form_base_class = SecureForm form_base_class = SecureForm
column_searchable_list = ["email", "id"] column_searchable_list = ["email", "id"]
@ -120,6 +131,8 @@ class UserAdmin(SLModelView):
column_formatters = { column_formatters = {
"upgrade_channel": _user_upgrade_channel_formatter, "upgrade_channel": _user_upgrade_channel_formatter,
"created_at": _admin_date_formatter,
"updated_at": _admin_date_formatter,
} }
@action( @action(
@ -353,12 +366,22 @@ class EmailLogAdmin(SLModelView):
can_edit = False can_edit = False
can_create = False can_create = False
column_formatters = {
"created_at": _admin_date_formatter,
"updated_at": _admin_date_formatter,
}
class AliasAdmin(SLModelView): class AliasAdmin(SLModelView):
form_base_class = SecureForm form_base_class = SecureForm
column_searchable_list = ["id", "user.email", "email", "mailbox.email"] column_searchable_list = ["id", "user.email", "email", "mailbox.email"]
column_filters = ["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( @action(
"disable_email_spoofing_check", "disable_email_spoofing_check",
"Disable email spoofing protection", "Disable email spoofing protection",
@ -385,6 +408,11 @@ class MailboxAdmin(SLModelView):
column_searchable_list = ["id", "user.email", "email"] column_searchable_list = ["id", "user.email", "email"]
column_filters = ["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): # class LifetimeCouponAdmin(SLModelView):
# can_edit = True # can_edit = True
@ -396,12 +424,22 @@ class CouponAdmin(SLModelView):
can_edit = False can_edit = False
can_create = True can_create = True
column_formatters = {
"created_at": _admin_date_formatter,
"updated_at": _admin_date_formatter,
}
class ManualSubscriptionAdmin(SLModelView): class ManualSubscriptionAdmin(SLModelView):
form_base_class = SecureForm form_base_class = SecureForm
can_edit = True can_edit = True
column_searchable_list = ["id", "user.email"] column_searchable_list = ["id", "user.email"]
column_formatters = {
"created_at": _admin_date_formatter,
"updated_at": _admin_date_formatter,
}
@action( @action(
"extend_1y", "extend_1y",
"Extend for 1 year", "Extend for 1 year",
@ -445,12 +483,22 @@ class CustomDomainAdmin(SLModelView):
column_exclude_list = ["ownership_txt_token"] column_exclude_list = ["ownership_txt_token"]
can_edit = False can_edit = False
column_formatters = {
"created_at": _admin_date_formatter,
"updated_at": _admin_date_formatter,
}
class ReferralAdmin(SLModelView): class ReferralAdmin(SLModelView):
form_base_class = SecureForm form_base_class = SecureForm
column_searchable_list = ["id", "user.email", "code", "name"] column_searchable_list = ["id", "user.email", "code", "name"]
column_filters = ["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): def scaffold_list_columns(self):
ret = super().scaffold_list_columns() ret = super().scaffold_list_columns()
ret.insert(0, "nb_user") ret.insert(0, "nb_user")
@ -466,15 +514,6 @@ class ReferralAdmin(SLModelView):
# can_delete = True # 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): class AdminAuditLogAdmin(SLModelView):
form_base_class = SecureForm form_base_class = SecureForm
column_searchable_list = ["admin.id", "admin.email", "model_id", "created_at"] column_searchable_list = ["admin.id", "admin.email", "model_id", "created_at"]
@ -487,7 +526,8 @@ class AdminAuditLogAdmin(SLModelView):
column_formatters = { column_formatters = {
"action": _admin_action_formatter, "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 can_delete = False
column_formatters = { column_formatters = {
"created_at": _admin_created_at_formatter, "created_at": _admin_date_formatter,
"updated_at": _admin_created_at_formatter, "updated_at": _admin_date_formatter,
"state": _transactionalcomplaint_state_formatter, "state": _transactionalcomplaint_state_formatter,
"phase": _transactionalcomplaint_phase_formatter, "phase": _transactionalcomplaint_phase_formatter,
"refused_email": _transactionalcomplaint_refused_email_id_formatter, "refused_email": _transactionalcomplaint_refused_email_id_formatter,
@ -687,3 +727,38 @@ class InvalidMailboxDomainAdmin(SLModelView):
form_base_class = SecureForm form_base_class = SecureForm
can_create = True can_create = True
can_delete = 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(),
)

View File

@ -45,6 +45,7 @@ from app.admin_model import (
DailyMetricAdmin, DailyMetricAdmin,
MetricAdmin, MetricAdmin,
InvalidMailboxDomainAdmin, InvalidMailboxDomainAdmin,
EmailSearchAdmin,
) )
from app.api.base import api_bp from app.api.base import api_bp
from app.auth.base import auth_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(UserAdmin(User, Session))
admin.add_view(AliasAdmin(Alias, Session)) admin.add_view(AliasAdmin(Alias, Session))
admin.add_view(MailboxAdmin(Mailbox, 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(CouponAdmin(Coupon, Session))
admin.add_view(ManualSubscriptionAdmin(ManualSubscription, Session)) admin.add_view(ManualSubscriptionAdmin(ManualSubscription, Session))
admin.add_view(CustomDomainAdmin(CustomDomain, Session)) admin.add_view(CustomDomainAdmin(CustomDomain, Session))

View File

@ -0,0 +1,300 @@
{% extends 'admin/master.html' %}
{% block body %}
<div class="border border-dark border-2 mt-1 mb-2 p-3">
<form method="post">
<div class="form-group">
<label for="email">Email to search:</label>
<input type="text"
class="form-control"
name="email"
value="{{ email or '' }}"/>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
{% if no_match %}
<div class="border border-dark border-2 mt-1 mb-2 p-3 alert alert-warning"
role="alert">
No user, alias or mailbox found for {{ email }}
</div>
{% endif %}
{% if alias %}
<div class="border border-dark border-2 mt-1 mb-2 p-3">
<h3>Alias {{ alias.email }} found</h3>
<dl>
<dt>
Alias id
</dt>
<dd>
{{ alias.id }}
</dd>
<dt>
Email
</dt>
<dd>
{{ alias.email }}
</dd>
<dt>
Created at
</dt>
<dd>
{{ alias.created_at }}
</dd>
<dt class="mb-2">
User
</dt>
<dd class="ml-4 border-secondary border p-2">
<dl>
<dt>
User id
</dt>
<dd>
{{ alias.user.id }}
</dd>
<dt>
Email
</dt>
<dd>
{{ alias.user.email }}
</dd>
<dt>
Premium
</dt>
<dd>
{{ alias.user.is_premium() }}
</dd>
<dt>
Disabled
</dt>
<dd>
{{ alias.user.disabled }}
</dd>
<dt>
Crated At
</dt>
<dd>
{{ alias.user.created_at }}
</dd>
<dt class="border-dark border-top-2 mb-2">
Mailboxes
</dt>
<dd>
{% for mailbox in alias.mailboxes %}
<dl class="border border-grey border-2 ml-4 p-2">
<dt>
Mailbox id
</dt>
<dd>
{{ mailbox.id }}
</dd>
<dt>
Email
</dt>
<dd>
{{ mailbox.email }}
</dd>
<dt>
Verified
</dt>
<dd>
{{ mailbox.verified }}
</dd>
<dt>
Created At
</dt>
<dd>
{{ mailbox.created_at }}
</dd>
</dl>
{% endfor %}
</dd>
</dl>
</dd>
</dl>
</div>
{% endif %}
{% if user %}
<div class="border border-dark border-2 mt-1 mb-2 p-3">
<h3>User {{ user.email }} found</h3>
<dl>
<dt>
User id
</dt>
<dd>
{{ user.id }}
</dd>
<dt>
Email
</dt>
<dd>
{{ user.email }}
</dd>
{% if user.is_paid() %}
<dt>
Paid
</dt>
<dd>
Yes
</dd>
<dt>
Subscription
</dt>
<dd>
{{ user.get_active_subscription() }}
</dd>
{% else %}
<dt>
Paid
</dt>
<dd>
No
</dd>
{% endif %}
<dt>
Created at
</dt>
<dd>
{{ user.created_at }}
</dd>
<dt class="mb-2">
Mailboxes
</dt>
<dd>
{% for mailbox in user.mailboxes() %}
<dl class="border border-dark p-2 ml-4">
<dt>
Mailbox id
</dt>
<dd>
{{ mailbox.id }}
</dd>
<dt>
Email
</dt>
<dd>
{{ mailbox.email }}
</dd>
<dt>
Verified
</dt>
<dd>
{{ mailbox.verified }}
</dd>
<dt>
Created At
</dt>
<dd>
{{ mailbox.created_at }}
</dd>
</dl>
{% endfor %}
</dd>
<dt class="mb-2">
Aliases
</dt>
<dd>
{% for mailbox in user_aliases(user.id) %}
<dl class="border border-dark p-2 ml-4">
<dt>
Mailbox id
</dt>
<dd>
{{ mailbox.id }}
</dd>
<dt>
Email
</dt>
<dd>
{{ mailbox.email }}
</dd>
<dt>
Verified
</dt>
<dd>
{{ mailbox.verified }}
</dd>
<dt>
Created At
</dt>
<dd>
{{ mailbox.created_at }}
</dd>
</dl>
{% endfor %}
</dd>
</dl>
</div>
{% endif %}
{% if mailbox %}
<div class="border border-dark mt-1 mb-2 p-3">
<h3>Mailbox {{ mailbox.email }} found</h3>
<dl>
<dt>
Mailbox id
</dt>
<dd>
{{ mailbox.id }}
</dd>
<dt>
Email
</dt>
<dd>
{{ mailbox.email }}
</dd>
<dt>
Created at
</dt>
<dd>
{{ mailbox.created_at }}
</dd>
<dt class="mb-2">
User
</dt>
<dd class="ml-4">
<dl class="border-dark border p-2">
<dt>
User id
</dt>
<dd>
{{ mailbox.user.id }}
</dd>
<dt>
Email
</dt>
<dd>
{{ mailbox.user.email }}
</dd>
<dt>
Premium
</dt>
<dd>
{{ mailbox.user.is_premium() }}
</dd>
<dt>
Disabled
</dt>
<dd>
{{ mailbox.user.disabled }}
</dd>
<dt>
Crated At
</dt>
<dd>
{{ mailbox.user.created_at }}
</dd>
</dl>
</dd>
</dl>
</div>
{% endif %}
{% endblock %}