From ce0f7e0ac94bcb24c5834ec24e7ded0d3feb1eb4 Mon Sep 17 00:00:00 2001 From: nahakubuilde Date: Sat, 7 Jun 2025 11:57:21 +0100 Subject: [PATCH] web_UI working - needs more tweaking --- app.py | 6 +- email_frontend/example_app.py | 209 -------- email_frontend/templates/dkim.html | 318 ------------ email_frontend/templates/domains.html | 174 ------- .../templates/email/add_domain.html | 112 ----- email_frontend/templates/email/add_ip.html | 228 --------- email_frontend/templates/email/add_user.html | 176 ------- email_frontend/templates/email/dashboard.html | 285 ----------- email_frontend/templates/email/error.html | 173 ------- email_frontend/templates/email/ips.html | 203 -------- email_frontend/templates/email/users.html | 170 ------- email_frontend/templates/logs.html | 322 ------------ email_frontend/templates/settings.html | 355 ------------- .../server_web_ui}/README.md | 0 .../server_web_ui}/__init__.py | 0 .../server_web_ui}/requirements.txt | 0 .../server_web_ui/routes.py | 475 ++++++++++++++++-- .../static/css/smtp-management.css | 0 .../static/js/smtp-management.js | 0 .../server_web_ui}/templates/add_domain.html | 0 .../server_web_ui}/templates/add_ip.html | 0 .../server_web_ui}/templates/add_user.html | 0 .../server_web_ui}/templates/base.html | 0 .../server_web_ui}/templates/dashboard.html | 0 .../server_web_ui/templates}/dkim.html | 67 ++- .../server_web_ui/templates}/domains.html | 44 +- .../server_web_ui/templates/edit_dkim.html | 129 +++++ .../server_web_ui/templates/edit_domain.html | 164 ++++++ .../server_web_ui/templates/edit_ip.html | 163 ++++++ .../server_web_ui/templates/edit_user.html | 190 +++++++ .../server_web_ui}/templates/error.html | 2 +- .../server_web_ui}/templates/ips.html | 44 +- .../server_web_ui/templates}/logs.html | 0 .../server_web_ui/templates}/settings.html | 0 .../templates/sidebar_email.html | 0 .../server_web_ui}/templates/users.html | 43 +- main.py | 3 + requirements.txt | 1 - 38 files changed, 1257 insertions(+), 2799 deletions(-) delete mode 100644 email_frontend/example_app.py delete mode 100644 email_frontend/templates/dkim.html delete mode 100644 email_frontend/templates/domains.html delete mode 100644 email_frontend/templates/email/add_domain.html delete mode 100644 email_frontend/templates/email/add_ip.html delete mode 100644 email_frontend/templates/email/add_user.html delete mode 100644 email_frontend/templates/email/dashboard.html delete mode 100644 email_frontend/templates/email/error.html delete mode 100644 email_frontend/templates/email/ips.html delete mode 100644 email_frontend/templates/email/users.html delete mode 100644 email_frontend/templates/logs.html delete mode 100644 email_frontend/templates/settings.html rename {email_frontend => email_server/server_web_ui}/README.md (100%) rename {email_frontend => email_server/server_web_ui}/__init__.py (100%) rename {email_frontend => email_server/server_web_ui}/requirements.txt (100%) rename email_frontend/blueprint.py => email_server/server_web_ui/routes.py (56%) rename {email_frontend => email_server/server_web_ui}/static/css/smtp-management.css (100%) rename {email_frontend => email_server/server_web_ui}/static/js/smtp-management.js (100%) rename {email_frontend => email_server/server_web_ui}/templates/add_domain.html (100%) rename {email_frontend => email_server/server_web_ui}/templates/add_ip.html (100%) rename {email_frontend => email_server/server_web_ui}/templates/add_user.html (100%) rename {email_frontend => email_server/server_web_ui}/templates/base.html (100%) rename {email_frontend => email_server/server_web_ui}/templates/dashboard.html (100%) rename {email_frontend/templates/email => email_server/server_web_ui/templates}/dkim.html (78%) rename {email_frontend/templates/email => email_server/server_web_ui/templates}/domains.html (73%) create mode 100644 email_server/server_web_ui/templates/edit_dkim.html create mode 100644 email_server/server_web_ui/templates/edit_domain.html create mode 100644 email_server/server_web_ui/templates/edit_ip.html create mode 100644 email_server/server_web_ui/templates/edit_user.html rename {email_frontend => email_server/server_web_ui}/templates/error.html (98%) rename {email_frontend => email_server/server_web_ui}/templates/ips.html (76%) rename {email_frontend/templates/email => email_server/server_web_ui/templates}/logs.html (100%) rename {email_frontend/templates/email => email_server/server_web_ui/templates}/settings.html (100%) rename {email_frontend => email_server/server_web_ui}/templates/sidebar_email.html (100%) rename {email_frontend => email_server/server_web_ui}/templates/users.html (72%) diff --git a/app.py b/app.py index 6553250..d8c448d 100644 --- a/app.py +++ b/app.py @@ -34,7 +34,7 @@ from email_server.tool_box import get_logger from email_server.dkim_manager import DKIMManager # Import Flask frontend -from email_frontend.blueprint import email_bp +from email_server.server_web_ui.routes import email_bp logger = get_logger() @@ -69,8 +69,8 @@ class SMTPServerApp: def create_flask_app(self): """Create and configure the Flask application""" app = Flask(__name__, - static_folder='email_frontend/static', - template_folder='email_frontend/templates') + static_folder='email_server/server_web_ui/static', + template_folder='email_server/server_web_ui/templates') # Flask configuration app.config.update({ diff --git a/email_frontend/example_app.py b/email_frontend/example_app.py deleted file mode 100644 index 8dc2a26..0000000 --- a/email_frontend/example_app.py +++ /dev/null @@ -1,209 +0,0 @@ -#!/usr/bin/env python3 -""" -Example Flask Application demonstrating SMTP Management Frontend -This example shows how to integrate the email_frontend Blueprint -""" - -import os -import sys -from datetime import datetime -from flask import Flask, render_template, request, redirect, url_for, flash, jsonify -from flask_sqlalchemy import SQLAlchemy - -# Add the project root to Python path -sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -# Import the SMTP server models and utilities -try: - from database import Database, Domain, User, WhitelistedIP, DKIMKey, EmailLog, AuthLog - from email_frontend.blueprint import email_bp -except ImportError as e: - print(f"Error importing modules: {e}") - print("Make sure you're running this from the SMTP_Server directory") - sys.exit(1) - -def create_app(config_file='settings.ini'): - """Create and configure the Flask application.""" - app = Flask(__name__) - - # Basic Flask configuration - app.config['SECRET_KEY'] = 'your-secret-key-change-this-in-production' - app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///smtp_server.db' - app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False - - # Initialize database - db = SQLAlchemy(app) - - # Create database tables if they don't exist - with app.app_context(): - db.create_all() - - # Register the email management blueprint - app.register_blueprint(email_bp, url_prefix='/email') - - # Main application routes - @app.route('/') - def index(): - """Main application dashboard.""" - return redirect(url_for('email.dashboard')) - - @app.route('/health') - def health_check(): - """Simple health check endpoint.""" - return jsonify({ - 'status': 'healthy', - 'timestamp': datetime.utcnow().isoformat(), - 'service': 'SMTP Management Frontend' - }) - - # Error handlers - @app.errorhandler(404) - def not_found_error(error): - """Handle 404 errors.""" - return render_template('error.html', - error_code=404, - error_message="Page not found", - error_details="The requested page could not be found."), 404 - - @app.errorhandler(500) - def internal_error(error): - """Handle 500 errors.""" - return render_template('error.html', - error_code=500, - error_message="Internal server error", - error_details=str(error)), 500 - - @app.errorhandler(403) - def forbidden_error(error): - """Handle 403 errors.""" - return render_template('error.html', - error_code=403, - error_message="Access forbidden", - error_details="You don't have permission to access this resource."), 403 - - # Context processors for templates - @app.context_processor - def utility_processor(): - """Add utility functions to template context.""" - return { - 'moment': datetime, - 'len': len, - 'enumerate': enumerate, - 'zip': zip, - 'str': str, - 'int': int, - } - - return app - -def init_sample_data(): - """Initialize the database with sample data for testing.""" - try: - # Initialize database connection - db = Database('settings.ini') - - # Add sample domains - sample_domains = [ - 'example.com', - 'testdomain.org', - 'mydomain.net' - ] - - for domain_name in sample_domains: - if not db.get_domain(domain_name): - domain = Domain(domain_name) - db.add_domain(domain) - print(f"Added sample domain: {domain_name}") - - # Add sample users - sample_users = [ - ('admin@example.com', 'example.com', 'admin123'), - ('user@example.com', 'example.com', 'user123'), - ('test@testdomain.org', 'testdomain.org', 'test123') - ] - - for email, domain, password in sample_users: - if not db.get_user(email): - user = User(email, domain, password) - db.add_user(user) - print(f"Added sample user: {email}") - - # Add sample whitelisted IPs - sample_ips = [ - ('127.0.0.1', 'example.com', 'localhost'), - ('192.168.1.0/24', 'example.com', 'local network'), - ('10.0.0.0/8', 'testdomain.org', 'private network') - ] - - for ip, domain, description in sample_ips: - if not db.get_whitelisted_ip(ip, domain): - whitelisted_ip = WhitelistedIP(ip, domain, description) - db.add_whitelisted_ip(whitelisted_ip) - print(f"Added sample whitelisted IP: {ip} for {domain}") - - print("Sample data initialized successfully!") - - except Exception as e: - print(f"Error initializing sample data: {e}") - -def main(): - """Main function to run the example application.""" - import argparse - - parser = argparse.ArgumentParser(description='SMTP Management Frontend Example') - parser.add_argument('--host', default='127.0.0.1', help='Host to bind to') - parser.add_argument('--port', type=int, default=5000, help='Port to bind to') - parser.add_argument('--debug', action='store_true', help='Enable debug mode') - parser.add_argument('--init-data', action='store_true', help='Initialize sample data') - parser.add_argument('--config', default='settings.ini', help='Configuration file path') - - args = parser.parse_args() - - # Initialize sample data if requested - if args.init_data: - print("Initializing sample data...") - init_sample_data() - return - - # Create Flask application - app = create_app(args.config) - - print(f""" - SMTP Management Frontend Example - ================================ - - Starting server on http://{args.host}:{args.port} - - Available routes: - - / -> Dashboard (redirects to /email/dashboard) - - /email/dashboard -> Main dashboard - - /email/domains -> Domain management - - /email/users -> User management - - /email/ips -> IP whitelist management - - /email/dkim -> DKIM management - - /email/settings -> Server settings - - /email/logs -> Email and authentication logs - - /health -> Health check endpoint - - Debug mode: {'ON' if args.debug else 'OFF'} - - To initialize sample data, run: - python example_app.py --init-data - """) - - # Run the Flask application - try: - app.run( - host=args.host, - port=args.port, - debug=args.debug, - threaded=True - ) - except KeyboardInterrupt: - print("\nShutting down gracefully...") - except Exception as e: - print(f"Error starting server: {e}") - sys.exit(1) - -if __name__ == '__main__': - main() diff --git a/email_frontend/templates/dkim.html b/email_frontend/templates/dkim.html deleted file mode 100644 index d5bc82e..0000000 --- a/email_frontend/templates/dkim.html +++ /dev/null @@ -1,318 +0,0 @@ -{% extends "base.html" %} - -{% block title %}DKIM Keys - Email Server{% endblock %} - -{% block extra_css %} - -{% endblock %} - -{% block content %} -
-
-

- - DKIM Key Management -

-
- -
-
- - {% for item in dkim_data %} -
-
-
-
- - {{ item.domain.domain_name }} - {% if item.dkim_key.is_active %} - Active - {% else %} - Inactive - {% endif %} -
-
- -
- -
-
-
-
-
-
- -
-
- - DKIM DNS Record - - - Not checked - -
-
- Name: -
{{ item.dns_record.name }}
-
-
- Type: TXT -
-
- Value: -
{{ item.dns_record.value }}
-
- -
- - -
-
- - SPF DNS Record - - - Not checked - -
-
- Name: -
{{ item.domain.domain_name }}
-
-
- Type: TXT -
- {% if item.existing_spf %} -
- Current SPF: -
{{ item.existing_spf }}
-
- {% endif %} -
- Recommended SPF: -
{{ item.recommended_spf }}
-
- -
-
- - -
-
-
Key Information
-
-
- Selector:
- {{ item.dkim_key.selector }} -
-
- Created:
- {{ item.dkim_key.created_at.strftime('%Y-%m-%d %H:%M') }} -
-
- Server IP:
- {{ item.public_ip }} -
-
- Status:
- {% if item.dkim_key.is_active %} - Active - {% else %} - Inactive - {% endif %} -
-
-
-
-
-
- {% endfor %} - - {% if not dkim_data %} -
-
- -

No DKIM Keys Found

-

Add domains first to automatically generate DKIM keys

- - - Add Domain - -
-
- {% endif %} -
- - - -{% endblock %} - -{% block extra_js %} - -{% endblock %} diff --git a/email_frontend/templates/domains.html b/email_frontend/templates/domains.html deleted file mode 100644 index 96d22ef..0000000 --- a/email_frontend/templates/domains.html +++ /dev/null @@ -1,174 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Domains - Email Server Management{% endblock %} -{% block page_title %}Domain Management{% endblock %} - -{% block content %} -
-

- - Domains -

- - - Add Domain - -
- -
-
-
- - All Domains -
-
-
- {% if domains %} -
- - - - - - - - - - - - - {% for domain in domains %} - - - - - - - - - {% endfor %} - -
Domain NameStatusCreatedUsersDKIMActions
-
{{ domain.domain_name }}
-
- {% if domain.is_active %} - - - Active - - {% else %} - - - Inactive - - {% endif %} - - - {{ domain.created_at.strftime('%Y-%m-%d %H:%M') }} - - - - {{ domain.users|length if domain.users else 0 }} users - - - {% set has_dkim = domain.dkim_keys and domain.dkim_keys|selectattr('is_active')|list %} - {% if has_dkim %} - - - - {% else %} - - - - {% endif %} - -
- - - - {% if domain.is_active %} -
- -
- {% endif %} -
-
-
- {% else %} -
- -

No domains configured

-

Get started by adding your first domain

- - - Add Your First Domain - -
- {% endif %} -
-
- -{% if domains %} -
-
-
-
-
- - Domain Information -
-
-
-
    -
  • - - Active domains: {{ domains|selectattr('is_active')|list|length }} -
  • -
  • - - DKIM configured: {{ domains|selectattr('dkim_keys')|list|length }} -
  • -
  • - - Total users: {{ domains|sum(attribute='users')|length if domains[0].users is defined else 'N/A' }} -
  • -
-
-
-
-
-
-
-
- - Quick Tips -
-
-
-
    -
  • - - DKIM keys are automatically generated for new domains -
  • -
  • - - Configure DNS records after adding domains -
  • -
  • - - Add users or whitelist IPs for authentication -
  • -
-
-
-
-
-{% endif %} -{% endblock %} diff --git a/email_frontend/templates/email/add_domain.html b/email_frontend/templates/email/add_domain.html deleted file mode 100644 index 65e3a21..0000000 --- a/email_frontend/templates/email/add_domain.html +++ /dev/null @@ -1,112 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Add Domain - Email Server Management{% endblock %} -{% block page_title %}Add New Domain{% endblock %} - -{% block content %} -
-
-
-
-
- - Add New Domain -
-
-
-
-
- - -
- Enter the domain name that will be used for sending emails (e.g., example.com) -
-
- -
-
- - What happens next? -
-
    -
  • Domain will be added to the system
  • -
  • DKIM key pair will be automatically generated
  • -
  • You'll need to configure DNS records
  • -
  • Add users or whitelist IPs for authentication
  • -
-
- -
- - - Back to Domains - - -
-
-
-
- -
-
-
- - Domain Requirements -
-
-
-
-
-
- - Valid Examples -
-
    -
  • example.com
  • -
  • mail.example.com
  • -
  • my-domain.org
  • -
  • company.co.uk
  • -
-
-
-
- - Invalid Examples -
-
    -
  • http://example.com
  • -
  • example
  • -
  • .example.com
  • -
  • example..com
  • -
-
-
-
-
-
-
-{% endblock %} - -{% block extra_js %} - -{% endblock %} diff --git a/email_frontend/templates/email/add_ip.html b/email_frontend/templates/email/add_ip.html deleted file mode 100644 index f49d80f..0000000 --- a/email_frontend/templates/email/add_ip.html +++ /dev/null @@ -1,228 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Add IP Address - Email Server{% endblock %} - -{% block content %} -
-
-
-
-
-

- - Add IP Address to Whitelist -

-
-
-
-
- - -
- IPv4 address that will be allowed to send emails without authentication -
-
- -
- - -
- This IP will only be able to send emails for the selected domain -
-
- -
-
- - Security Note -
-
    -
  • Only whitelist trusted IP addresses
  • -
  • This IP can send emails without username/password authentication
  • -
  • The IP is restricted to the selected domain only
  • -
  • Use static IP addresses for reliable access
  • -
-
- -
- - - Back to IP List - - -
-
-
-
-
-
-
- - -
-
-
-
-
- - Your Current IP -
-
-
-
- - Detecting... -
- -
-
-
- - {% if domains %} -
-
-
-
- - Available Domains -
-
-
- {% for domain in domains %} -
-
{{ domain.domain_name }}
- - Created: {{ domain.created_at.strftime('%Y-%m-%d') }} - -
- {% if not loop.last %}
{% endif %} - {% endfor %} -
-
-
- {% endif %} -
- - -
-
-
-
-
- - Common Use Cases -
-
-
-
-
-
- - Application Servers -
-

- Web applications that need to send transactional emails - (password resets, notifications, etc.) -

-
-
-
- - Scheduled Tasks -
-

- Cron jobs or scheduled scripts that send automated - reports or alerts -

-
-
-
-
-
- - Monitoring Systems -
-

- Monitoring tools that send alerts and status updates - to administrators -

-
-
-
- - Cloud Services -
-

- Cloud-based applications or services that need to - send emails on behalf of your domain -

-
-
-
-
-
-
-{% endblock %} - -{% block extra_js %} - -{% endblock %} diff --git a/email_frontend/templates/email/add_user.html b/email_frontend/templates/email/add_user.html deleted file mode 100644 index 902a042..0000000 --- a/email_frontend/templates/email/add_user.html +++ /dev/null @@ -1,176 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Add User - Email Server{% endblock %} - -{% block content %} -
-
-
-
-
-

- - Add New User -

-
-
-
-
- - -
- The email address for authentication and sending -
-
- -
- - -
- Minimum 6 characters -
-
- -
- - -
- The domain this user belongs to -
-
- -
-
- - -
- If checked, user can send emails as any address in their domain. - Otherwise, user can only send as their own email address. -
-
-
- -
-
- - Permission Levels -
-
    -
  • Regular User: Can only send emails from their own email address
  • -
  • Domain Admin: Can send emails from any address in their domain (e.g., noreply@domain.com, support@domain.com)
  • -
-
- -
- - - Back to Users - - -
-
-
-
-
-
-
- - -
- {% if domains %} -
-
-
-
- - Available Domains -
-
-
-
- {% for domain in domains %} -
-
-
{{ domain.domain_name }}
- - Created: {{ domain.created_at.strftime('%Y-%m-%d') }} - -
-
- {% endfor %} -
-
-
-
- {% endif %} -
-{% endblock %} - -{% block extra_js %} - -{% endblock %} diff --git a/email_frontend/templates/email/dashboard.html b/email_frontend/templates/email/dashboard.html deleted file mode 100644 index 60382f6..0000000 --- a/email_frontend/templates/email/dashboard.html +++ /dev/null @@ -1,285 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Dashboard - Email Server Management{% endblock %} -{% block page_title %}Dashboard{% endblock %} - -{% block content %} -
- -
-
-
-
-
-
- - Domains -
-

{{ domain_count }}

- Active domains -
-
- -
-
-
-
-
- -
-
-
-
-
-
- - Users -
-

{{ user_count }}

- Authenticated users -
-
- -
-
-
-
-
- -
-
-
-
-
-
- - DKIM Keys -
-

{{ dkim_count }}

- Active DKIM keys -
-
- -
-
-
-
-
- -
-
-
-
-
-
- - Status -
-
- - Online -
- Server running -
-
- -
-
-
-
-
-
- -
- -
-
-
-
- - Recent Email Activity -
- - View All - -
-
- {% if recent_emails %} -
- - - - - - - - - - - - {% for email in recent_emails %} - - - - - - - - {% endfor %} - -
TimeFromToStatusDKIM
- - {{ email.created_at.strftime('%H:%M:%S') }} - - - - {{ email.mail_from }} - - - - {{ email.to_address }} - - - {% if email.status == 'relayed' %} - - - Sent - - {% else %} - - - Failed - - {% endif %} - - {% if email.dkim_signed %} - - - - {% else %} - - - - {% endif %} -
-
- {% else %} -
- -

No email activity yet

-
- {% endif %} -
-
-
- - -
-
-
-
- - Recent Auth Activity -
- - View All - -
-
- {% if recent_auths %} -
- {% for auth in recent_auths %} -
-
-
- {% if auth.success %} - - {% else %} - - {% endif %} - {{ auth.auth_type|title }} -
- - {{ auth.identifier }} - -
- - {{ auth.timestamp.strftime('%H:%M:%S') }} - -
- - {{ auth.ip_address }} - -
- {% endfor %} -
- {% else %} -
- -

No authentication activity yet

-
- {% endif %} -
-
-
-
- - -
-
-
-
-
- - Quick Actions -
-
- -
-
-
-{% endblock %} - -{% block extra_js %} - -{% endblock %} diff --git a/email_frontend/templates/email/error.html b/email_frontend/templates/email/error.html deleted file mode 100644 index 09cf2e9..0000000 --- a/email_frontend/templates/email/error.html +++ /dev/null @@ -1,173 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Error - SMTP Management{% endblock %} - -{% block content %} -
-
-
-
-
- -
Error Occurred
-
-
-
- {% if error_code %} -
-
Error Code:
-
- {{ error_code }} -
-
- {% endif %} - - {% if error_message %} -
-
Message:
-
-
- {{ error_message }} -
-
-
- {% endif %} - - {% if error_details %} -
-
Details:
-
-
-
{{ error_details }}
-
-
-
- {% endif %} - -
-
Timestamp:
-
- {{ moment().format('YYYY-MM-DD HH:mm:ss') }} -
-
- -
-
Request URL:
-
- {{ request.url if request else 'Unknown' }} -
-
-
- -
-
-
- - -
-
-
-
-
- - Common Solutions -
-
-
-
-
-
Database Issues:
-
    -
  • Check database connection settings
  • -
  • Verify database tables exist
  • -
  • Check database permissions
  • -
-
-
-
Configuration Issues:
-
    -
  • Verify settings.ini file exists
  • -
  • Check file permissions
  • -
  • Validate configuration values
  • -
-
-
-
-
-
Network Issues:
-
    -
  • Check firewall settings
  • -
  • Verify DNS resolution
  • -
  • Test network connectivity
  • -
-
-
-
Permission Issues:
-
    -
  • Check file system permissions
  • -
  • Verify user authentication
  • -
  • Review access controls
  • -
-
-
-
-
-
-
- - -{% endblock %} diff --git a/email_frontend/templates/email/ips.html b/email_frontend/templates/email/ips.html deleted file mode 100644 index 5653e72..0000000 --- a/email_frontend/templates/email/ips.html +++ /dev/null @@ -1,203 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Whitelisted IPs - Email Server{% endblock %} - -{% block content %} -
-
-

- - Whitelisted IP Addresses -

- - - Add IP Address - -
- -
-
-
-
-
- - Whitelisted IP Addresses -
-
-
- {% if ips %} -
- - - - - - - - - - - - {% for ip, domain in ips %} - - - - - - - - {% endfor %} - -
IP AddressDomainStatusAddedActions
-
{{ ip.ip_address }}
-
- {{ domain.domain_name }} - - {% if ip.is_active %} - - - Active - - {% else %} - - - Inactive - - {% endif %} - - - {{ ip.created_at.strftime('%Y-%m-%d %H:%M') }} - - - {% if ip.is_active %} -
- -
- {% else %} - - - - {% endif %} -
-
- {% else %} -
- -

No IP Addresses Whitelisted

-

Add IP addresses to allow authentication without username/password

- - - Add First IP Address - -
- {% endif %} -
-
-
- -
- -
-
-
- - IP Whitelist Information -
-
-
- {% if ips %} -
    -
  • - - Active IPs: {{ ips|selectattr('0.is_active')|list|length }} -
  • -
  • - - Domains covered: {{ ips|map(attribute='1.domain_name')|unique|list|length }} -
  • -
  • - - Latest addition: - {% set latest = ips|map(attribute='0')|max(attribute='created_at') %} - {{ latest.strftime('%Y-%m-%d') if latest else 'N/A' }} -
  • -
- {% endif %} - -
-
- - How IP Whitelisting Works -
-
    -
  • Whitelisted IPs can send emails without username/password authentication
  • -
  • Each IP is associated with a specific domain
  • -
  • IP can only send emails for its authorized domain
  • -
  • Useful for server-to-server email sending
  • -
-
-
-
- - -
-
-
- - Your Current IP -
-
-
-
-
- - Detecting... -
- -
-
-
-
-
-
-{% endblock %} - -{% block extra_js %} - -{% endblock %} diff --git a/email_frontend/templates/email/users.html b/email_frontend/templates/email/users.html deleted file mode 100644 index a64ceae..0000000 --- a/email_frontend/templates/email/users.html +++ /dev/null @@ -1,170 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Users - Email Server Management{% endblock %} -{% block page_title %}User Management{% endblock %} - -{% block content %} -
-

- - Users -

- - - Add User - -
- -
-
-
- - All Users -
-
-
- {% if users %} -
- - - - - - - - - - - - - {% for user, domain in users %} - - - - - - - - - {% endfor %} - -
EmailDomainPermissionsStatusCreatedActions
-
{{ user.email }}
-
- {{ domain.domain_name }} - - {% if user.can_send_as_domain %} - - - Domain Admin - -
- Can send as *@{{ domain.domain_name }} - {% else %} - - - User - -
- Can only send as {{ user.email }} - {% endif %} -
- {% if user.is_active %} - - - Active - - {% else %} - - - Inactive - - {% endif %} - - - {{ user.created_at.strftime('%Y-%m-%d %H:%M') }} - - - {% if user.is_active %} -
- -
- {% else %} - - - - {% endif %} -
-
- {% else %} -
- -

No users configured

-

Add users to enable username/password authentication

- - - Add Your First User - -
- {% endif %} -
-
- -{% if users %} -
-
-
-
-
- - User Statistics -
-
-
-
    -
  • - - Active users: {{ users|selectattr('0.is_active')|list|length }} -
  • -
  • - - Domain admins: {{ users|selectattr('0.can_send_as_domain')|list|length }} -
  • -
  • - - Regular users: {{ users|rejectattr('0.can_send_as_domain')|list|length }} -
  • -
-
-
-
-
-
-
-
- - Permission Levels -
-
-
-
    -
  • - Domain Admin - Can send as any email address in their domain -
  • -
  • - Regular User - Can only send as their own email address -
  • -
-
-
-
-
-{% endif %} -{% endblock %} diff --git a/email_frontend/templates/logs.html b/email_frontend/templates/logs.html deleted file mode 100644 index 6d9f7fd..0000000 --- a/email_frontend/templates/logs.html +++ /dev/null @@ -1,322 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Logs - Email Server{% endblock %} - -{% block extra_css %} - -{% endblock %} - -{% block content %} -
- - -
-
-
-
-
- {% if filter_type == 'emails' %} - - Email Activity - {% elif filter_type == 'auth' %} - - Authentication Activity - {% else %} - - Recent Activity - {% endif %} -
- -
-
- {% if logs %} - {% if filter_type == 'all' %} - - {% for log_entry in logs %} - {% if log_entry.type == 'email' %} - {% set log = log_entry.data %} -
-
-
- EMAIL - {{ log.mail_from }} → {{ log.rcpt_tos }} - {% if log.dkim_signed %} - - - DKIM - - {% endif %} -
- {{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }} -
-
-
- Status: - {% if log.status == 'relayed' %} - Sent Successfully - {% else %} - Failed - {% endif %} -
-
- Message ID: {{ log.message_id }} -
-
- {% if log.subject %} -
- Subject: {{ log.subject }} -
- {% endif %} -
- {% else %} - {% set log = log_entry.data %} -
-
-
- AUTH - {{ log.identifier }} - - {{ 'Success' if log.success else 'Failed' }} - -
- {{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }} -
-
-
- Type: {{ log.auth_type.upper() }} -
-
- IP: {{ log.ip_address or 'N/A' }} -
-
- {% if log.message %} -
- Message: {{ log.message }} -
- {% endif %} -
- {% endif %} - {% endfor %} - {% elif filter_type == 'emails' %} - - {% for log in logs %} -
-
-
- {{ log.mail_from }} → {{ log.rcpt_tos }} - {% if log.dkim_signed %} - - - DKIM - - {% endif %} -
- {{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }} -
-
-
- Status: - {% if log.status == 'relayed' %} - Sent - {% else %} - Failed - {% endif %} -
-
- Peer: {{ log.peer }} -
-
- Message ID: {{ log.message_id }} -
-
- {% if log.subject %} -
- Subject: {{ log.subject }} -
- {% endif %} - {% if log.content and log.content|length > 50 %} -
- -
-
{{ log.content }}
-
-
- {% endif %} -
- {% endfor %} - {% else %} - - {% for log in logs %} -
-
-
- {{ log.identifier }} - - {{ 'Success' if log.success else 'Failed' }} - -
- {{ log.created_at.strftime('%Y-%m-%d %H:%M:%S') }} -
-
-
- Type: {{ log.auth_type.upper() }} -
-
- IP: {{ log.ip_address or 'N/A' }} -
-
- Result: - {% if log.success %} - Authenticated - {% else %} - Rejected - {% endif %} -
-
- {% if log.message %} -
- Details: {{ log.message }} -
- {% endif %} -
- {% endfor %} - {% endif %} - - - {% if has_prev or has_next %} - - {% endif %} - {% else %} -
- -

No Logs Found

-

- {% if filter_type == 'emails' %} - No email activity has been logged yet. - {% elif filter_type == 'auth' %} - No authentication attempts have been logged yet. - {% else %} - No activity has been logged yet. - {% endif %} -

-
- {% endif %} -
-
-
-
-
-{% endblock %} - -{% block extra_js %} - -{% endblock %} diff --git a/email_frontend/templates/settings.html b/email_frontend/templates/settings.html deleted file mode 100644 index f3286ea..0000000 --- a/email_frontend/templates/settings.html +++ /dev/null @@ -1,355 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Server Settings - Email Server{% endblock %} - -{% block extra_css %} - -{% endblock %} - -{% block content %} -
-
-

- - Server Settings -

-
- - -
-
- -
- -
-
-
- - Server Configuration -
-
-
-
-
-
-
- -
Port for SMTP connections (standard: 25, 587)
- -
-
-
-
- -
Port for SMTP over TLS connections (standard: 465)
- -
-
-
-
-
-
- -
IP address to bind the server to (0.0.0.0 for all interfaces)
- -
-
-
-
- -
Server hostname for HELO/EHLO commands
- -
-
-
-
-
-
- - -
-
-
- - Database Configuration -
-
-
-
-
- -
SQLite database file path or connection string
- -
-
-
-
- - -
-
-
- - Logging Configuration -
-
-
-
-
-
-
- -
Minimum log level to record
- -
-
-
-
- -
Reduce verbose logging from aiosmtpd library
- -
-
-
-
-
-
- - -
-
-
- - Email Relay Configuration -
-
-
-
-
- -
Timeout for external SMTP connections when relaying emails
- -
-
-
-
- - -
-
-
- - TLS/SSL Configuration -
-
-
-
-
-
-
- -
Path to SSL certificate file (.crt or .pem)
- -
-
-
-
- -
Path to SSL private key file (.key or .pem)
- -
-
-
-
-
-
- - -
-
-
- - DKIM Configuration -
-
-
-
-
- -
RSA key size for new DKIM keys (larger = more secure, slower)
- -
-
-
-
- - -
-
- - Server restart required after changing settings -
- -
-
-
- - - -{% endblock %} - -{% block extra_js %} - -{% endblock %} diff --git a/email_frontend/README.md b/email_server/server_web_ui/README.md similarity index 100% rename from email_frontend/README.md rename to email_server/server_web_ui/README.md diff --git a/email_frontend/__init__.py b/email_server/server_web_ui/__init__.py similarity index 100% rename from email_frontend/__init__.py rename to email_server/server_web_ui/__init__.py diff --git a/email_frontend/requirements.txt b/email_server/server_web_ui/requirements.txt similarity index 100% rename from email_frontend/requirements.txt rename to email_server/server_web_ui/requirements.txt diff --git a/email_frontend/blueprint.py b/email_server/server_web_ui/routes.py similarity index 56% rename from email_frontend/blueprint.py rename to email_server/server_web_ui/routes.py index 37c8fa1..24da34a 100644 --- a/email_frontend/blueprint.py +++ b/email_server/server_web_ui/routes.py @@ -103,6 +103,7 @@ def generate_spf_record(domain: str, public_ip: str, existing_spf: str = None) - spf_parts = ['v=spf1'] + base_mechanisms + ['~all'] return ' '.join(spf_parts) +# Dashboard and Main Routes @email_bp.route('/') def dashboard(): """Main dashboard showing overview of the email server.""" @@ -128,6 +129,7 @@ def dashboard(): finally: session.close() +# Domain Management Routes @email_bp.route('/domains') def domains_list(): """List all domains.""" @@ -180,7 +182,7 @@ def add_domain(): @email_bp.route('/domains//delete', methods=['POST']) def delete_domain(domain_id: int): - """Delete domain.""" + """Delete domain (soft delete).""" session = Session() try: domain = session.query(Domain).get(domain_id) @@ -192,17 +194,132 @@ def delete_domain(domain_id: int): domain.is_active = False session.commit() - flash(f'Domain {domain_name} deactivated', 'success') + flash(f'Domain {domain_name} disabled', 'success') return redirect(url_for('email.domains_list')) except Exception as e: session.rollback() - logger.error(f"Error deleting domain: {e}") - flash(f'Error deleting domain: {str(e)}', 'error') + logger.error(f"Error disabling domain: {e}") + flash(f'Error disabling domain: {str(e)}', 'error') return redirect(url_for('email.domains_list')) finally: session.close() +@email_bp.route('/domains//edit', methods=['GET', 'POST']) +def edit_domain(domain_id: int): + """Edit domain.""" + session = Session() + try: + domain = session.query(Domain).get(domain_id) + if not domain: + flash('Domain not found', 'error') + return redirect(url_for('email.domains_list')) + + if request.method == 'POST': + domain_name = request.form.get('domain_name', '').strip().lower() + requires_auth = request.form.get('requires_auth') == 'on' + + if not domain_name: + flash('Domain name is required', 'error') + return redirect(url_for('email.edit_domain', domain_id=domain_id)) + + # Basic domain validation + if '.' not in domain_name or len(domain_name.split('.')) < 2: + flash('Invalid domain format', 'error') + return redirect(url_for('email.edit_domain', domain_id=domain_id)) + + # Check if domain name already exists (excluding current domain) + existing = session.query(Domain).filter( + Domain.domain_name == domain_name, + Domain.id != domain_id + ).first() + if existing: + flash(f'Domain {domain_name} already exists', 'error') + return redirect(url_for('email.edit_domain', domain_id=domain_id)) + + old_name = domain.domain_name + domain.domain_name = domain_name + domain.requires_auth = requires_auth + session.commit() + + flash(f'Domain updated from "{old_name}" to "{domain_name}"', 'success') + return redirect(url_for('email.domains_list')) + + return render_template('edit_domain.html', domain=domain) + + except Exception as e: + session.rollback() + logger.error(f"Error editing domain: {e}") + flash(f'Error editing domain: {str(e)}', 'error') + return redirect(url_for('email.domains_list')) + finally: + session.close() + +@email_bp.route('/domains//toggle', methods=['POST']) +def toggle_domain(domain_id: int): + """Toggle domain active status (Enable/Disable).""" + session = Session() + try: + domain = session.query(Domain).get(domain_id) + if not domain: + flash('Domain not found', 'error') + return redirect(url_for('email.domains_list')) + + old_status = domain.is_active + domain.is_active = not old_status + session.commit() + + status_text = "enabled" if domain.is_active else "disabled" + flash(f'Domain {domain.domain_name} has been {status_text}', 'success') + return redirect(url_for('email.domains_list')) + + except Exception as e: + session.rollback() + logger.error(f"Error toggling domain status: {e}") + flash(f'Error toggling domain status: {str(e)}', 'error') + return redirect(url_for('email.domains_list')) + finally: + session.close() + +@email_bp.route('/domains//remove', methods=['POST']) +def remove_domain(domain_id: int): + """Permanently remove domain and all associated data.""" + session = Session() + try: + domain = session.query(Domain).get(domain_id) + if not domain: + flash('Domain not found', 'error') + return redirect(url_for('email.domains_list')) + + domain_name = domain.domain_name + + # Count associated records + user_count = session.query(User).filter_by(domain_id=domain_id).count() + ip_count = session.query(WhitelistedIP).filter_by(domain_id=domain_id).count() + dkim_count = session.query(DKIMKey).filter_by(domain_id=domain_id).count() + + # Delete associated records + session.query(User).filter_by(domain_id=domain_id).delete() + session.query(WhitelistedIP).filter_by(domain_id=domain_id).delete() + session.query(DKIMKey).filter_by(domain_id=domain_id).delete() + session.query(CustomHeader).filter_by(domain_id=domain_id).delete() + + # Delete domain + session.delete(domain) + session.commit() + + flash(f'Domain {domain_name} and all associated data permanently removed ({user_count} users, {ip_count} IPs, {dkim_count} DKIM keys)', 'success') + return redirect(url_for('email.domains_list')) + + except Exception as e: + session.rollback() + logger.error(f"Error removing domain: {e}") + flash(f'Error removing domain: {str(e)}', 'error') + return redirect(url_for('email.domains_list')) + finally: + session.close() + +# User Management Routes @email_bp.route('/users') def users_list(): """List all users.""" @@ -266,7 +383,7 @@ def add_user(): @email_bp.route('/users//delete', methods=['POST']) def delete_user(user_id: int): - """Delete user.""" + """Disable user (soft delete).""" session = Session() try: user = session.query(User).get(user_id) @@ -278,17 +395,128 @@ def delete_user(user_id: int): user.is_active = False session.commit() - flash(f'User {user_email} deactivated', 'success') + flash(f'User {user_email} disabled', 'success') return redirect(url_for('email.users_list')) except Exception as e: session.rollback() - logger.error(f"Error deleting user: {e}") - flash(f'Error deleting user: {str(e)}', 'error') + logger.error(f"Error disabling user: {e}") + flash(f'Error disabling user: {str(e)}', 'error') return redirect(url_for('email.users_list')) finally: session.close() +@email_bp.route('/users//enable', methods=['POST']) +def enable_user(user_id: int): + """Enable user.""" + session = Session() + try: + user = session.query(User).get(user_id) + if not user: + flash('User not found', 'error') + return redirect(url_for('email.users_list')) + + user_email = user.email + user.is_active = True + session.commit() + + flash(f'User {user_email} enabled', 'success') + return redirect(url_for('email.users_list')) + + except Exception as e: + session.rollback() + logger.error(f"Error enabling user: {e}") + flash(f'Error enabling user: {str(e)}', 'error') + return redirect(url_for('email.users_list')) + finally: + session.close() + +@email_bp.route('/users//remove', methods=['POST']) +def remove_user(user_id: int): + """Permanently remove user.""" + session = Session() + try: + user = session.query(User).get(user_id) + if not user: + flash('User not found', 'error') + return redirect(url_for('email.users_list')) + + user_email = user.email + session.delete(user) + session.commit() + + flash(f'User {user_email} permanently removed', 'success') + return redirect(url_for('email.users_list')) + + except Exception as e: + session.rollback() + logger.error(f"Error removing user: {e}") + flash(f'Error removing user: {str(e)}', 'error') + return redirect(url_for('email.users_list')) + finally: + session.close() + +@email_bp.route('/users//edit', methods=['GET', 'POST']) +def edit_user(user_id: int): + """Edit user.""" + session = Session() + try: + user = session.query(User).get(user_id) + if not user: + flash('User not found', 'error') + return redirect(url_for('email.users_list')) + + domains = session.query(Domain).filter_by(is_active=True).order_by(Domain.domain_name).all() + + if request.method == 'POST': + email = request.form.get('email', '').strip().lower() + password = request.form.get('password', '').strip() + domain_id = request.form.get('domain_id', type=int) + can_send_as_domain = request.form.get('can_send_as_domain') == 'on' + + if not all([email, domain_id]): + flash('Email and domain are required', 'error') + return redirect(url_for('email.edit_user', user_id=user_id)) + + # Email validation + if '@' not in email or '.' not in email.split('@')[1]: + flash('Invalid email format', 'error') + return redirect(url_for('email.edit_user', user_id=user_id)) + + # Check if email already exists (excluding current user) + existing = session.query(User).filter( + User.email == email, + User.id != user_id + ).first() + if existing: + flash(f'Email {email} already exists', 'error') + return redirect(url_for('email.edit_user', user_id=user_id)) + + # Update user + user.email = email + user.domain_id = domain_id + user.can_send_as_domain = can_send_as_domain + + # Update password if provided + if password: + user.password_hash = hash_password(password) + + session.commit() + + flash(f'User {email} updated successfully', 'success') + return redirect(url_for('email.users_list')) + + return render_template('edit_user.html', user=user, domains=domains) + + except Exception as e: + session.rollback() + logger.error(f"Error editing user: {e}") + flash(f'Error editing user: {str(e)}', 'error') + return redirect(url_for('email.users_list')) + finally: + session.close() + +# IP Management Routes @email_bp.route('/ips') def ips_list(): """List all whitelisted IPs.""" @@ -350,7 +578,7 @@ def add_ip(): @email_bp.route('/ips//delete', methods=['POST']) def delete_ip(ip_id: int): - """Delete whitelisted IP.""" + """Disable whitelisted IP (soft delete).""" session = Session() try: ip_record = session.query(WhitelistedIP).get(ip_id) @@ -362,17 +590,123 @@ def delete_ip(ip_id: int): ip_record.is_active = False session.commit() - flash(f'IP {ip_address} removed from whitelist', 'success') + flash(f'IP {ip_address} disabled', 'success') return redirect(url_for('email.ips_list')) except Exception as e: session.rollback() - logger.error(f"Error deleting IP: {e}") - flash(f'Error deleting IP: {str(e)}', 'error') + logger.error(f"Error disabling IP: {e}") + flash(f'Error disabling IP: {str(e)}', 'error') return redirect(url_for('email.ips_list')) finally: session.close() +@email_bp.route('/ips//enable', methods=['POST']) +def enable_ip(ip_id: int): + """Enable whitelisted IP.""" + session = Session() + try: + ip_record = session.query(WhitelistedIP).get(ip_id) + if not ip_record: + flash('IP record not found', 'error') + return redirect(url_for('email.ips_list')) + + ip_address = ip_record.ip_address + ip_record.is_active = True + session.commit() + + flash(f'IP {ip_address} enabled', 'success') + return redirect(url_for('email.ips_list')) + + except Exception as e: + session.rollback() + logger.error(f"Error enabling IP: {e}") + flash(f'Error enabling IP: {str(e)}', 'error') + return redirect(url_for('email.ips_list')) + finally: + session.close() + +@email_bp.route('/ips//remove', methods=['POST']) +def remove_ip(ip_id: int): + """Permanently remove whitelisted IP.""" + session = Session() + try: + ip_record = session.query(WhitelistedIP).get(ip_id) + if not ip_record: + flash('IP record not found', 'error') + return redirect(url_for('email.ips_list')) + + ip_address = ip_record.ip_address + session.delete(ip_record) + session.commit() + + flash(f'IP {ip_address} permanently removed', 'success') + return redirect(url_for('email.ips_list')) + + except Exception as e: + session.rollback() + logger.error(f"Error removing IP: {e}") + flash(f'Error removing IP: {str(e)}', 'error') + return redirect(url_for('email.ips_list')) + finally: + session.close() + +@email_bp.route('/ips//edit', methods=['GET', 'POST']) +def edit_ip(ip_id: int): + """Edit whitelisted IP.""" + session = Session() + try: + ip_record = session.query(WhitelistedIP).get(ip_id) + if not ip_record: + flash('IP record not found', 'error') + return redirect(url_for('email.ips_list')) + + domains = session.query(Domain).filter_by(is_active=True).order_by(Domain.domain_name).all() + + if request.method == 'POST': + ip_address = request.form.get('ip_address', '').strip() + domain_id = request.form.get('domain_id', type=int) + + if not all([ip_address, domain_id]): + flash('All fields are required', 'error') + return redirect(url_for('email.edit_ip', ip_id=ip_id)) + + # Basic IP validation + try: + socket.inet_aton(ip_address) + except socket.error: + flash('Invalid IP address format', 'error') + return redirect(url_for('email.edit_ip', ip_id=ip_id)) + + # Check if IP already exists for this domain (excluding current record) + existing = session.query(WhitelistedIP).filter( + WhitelistedIP.ip_address == ip_address, + WhitelistedIP.domain_id == domain_id, + WhitelistedIP.id != ip_id + ).first() + if existing: + flash(f'IP {ip_address} already whitelisted for this domain', 'error') + return redirect(url_for('email.edit_ip', ip_id=ip_id)) + + # Update IP record + ip_record.ip_address = ip_address + ip_record.domain_id = domain_id + session.commit() + + flash(f'IP whitelist record updated', 'success') + return redirect(url_for('email.ips_list')) + + return render_template('edit_ip.html', ip_record=ip_record, domains=domains) + + except Exception as e: + session.rollback() + logger.error(f"Error editing IP: {e}") + flash(f'Error editing IP: {str(e)}', 'error') + return redirect(url_for('email.ips_list')) + finally: + session.close() + +# DKIM Management Routes @email_bp.route('/dkim') def dkim_list(): """List all DKIM keys and DNS records.""" @@ -449,20 +783,9 @@ def regenerate_dkim(domain_id: int): finally: session.close() -@email_bp.route('/dkim//update_selector', methods=['POST']) -def update_dkim_selector(dkim_id: int): - """Update DKIM selector name.""" - new_selector = request.form.get('selector', '').strip() - - if not new_selector: - flash('Selector name is required', 'error') - return redirect(url_for('email.dkim_list')) - - # Validate selector (alphanumeric only) - if not re.match(r'^[a-zA-Z0-9]+$', new_selector): - flash('Selector must contain only letters and numbers', 'error') - return redirect(url_for('email.dkim_list')) - +@email_bp.route('/dkim//edit', methods=['GET', 'POST']) +def edit_dkim(dkim_id: int): + """Edit DKIM key selector.""" session = Session() try: dkim_key = session.query(DKIMKey).get(dkim_id) @@ -470,21 +793,103 @@ def update_dkim_selector(dkim_id: int): flash('DKIM key not found', 'error') return redirect(url_for('email.dkim_list')) - old_selector = dkim_key.selector - dkim_key.selector = new_selector - session.commit() + domain = session.query(Domain).get(dkim_key.domain_id) - flash(f'DKIM selector updated from {old_selector} to {new_selector}', 'success') - return redirect(url_for('email.dkim_list')) + if request.method == 'POST': + new_selector = request.form.get('selector', '').strip() + + if not new_selector: + flash('Selector name is required', 'error') + return render_template('edit_dkim.html', dkim_key=dkim_key, domain=domain) + + # Validate selector (alphanumeric only) + if not re.match(r'^[a-zA-Z0-9_-]+$', new_selector): + flash('Selector must contain only letters, numbers, hyphens, and underscores', 'error') + return render_template('edit_dkim.html', dkim_key=dkim_key, domain=domain) + + # Check for duplicate selector in same domain + existing = session.query(DKIMKey).filter_by( + domain_id=dkim_key.domain_id, + selector=new_selector, + is_active=True + ).filter(DKIMKey.id != dkim_id).first() + + if existing: + flash(f'A DKIM key with selector "{new_selector}" already exists for this domain', 'error') + return render_template('edit_dkim.html', dkim_key=dkim_key, domain=domain) + + old_selector = dkim_key.selector + dkim_key.selector = new_selector + session.commit() + + flash(f'DKIM selector updated from "{old_selector}" to "{new_selector}" for {domain.domain_name}', 'success') + return redirect(url_for('email.dkim_list')) + + return render_template('edit_dkim.html', dkim_key=dkim_key, domain=domain) except Exception as e: session.rollback() - logger.error(f"Error updating DKIM selector: {e}") - flash(f'Error updating DKIM selector: {str(e)}', 'error') + logger.error(f"Error editing DKIM: {e}") + flash(f'Error editing DKIM key: {str(e)}', 'error') return redirect(url_for('email.dkim_list')) finally: session.close() +@email_bp.route('/dkim//toggle', methods=['POST']) +def toggle_dkim(dkim_id: int): + """Toggle DKIM key active status (Enable/Disable).""" + session = Session() + try: + dkim_key = session.query(DKIMKey).get(dkim_id) + if not dkim_key: + flash('DKIM key not found', 'error') + return redirect(url_for('email.dkim_list')) + + domain = session.query(Domain).get(dkim_key.domain_id) + old_status = dkim_key.is_active + dkim_key.is_active = not old_status + session.commit() + + status_text = "enabled" if dkim_key.is_active else "disabled" + flash(f'DKIM key for {domain.domain_name} (selector: {dkim_key.selector}) has been {status_text}', 'success') + return redirect(url_for('email.dkim_list')) + + except Exception as e: + session.rollback() + logger.error(f"Error toggling DKIM status: {e}") + flash(f'Error toggling DKIM status: {str(e)}', 'error') + return redirect(url_for('email.dkim_list')) + finally: + session.close() + +@email_bp.route('/dkim//remove', methods=['POST']) +def remove_dkim(dkim_id: int): + """Permanently remove DKIM key.""" + session = Session() + try: + dkim_key = session.query(DKIMKey).get(dkim_id) + if not dkim_key: + flash('DKIM key not found', 'error') + return redirect(url_for('email.dkim_list')) + + domain = session.query(Domain).get(dkim_key.domain_id) + selector = dkim_key.selector + + session.delete(dkim_key) + session.commit() + + flash(f'DKIM key for {domain.domain_name} (selector: {selector}) has been permanently removed', 'success') + return redirect(url_for('email.dkim_list')) + + except Exception as e: + session.rollback() + logger.error(f"Error removing DKIM key: {e}") + flash(f'Error removing DKIM key: {str(e)}', 'error') + return redirect(url_for('email.dkim_list')) + finally: + session.close() + +# AJAX DNS Check Routes @email_bp.route('/dkim/check_dns', methods=['POST']) def check_dkim_dns(): """Check DKIM DNS record via AJAX.""" @@ -527,6 +932,7 @@ def check_spf_dns(): return jsonify(result) +# Settings Routes @email_bp.route('/settings') def settings(): """Display and edit server settings.""" @@ -560,6 +966,7 @@ def update_settings(): flash(f'Error updating settings: {str(e)}', 'error') return redirect(url_for('email.settings')) +# Logs Routes @email_bp.route('/logs') def logs(): """Display email and authentication logs.""" @@ -629,7 +1036,6 @@ def logs(): @email_bp.errorhandler(404) def not_found(error): """Handle 404 errors.""" - from datetime import datetime return render_template('error.html', error_code=404, error_message='Page not found', @@ -638,7 +1044,6 @@ def not_found(error): @email_bp.errorhandler(500) def internal_error(error): """Handle 500 errors.""" - from datetime import datetime logger.error(f"Internal error: {error}") return render_template('error.html', error_code=500, diff --git a/email_frontend/static/css/smtp-management.css b/email_server/server_web_ui/static/css/smtp-management.css similarity index 100% rename from email_frontend/static/css/smtp-management.css rename to email_server/server_web_ui/static/css/smtp-management.css diff --git a/email_frontend/static/js/smtp-management.js b/email_server/server_web_ui/static/js/smtp-management.js similarity index 100% rename from email_frontend/static/js/smtp-management.js rename to email_server/server_web_ui/static/js/smtp-management.js diff --git a/email_frontend/templates/add_domain.html b/email_server/server_web_ui/templates/add_domain.html similarity index 100% rename from email_frontend/templates/add_domain.html rename to email_server/server_web_ui/templates/add_domain.html diff --git a/email_frontend/templates/add_ip.html b/email_server/server_web_ui/templates/add_ip.html similarity index 100% rename from email_frontend/templates/add_ip.html rename to email_server/server_web_ui/templates/add_ip.html diff --git a/email_frontend/templates/add_user.html b/email_server/server_web_ui/templates/add_user.html similarity index 100% rename from email_frontend/templates/add_user.html rename to email_server/server_web_ui/templates/add_user.html diff --git a/email_frontend/templates/base.html b/email_server/server_web_ui/templates/base.html similarity index 100% rename from email_frontend/templates/base.html rename to email_server/server_web_ui/templates/base.html diff --git a/email_frontend/templates/dashboard.html b/email_server/server_web_ui/templates/dashboard.html similarity index 100% rename from email_frontend/templates/dashboard.html rename to email_server/server_web_ui/templates/dashboard.html diff --git a/email_frontend/templates/email/dkim.html b/email_server/server_web_ui/templates/dkim.html similarity index 78% rename from email_frontend/templates/email/dkim.html rename to email_server/server_web_ui/templates/dkim.html index d5bc82e..2787a00 100644 --- a/email_frontend/templates/email/dkim.html +++ b/email_server/server_web_ui/templates/dkim.html @@ -6,6 +6,7 @@