confimration mail at end of launch process

This commit is contained in:
2025-06-20 13:36:45 +02:00
parent c9d1d7416b
commit f825bab894
5 changed files with 192 additions and 28 deletions

Binary file not shown.

View File

@@ -550,8 +550,12 @@ def generate_password_reset_token(current_user, user_id):
db.session.add(reset_token) db.session.add(reset_token)
db.session.commit() db.session.commit()
# Get the instance URL from the request data or use the current host
data = request.get_json() or {}
instance_url = data.get('instance_url', request.host_url.rstrip('/'))
# Return the token and reset URL # Return the token and reset URL
reset_url = f"{request.host_url.rstrip('/')}/reset-password/{token}" reset_url = f"{instance_url}/reset-password/{token}"
return jsonify({ return jsonify({
'message': 'Password reset token generated successfully', 'message': 'Password reset token generated successfully',

View File

@@ -4,11 +4,15 @@ from models import (
Instance Instance
) )
from extensions import db, csrf from extensions import db, csrf
from routes.admin_api import token_required
import json
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.utils import formatdate
from datetime import datetime from datetime import datetime
import requests import requests
import base64 import base64
from routes.admin_api import token_required
import json
launch_api = Blueprint('launch_api', __name__) launch_api = Blueprint('launch_api', __name__)
@@ -1117,9 +1121,9 @@ def update_admin_credentials():
users_data = users_response.json() users_data = users_response.json()
admin_user = None admin_user = None
# Find the administrator user # Find the administrator user by role (since email was already updated)
for user in users_data: for user in users_data:
if user.get('email') == email: if user.get('is_admin') == True:
admin_user = user admin_user = user
break break
@@ -1291,9 +1295,9 @@ def send_completion_email():
users_data = users_response.json() users_data = users_response.json()
admin_user = None admin_user = None
# Find the administrator user by email # Find the administrator user by role (since email was already updated)
for user in users_data: for user in users_data:
if user.get('email') == credentials_data.get('email'): if user.get('is_admin') == True:
admin_user = user admin_user = user
break break
@@ -1307,8 +1311,10 @@ def send_completion_email():
f"{instance_url.rstrip('/')}/api/admin/generate-password-reset/{admin_user_id}", f"{instance_url.rstrip('/')}/api/admin/generate-password-reset/{admin_user_id}",
headers={ headers={
'Authorization': f'Bearer {jwt_token}', 'Authorization': f'Bearer {jwt_token}',
'Accept': 'application/json' 'Accept': 'application/json',
'Content-Type': 'application/json'
}, },
json={'instance_url': instance_url},
timeout=10 timeout=10
) )
@@ -1364,8 +1370,8 @@ def send_completion_email():
<div class="credentials"> <div class="credentials">
<h3>🔐 Account Access</h3> <h3>🔐 Account Access</h3>
<p><strong>Email Address:</strong> {credentials_data.get('email', 'Not set')}</p> <p><strong>Email Address:</strong> {admin_user.get('email', 'Not set')}</p>
<p><strong>Username:</strong> {credentials_data.get('username', 'administrator')}</p> <p><strong>Username:</strong> {admin_user.get('username', 'administrator')}</p>
<div class="security-notice"> <div class="security-notice">
<h4>🔒 Security Setup Required</h4> <h4>🔒 Security Setup Required</h4>
@@ -1424,8 +1430,8 @@ INSTANCE DETAILS:
- Deployment Date: {datetime.utcnow().strftime('%B %d, %Y at %I:%M %p UTC')} - Deployment Date: {datetime.utcnow().strftime('%B %d, %Y at %I:%M %p UTC')}
ACCOUNT ACCESS: ACCOUNT ACCESS:
- Email Address: {credentials_data.get('email', 'Not set')} - Email Address: {admin_user.get('email', 'Not set')}
- Username: {credentials_data.get('username', 'administrator')} - Username: {admin_user.get('username', 'administrator')}
SECURITY SETUP REQUIRED: SECURITY SETUP REQUIRED:
For your security, you need to set up your password. For your security, you need to set up your password.
@@ -1454,15 +1460,36 @@ Thank you for choosing DocuPulse!
""" """
# Send email using master instance's email system # Send email using master instance's email system
from utils.email_templates import send_email
try: try:
send_email( # Get SMTP settings
to_email=company_data.get('email'), smtp_settings = KeyValueSettings.get_value('smtp_settings')
subject=subject, if not smtp_settings:
html_content=html_content, return jsonify({'error': 'SMTP settings not configured'}), 400
text_content=text_content
) # Create message
msg = MIMEMultipart()
msg['From'] = f"{smtp_settings.get('smtp_from_name', 'DocuPulse')} <{smtp_settings.get('smtp_from_email')}>"
msg['To'] = company_data.get('email')
msg['Subject'] = subject
msg['Date'] = formatdate(localtime=True)
# Add HTML content
msg.attach(MIMEText(html_content, 'html'))
# Send email
if smtp_settings.get('smtp_security') == 'ssl':
server = smtplib.SMTP_SSL(smtp_settings.get('smtp_host'), smtp_settings.get('smtp_port'))
else:
server = smtplib.SMTP(smtp_settings.get('smtp_host'), smtp_settings.get('smtp_port'))
if smtp_settings.get('smtp_security') == 'tls':
server.starttls()
if smtp_settings.get('smtp_username') and smtp_settings.get('smtp_password'):
server.login(smtp_settings.get('smtp_username'), smtp_settings.get('smtp_password'))
server.send_message(msg)
server.quit()
# Log the email sending # Log the email sending
current_app.logger.info(f"Completion email sent to {company_data.get('email')} for instance {instance_url}") current_app.logger.info(f"Completion email sent to {company_data.get('email')} for instance {instance_url}")

View File

@@ -672,10 +672,6 @@ async function startLaunch(data) {
<td>Email Address</td> <td>Email Address</td>
<td>${credentialsResult.data.email || 'Not set'}</td> <td>${credentialsResult.data.email || 'Not set'}</td>
</tr> </tr>
<tr>
<td>Password</td>
<td><code class="text-primary">${credentialsResult.data.password || 'Not set'}</code></td>
</tr>
<tr> <tr>
<td>Username</td> <td>Username</td>
<td>${credentialsResult.data.username || 'administrator'}</td> <td>${credentialsResult.data.username || 'administrator'}</td>
@@ -684,6 +680,10 @@ async function startLaunch(data) {
<td>Role</td> <td>Role</td>
<td><span class="badge bg-primary">Administrator</span></td> <td><span class="badge bg-primary">Administrator</span></td>
</tr> </tr>
<tr>
<td>Password Setup</td>
<td><span class="badge bg-warning">Reset Link Pending</span></td>
</tr>
<tr> <tr>
<td>Status</td> <td>Status</td>
<td><span class="badge bg-${credentialsResult.data.already_updated ? 'info' : 'success'}">${credentialsResult.data.already_updated ? 'Already Updated' : 'Updated'}</span></td> <td><span class="badge bg-${credentialsResult.data.already_updated ? 'info' : 'success'}">${credentialsResult.data.already_updated ? 'Already Updated' : 'Updated'}</span></td>
@@ -693,10 +693,10 @@ async function startLaunch(data) {
</div> </div>
<div class="alert alert-${credentialsResult.data.already_updated ? 'info' : 'warning'} mt-3"> <div class="alert alert-${credentialsResult.data.already_updated ? 'info' : 'warning'} mt-3">
<i class="fas fa-${credentialsResult.data.already_updated ? 'info-circle' : 'exclamation-triangle'} me-2"></i> <i class="fas fa-${credentialsResult.data.already_updated ? 'info-circle' : 'exclamation-triangle'} me-2"></i>
<strong>${credentialsResult.data.already_updated ? 'Note:' : 'Important:'}</strong> <strong>${credentialsResult.data.already_updated ? 'Note:' : 'Security Setup Required:'}</strong>
${credentialsResult.data.already_updated ? ${credentialsResult.data.already_updated ?
'Admin credentials were already updated from the default settings.' : 'Admin credentials were already updated from the default settings.' :
'Please save these credentials securely. The password will not be shown again.'} 'A secure password reset link will be sent to the admin email address in the completion email.'}
</div> </div>
<div class="alert alert-info mt-2"> <div class="alert alert-info mt-2">
<i class="fas fa-info-circle me-2"></i> <i class="fas fa-info-circle me-2"></i>
@@ -753,13 +753,146 @@ async function startLaunch(data) {
<td>Instance URL</td> <td>Instance URL</td>
<td><a href="https://${data.webAddresses[0]}" target="_blank">https://${data.webAddresses[0]}</a></td> <td><a href="https://${data.webAddresses[0]}" target="_blank">https://${data.webAddresses[0]}</a></td>
</tr> </tr>
<tr>
<td>Password Reset</td>
<td><span class="badge bg-success">Link Included</span></td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Email Content Dropdown -->
<div class="accordion mt-3" id="emailContentAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="emailContentHeader">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#emailContentCollapse" aria-expanded="false" aria-controls="emailContentCollapse">
<i class="fas fa-envelope me-2"></i>View Sent Email Content
</button>
</h2>
<div id="emailContentCollapse" class="accordion-collapse collapse" aria-labelledby="emailContentHeader" data-bs-parent="#emailContentAccordion">
<div class="accordion-body">
<div class="row">
<div class="col-md-6">
<h6 class="text-primary">HTML Version</h6>
<div class="border rounded p-3 bg-light" style="max-height: 400px; overflow-y: auto; font-size: 0.9em;">
<div class="email-preview">
<div style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="background-color: #16767b; color: white; padding: 20px; text-align: center; border-radius: 5px 5px 0 0;">
<h1>🎉 Your DocuPulse Instance is Ready!</h1>
</div>
<div style="background-color: #f9f9f9; padding: 20px; border-radius: 0 0 5px 5px;">
<p>Dear ${data.company.name || 'Valued Customer'},</p>
<p>Great news! Your DocuPulse instance has been successfully deployed and configured.</p>
<div style="background-color: #e3f2fd; padding: 15px; border-radius: 5px; margin: 15px 0;">
<h3>📋 Instance Details</h3>
<p><strong>Instance URL:</strong> <a href="https://${data.webAddresses[0]}">https://${data.webAddresses[0]}</a></p>
<p><strong>Company Name:</strong> ${data.company.name || 'Not set'}</p>
<p><strong>Industry:</strong> ${data.company.industry || 'Not set'}</p>
<p><strong>Deployment Date:</strong> ${new Date().toLocaleString()}</p>
</div>
<div style="background-color: #e8f5e9; padding: 15px; border-radius: 5px; margin: 15px 0;">
<h3>🔐 Account Access</h3>
<p><strong>Email Address:</strong> ${data.company.email || 'Not set'}</p>
<p><strong>Username:</strong> administrator</p>
<div style="background-color: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 15px 0;">
<h4>🔒 Security Setup Required</h4>
<p>For your security, you need to set up your password. Click the button below to create your secure password.</p>
<p><strong>Password Reset Link Expires:</strong> 24 hours</p>
</div>
</div>
<div style="text-align: center; margin: 30px 0;">
<a href="#" class="btn btn-primary" style="background-color: #16767b; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin: 10px 0;">🔐 Set Up Your Password</a>
<br><br>
<a href="https://${data.webAddresses[0]}" class="btn btn-primary" style="background-color: #16767b; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin: 10px 0;">🚀 Access Your Instance</a>
</div>
<h3>✅ What's Been Configured</h3>
<ul>
<li>✅ Secure SSL certificate for HTTPS access</li>
<li>✅ Company information and branding</li>
<li>✅ Custom color scheme</li>
<li>✅ Admin account created</li>
<li>✅ Document management system ready</li>
</ul>
<h3>🎯 Next Steps</h3>
<ol>
<li>Click the "Set Up Your Password" button above</li>
<li>Create your secure password</li>
<li>Return to your instance and log in</li>
<li>Explore your new DocuPulse platform</li>
<li>Start uploading and organizing your documents</li>
<li>Invite team members to collaborate</li>
</ol>
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 12px; color: #666;">
<p>If you have any questions or need assistance, please don't hesitate to contact our support team.</p>
<p>Thank you for choosing DocuPulse!</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<h6 class="text-primary">Plain Text Version</h6>
<div class="border rounded p-3 bg-light" style="max-height: 400px; overflow-y: auto; font-family: monospace; font-size: 0.85em; white-space: pre-wrap;">
Your DocuPulse Instance is Ready!
Dear ${data.company.name || 'Valued Customer'},
Great news! Your DocuPulse instance has been successfully deployed and configured.
INSTANCE DETAILS:
- Instance URL: https://${data.webAddresses[0]}
- Company Name: ${data.company.name || 'Not set'}
- Industry: ${data.company.industry || 'Not set'}
- Deployment Date: ${new Date().toLocaleString()}
ACCOUNT ACCESS:
- Email Address: ${data.company.email || 'Not set'}
- Username: administrator
SECURITY SETUP REQUIRED:
For your security, you need to set up your password.
Password Reset Link: [Secure reset link included]
Password Reset Link Expires: 24 hours
WHAT'S BEEN CONFIGURED:
✓ Secure SSL certificate for HTTPS access
✓ Company information and branding
✓ Custom color scheme
✓ Admin account created
✓ Document management system ready
NEXT STEPS:
1. Click the password reset link above
2. Create your secure password
3. Return to your instance and log in
4. Explore your new DocuPulse platform
5. Start uploading and organizing your documents
6. Invite team members to collaborate
If you have any questions or need assistance, please don't hesitate to contact our support team.
Thank you for choosing DocuPulse!
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="alert alert-success mt-3"> <div class="alert alert-success mt-3">
<i class="fas fa-check-circle me-2"></i> <i class="fas fa-check-circle me-2"></i>
<strong>Launch Complete!</strong> Your DocuPulse instance has been successfully deployed and configured. <strong>Launch Complete!</strong> Your DocuPulse instance has been successfully deployed and configured.
The client has been notified via email with all necessary login information. The client has been notified via email with all necessary login information and a secure password reset link.
</div> </div>
</div> </div>
</div> </div>