From 5a9b6be79de70c1c8050e7aaf1ae123645ea0260 Mon Sep 17 00:00:00 2001 From: Kobe Date: Mon, 2 Jun 2025 09:30:42 +0200 Subject: [PATCH] email log --- routes/__pycache__/main.cpython-313.pyc | Bin 65067 -> 71899 bytes routes/main.py | 168 ++++++++++++++++- templates/settings/settings.html | 11 ++ templates/settings/tabs/mails.html | 229 ++++++++++++++++++++++++ 4 files changed, 406 insertions(+), 2 deletions(-) create mode 100644 templates/settings/tabs/mails.html diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index 9c60ea4663e24334fb38f1c2634af5b72cb348ed..337a0b71a07a2ca865f4a3be93f195e8323a39d1 100644 GIT binary patch delta 9254 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 RnA6ZRKP^yiGVr1&{twiK3u*uW delta 5896 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 start_date) + if user_id: + query = query.filter(Mail.recipient == User.query.get(user_id).email) + + # Get total count for pagination + total_mails = query.count() + total_pages = (total_mails + per_page - 1) // per_page + + # Get paginated mails + mails = query.order_by(Mail.created_at.desc()).paginate(page=page, per_page=per_page) + + # Get all users for filter dropdown + users = User.query.order_by(User.username).all() + + # Check if this is an AJAX request + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return render_template('settings/tabs/mails.html', + mails=mails.items, + total_pages=total_pages, + current_page=page, + status=status, + date_range=date_range, + user_id=user_id, + users=users, + csrf_token=session.get('csrf_token')) + + # For full page requests, render the full settings page + site_settings = SiteSettings.get_settings() + return render_template('settings/settings.html', + primary_color=site_settings.primary_color, + secondary_color=site_settings.secondary_color, + active_tab='mails', + site_settings=site_settings, + mails=mails.items, + total_pages=total_pages, + current_page=page, + users=users, + csrf_token=session.get('csrf_token')) + + @main_bp.route('/settings/mails/') + @login_required + def get_mail_details(mail_id): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + mail = Mail.query.get_or_404(mail_id) + return jsonify({ + 'id': mail.id, + 'recipient': mail.recipient, + 'subject': mail.subject, + 'body': mail.body, + 'status': mail.status, + 'created_at': mail.created_at.isoformat(), + 'sent_at': mail.sent_at.isoformat() if mail.sent_at else None, + 'template': { + 'id': mail.template.id, + 'name': mail.template.name + } if mail.template else None + }) + + @main_bp.route('/settings/mails/download') + @login_required + def download_mails(): + if not current_user.is_admin: + flash('Only administrators can download mail logs.', 'error') + return redirect(url_for('main.dashboard')) + + # Get filter parameters + status = request.args.get('status') + date_range = request.args.get('date_range', '7d') + user_id = request.args.get('user_id') + + # Calculate date range + end_date = datetime.utcnow() + if date_range == '24h': + start_date = end_date - timedelta(days=1) + elif date_range == '7d': + start_date = end_date - timedelta(days=7) + elif date_range == '30d': + start_date = end_date - timedelta(days=30) + else: + start_date = None + + # Build query + query = Mail.query + + if status: + query = query.filter_by(status=status) + if start_date: + query = query.filter(Mail.created_at >= start_date) + if user_id: + query = query.filter(Mail.recipient == User.query.get(user_id).email) + + # Get all mails + mails = query.order_by(Mail.created_at.desc()).all() + + # Create CSV + output = StringIO() + writer = csv.writer(output) + + # Write header + writer.writerow([ + 'Created At', + 'Recipient', + 'Subject', + 'Status', + 'Template', + 'Sent At' + ]) + + # Write data + for mail in mails: + writer.writerow([ + mail.created_at.strftime('%Y-%m-%d %H:%M:%S'), + mail.recipient, + mail.subject, + mail.status, + mail.template.name if mail.template else '-', + mail.sent_at.strftime('%Y-%m-%d %H:%M:%S') if mail.sent_at else '-' + ]) + + output.seek(0) + + return Response( + output, + mimetype='text/csv', + headers={ + 'Content-Disposition': f'attachment; filename=mail_log_{datetime.utcnow().strftime("%Y%m%d_%H%M%S")}.csv' + } + ) \ No newline at end of file diff --git a/templates/settings/settings.html b/templates/settings/settings.html index 2d75fb3..b2c0f09 100644 --- a/templates/settings/settings.html +++ b/templates/settings/settings.html @@ -6,6 +6,7 @@ {% from "settings/tabs/debugging.html" import debugging_tab %} {% from "settings/tabs/events.html" import events_tab %} {% from "settings/tabs/email_templates.html" import email_templates_tab %} +{% from "settings/tabs/mails.html" import mails_tab %} {% from "settings/components/reset_colors_modal.html" import reset_colors_modal %} {% block title %}Settings - DocuPulse{% endblock %} @@ -43,6 +44,11 @@ Email Templates +