From e8d79cca19732cc96606ee4a57d1c3ec6b652d3d Mon Sep 17 00:00:00 2001 From: Kobe Date: Sun, 1 Jun 2025 22:00:45 +0200 Subject: [PATCH] email templates page --- routes/__pycache__/main.cpython-313.pyc | Bin 63192 -> 65102 bytes routes/main.py | 107 +++++++---- templates/settings/settings.html | 11 ++ templates/settings/tabs/email_templates.html | 191 +++++++++++++++++++ 4 files changed, 272 insertions(+), 37 deletions(-) create mode 100644 templates/settings/tabs/email_templates.html diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index 4ef22cdb369f0a4854e0ce772a11f3c8b1fb8481..cb6d1eac38ff3f3641f2bb58c3997e1cf95cf0e5 100644 GIT binary patch delta 8145 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 delta 6627 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 diff --git a/routes/main.py b/routes/main.py index 67cba4b..32575f3 100644 --- a/routes/main.py +++ b/routes/main.py @@ -1,6 +1,6 @@ from flask import render_template, Blueprint, redirect, url_for, request, flash, Response, jsonify, session from flask_login import current_user, login_required -from models import User, db, Room, RoomFile, RoomMemberPermission, SiteSettings, Event, Conversation, Message, MessageAttachment, Notif +from models import User, db, Room, RoomFile, RoomMemberPermission, SiteSettings, Event, Conversation, Message, MessageAttachment, Notif, EmailTemplate from routes.auth import require_password_change import os from werkzeug.utils import secure_filename @@ -621,56 +621,40 @@ def init_routes(main_bp): @login_required def settings(): if not current_user.is_admin: - flash('Only administrators can access settings.', 'error') - return redirect(url_for('main.dashboard')) + flash('You do not have permission to access settings.', 'error') + return redirect(url_for('main.index')) - # Get active tab from URL or default to colors active_tab = request.args.get('tab', 'colors') - - # Get site settings site_settings = SiteSettings.get_settings() + company_form = CompanySettingsForm() - # Get events data if events tab is active + # Get events for the events tab events = None total_pages = 0 current_page = 1 - users = [] + users = {} if active_tab == 'events': - # Get filter parameters - event_type = request.args.get('event_type', '') - date_range = request.args.get('date_range', '7d') - user_id = request.args.get('user_id', '') page = request.args.get('page', 1, type=int) per_page = 10 - - # Build query - query = Event.query - - # Apply filters - if event_type: - query = query.filter(Event.event_type == event_type) - if user_id: - query = query.filter(Event.user_id == user_id) - if date_range and date_range != 'all': - cutoff_date = datetime.utcnow() - timedelta(days=int(date_range[:-1])) - query = query.filter(Event.timestamp >= cutoff_date) - - # Get paginated events - events = query.order_by(Event.timestamp.desc()).paginate(page=page, per_page=per_page) + events = Event.query.order_by(Event.timestamp.desc()).paginate(page=page, per_page=per_page) total_pages = events.pages current_page = events.page - # Get all users for filter dropdown - users = User.query.order_by(User.username).all() - else: - events = None - total_pages = 0 - current_page = 1 - users = [] + # Get all users for the events + user_ids = set() + for event in events.items: + user_ids.add(event.user_id) + if event.details and 'target_user_id' in event.details: + user_ids.add(event.details['target_user_id']) + + users = {user.id: user for user in User.query.filter(User.id.in_(user_ids)).all()} + + # Get email templates for the email templates tab + email_templates = [] + if active_tab == 'email_templates': + email_templates = EmailTemplate.query.filter_by(is_active=True).all() - # Create form for company settings - company_form = CompanySettingsForm() if request.method == 'GET': company_form.company_name.data = site_settings.company_name company_form.company_website.data = site_settings.company_website @@ -693,6 +677,7 @@ def init_routes(main_bp): total_pages=total_pages, current_page=current_page, users=users, + email_templates=email_templates, form=company_form) @main_bp.route('/settings/colors', methods=['POST']) @@ -1088,4 +1073,52 @@ def init_routes(main_bp): headers={ 'Content-Disposition': f'attachment; filename=event_log_{datetime.utcnow().strftime("%Y%m%d_%H%M%S")}.csv' } - ) \ No newline at end of file + ) + + @main_bp.route('/settings/email-templates/', methods=['PUT']) + @login_required + def update_email_template(template_id): + if not current_user.is_admin: + return jsonify({'error': 'Unauthorized'}), 403 + + template = EmailTemplate.query.get_or_404(template_id) + + data = request.get_json() + if not data: + return jsonify({'error': 'No data provided'}), 400 + + template.subject = data.get('subject', template.subject) + template.body = data.get('body', template.body) + + try: + db.session.commit() + + # Log the template update + log_event( + event_type='settings_update', + details={ + 'user_id': current_user.id, + 'user_name': f"{current_user.username} {current_user.last_name}", + 'update_type': 'email_template', + 'template_id': template.id, + 'template_name': template.name, + 'changes': { + 'subject': template.subject, + 'body': template.body + } + } + ) + db.session.commit() + + return jsonify({ + 'message': 'Template updated successfully', + 'template': { + 'id': template.id, + 'name': template.name, + 'subject': template.subject, + 'body': template.body + } + }) + except Exception as e: + db.session.rollback() + return jsonify({'error': str(e)}), 500 \ No newline at end of file diff --git a/templates/settings/settings.html b/templates/settings/settings.html index d5c4dc0..2d75fb3 100644 --- a/templates/settings/settings.html +++ b/templates/settings/settings.html @@ -5,6 +5,7 @@ {% from "settings/tabs/security.html" import security_tab %} {% 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/components/reset_colors_modal.html" import reset_colors_modal %} {% block title %}Settings - DocuPulse{% endblock %} @@ -37,6 +38,11 @@ Company Info +