document.addEventListener('DOMContentLoaded', function() { // Get the launch data from sessionStorage const launchData = JSON.parse(sessionStorage.getItem('instanceLaunchData')); if (!launchData) { showError('No launch data found. Please start over.'); return; } // Initialize the steps initializeSteps(); // Start the launch process startLaunch(launchData); }); function initializeSteps() { const stepsContainer = document.getElementById('stepsContainer'); // Add Cloudflare connection check step const cloudflareStep = document.createElement('div'); cloudflareStep.className = 'step-item'; cloudflareStep.innerHTML = `
Checking Cloudflare Connection

Verifying Cloudflare API connection...

`; stepsContainer.appendChild(cloudflareStep); // Add DNS record creation step const dnsCreateStep = document.createElement('div'); dnsCreateStep.className = 'step-item'; dnsCreateStep.innerHTML = `
Creating DNS Records

Setting up domain DNS records in Cloudflare...

`; stepsContainer.appendChild(dnsCreateStep); // Add DNS check step const dnsStep = document.createElement('div'); dnsStep.className = 'step-item'; dnsStep.innerHTML = `
Checking DNS Records

Verifying domain configurations...

`; stepsContainer.appendChild(dnsStep); // Add NGINX connection check step const nginxStep = document.createElement('div'); nginxStep.className = 'step-item'; nginxStep.innerHTML = `
Checking NGINX Connection

Verifying connection to NGINX Proxy Manager...

`; stepsContainer.appendChild(nginxStep); // Add SSL Certificate generation step const sslStep = document.createElement('div'); sslStep.className = 'step-item'; sslStep.innerHTML = `
Generating SSL Certificate

Setting up secure HTTPS connection...

`; stepsContainer.appendChild(sslStep); // Add Proxy Host creation step const proxyStep = document.createElement('div'); proxyStep.className = 'step-item'; proxyStep.innerHTML = `
Creating Proxy Host

Setting up NGINX proxy host configuration...

`; stepsContainer.appendChild(proxyStep); // Add Portainer connection check step const portainerStep = document.createElement('div'); portainerStep.className = 'step-item'; portainerStep.innerHTML = `
Checking Portainer Connection

Verifying connection to Portainer...

`; stepsContainer.appendChild(portainerStep); // Add Docker Compose download step const dockerComposeStep = document.createElement('div'); dockerComposeStep.className = 'step-item'; dockerComposeStep.innerHTML = `
Downloading Docker Compose

Fetching docker-compose.yml from repository...

`; stepsContainer.appendChild(dockerComposeStep); // Add Portainer stack deployment step const stackDeployStep = document.createElement('div'); stackDeployStep.className = 'step-item'; stackDeployStep.innerHTML = `
Deploying Stack

Launching your application stack...

`; stepsContainer.appendChild(stackDeployStep); // Add Save Instance Data step const saveDataStep = document.createElement('div'); saveDataStep.className = 'step-item'; saveDataStep.innerHTML = `
Saving Instance Data

Storing instance information...

`; stepsContainer.appendChild(saveDataStep); // Add Health Check step const healthStep = document.createElement('div'); healthStep.className = 'step-item'; healthStep.innerHTML = `
Health Check

Verifying instance health...

`; stepsContainer.appendChild(healthStep); // Add Authentication step const authStep = document.createElement('div'); authStep.className = 'step-item'; authStep.innerHTML = `
Instance Authentication

Setting up instance authentication...

`; stepsContainer.appendChild(authStep); // Add Apply Company Information step const companyStep = document.createElement('div'); companyStep.className = 'step-item'; companyStep.innerHTML = `
Apply Company Information

Configuring company details...

`; stepsContainer.appendChild(companyStep); // Add Apply Colors step const colorsStep = document.createElement('div'); colorsStep.className = 'step-item'; colorsStep.innerHTML = `
Apply Colors

Configuring color scheme...

`; stepsContainer.appendChild(colorsStep); // Add Update Admin Credentials step const credentialsStep = document.createElement('div'); credentialsStep.className = 'step-item'; credentialsStep.innerHTML = `
Update Admin Credentials

Setting up admin account...

`; stepsContainer.appendChild(credentialsStep); // Add Copy SMTP Settings step const smtpStep = document.createElement('div'); smtpStep.className = 'step-item'; smtpStep.innerHTML = `
Copy SMTP Settings

Configuring email settings...

`; stepsContainer.appendChild(smtpStep); // Add Send Completion Email step const emailStep = document.createElement('div'); emailStep.className = 'step-item'; emailStep.innerHTML = `
Send Completion Email

Sending notification to client...

`; stepsContainer.appendChild(emailStep); } async function startLaunch(data) { try { // Step 1: Check Cloudflare connection await updateStep(1, 'Checking Cloudflare Connection', 'Verifying Cloudflare API connection...'); const cloudflareResult = await checkCloudflareConnection(); if (!cloudflareResult.success) { throw new Error(cloudflareResult.error || 'Failed to connect to Cloudflare'); } // Update the step to show success const cloudflareStep = document.querySelectorAll('.step-item')[0]; cloudflareStep.classList.remove('active'); cloudflareStep.classList.add('completed'); cloudflareStep.querySelector('.step-status').textContent = `Successfully connected to Cloudflare (${cloudflareResult.zone_name})`; // Step 2: Create DNS records await updateStep(2, 'Creating DNS Records', 'Setting up domain DNS records in Cloudflare...'); const dnsCreateResult = await createDNSRecords(data.webAddresses); if (!dnsCreateResult.success) { throw new Error(dnsCreateResult.error || 'Failed to create DNS records'); } // Update the step to show success const dnsCreateStep = document.querySelectorAll('.step-item')[1]; dnsCreateStep.classList.remove('active'); dnsCreateStep.classList.add('completed'); dnsCreateStep.querySelector('.step-status').textContent = 'DNS records created successfully'; // Add DNS creation details const dnsCreateDetails = document.createElement('div'); dnsCreateDetails.className = 'mt-3'; dnsCreateDetails.innerHTML = `
DNS Record Creation Results
${Object.entries(dnsCreateResult.results).map(([domain, result]) => ` `).join('')}
Domain Status Message
${domain} ${result.status} ${result.message}
`; dnsCreateStep.querySelector('.step-content').appendChild(dnsCreateDetails); // Step 3: Check DNS records await updateStep(3, 'Checking DNS Records', 'Verifying domain configurations...'); const dnsResult = await checkDNSRecords(data.webAddresses); // Check if any domains failed to resolve const failedDomains = Object.entries(dnsResult.results) .filter(([_, result]) => !result.resolved) .map(([domain]) => domain); if (failedDomains.length > 0) { throw new Error(`DNS records not found for: ${failedDomains.join(', ')}`); } // Update the step to show success const dnsStep = document.querySelectorAll('.step-item')[2]; dnsStep.classList.remove('active'); dnsStep.classList.add('completed'); // Create a details section for DNS results const detailsSection = document.createElement('div'); detailsSection.className = 'mt-3'; detailsSection.innerHTML = `
DNS Check Results
${Object.entries(dnsResult.results).map(([domain, result]) => ` `).join('')}
Domain Status IP Address TTL
${domain} ${result.resolved ? 'Resolved' : 'Not Found'} ${result.ip || 'N/A'} ${result.ttl || 'N/A'}
`; // Add the details section after the status text const statusText = dnsStep.querySelector('.step-status'); statusText.textContent = 'DNS records verified successfully'; statusText.after(detailsSection); // Step 4: Check NGINX connection await updateStep(4, 'Checking NGINX Connection', 'Verifying connection to NGINX Proxy Manager...'); const nginxResult = await checkNginxConnection(); if (!nginxResult.success) { throw new Error(nginxResult.error || 'Failed to connect to NGINX Proxy Manager'); } // Update the step to show success const nginxStep = document.querySelectorAll('.step-item')[3]; nginxStep.classList.remove('active'); nginxStep.classList.add('completed'); nginxStep.querySelector('.step-status').textContent = 'Successfully connected to NGINX Proxy Manager'; // Step 5: Generate SSL Certificate await updateStep(5, 'Generating SSL Certificate', 'Setting up secure HTTPS connection...'); const sslResult = await generateSSLCertificate(data.webAddresses); if (!sslResult.success) { throw new Error(sslResult.error || 'Failed to generate SSL certificate'); } // Step 6: Create Proxy Host await updateStep(6, 'Creating Proxy Host', 'Setting up NGINX proxy host configuration...'); const proxyResult = await createProxyHost(data.webAddresses, data.port, sslResult.data.certificate.id); if (!proxyResult.success) { throw new Error(proxyResult.error || 'Failed to create proxy host'); } // Step 7: Check Portainer connection await updateStep(7, 'Checking Portainer Connection', 'Verifying connection to Portainer...'); const portainerResult = await checkPortainerConnection(); if (!portainerResult.success) { throw new Error(portainerResult.message || 'Failed to connect to Portainer'); } // Update the step to show success const portainerStep = document.querySelectorAll('.step-item')[6]; portainerStep.classList.remove('active'); portainerStep.classList.add('completed'); portainerStep.querySelector('.step-status').textContent = portainerResult.message; // Step 8: Download Docker Compose await updateStep(8, 'Downloading Docker Compose', 'Fetching docker-compose.yml from repository...'); const dockerComposeResult = await downloadDockerCompose(data.repository, data.branch); if (!dockerComposeResult.success) { throw new Error(dockerComposeResult.error || 'Failed to download docker-compose.yml'); } // Update the step to show success const dockerComposeStep = document.querySelectorAll('.step-item')[7]; dockerComposeStep.classList.remove('active'); dockerComposeStep.classList.add('completed'); dockerComposeStep.querySelector('.step-status').textContent = 'Successfully downloaded docker-compose.yml'; // Add download button const downloadButton = document.createElement('button'); downloadButton.className = 'btn btn-sm btn-primary mt-2'; downloadButton.innerHTML = ' Download docker-compose.yml'; downloadButton.onclick = () => { const blob = new Blob([dockerComposeResult.content], { type: 'text/yaml' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'docker-compose.yml'; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); }; dockerComposeStep.querySelector('.step-content').appendChild(downloadButton); // Step 9: Deploy Stack await updateStep(9, 'Deploying Stack', 'Launching your application stack...'); const stackResult = await deployStack(dockerComposeResult.content, data.instanceName, data.port); if (!stackResult.success) { throw new Error(stackResult.error || 'Failed to deploy stack'); } // Update the step to show success const stackDeployStep = document.querySelectorAll('.step-item')[8]; stackDeployStep.classList.remove('active'); stackDeployStep.classList.add('completed'); stackDeployStep.querySelector('.step-status').textContent = stackResult.data.status === 'existing' ? 'Using existing stack' : 'Successfully deployed stack'; // Add stack details const stackDetails = document.createElement('div'); stackDetails.className = 'mt-3'; stackDetails.innerHTML = `
Stack Deployment Results
Property Value
Stack Name ${stackResult.data.name}
Stack ID ${stackResult.data.id}
Status ${stackResult.data.status === 'existing' ? 'Existing' : 'Deployed'}
`; stackDeployStep.querySelector('.step-content').appendChild(stackDetails); // Save instance data await updateStep(10, 'Saving Instance Data', 'Storing instance information...'); try { const instanceData = { name: data.instanceName, port: data.port, domains: data.webAddresses, stack_id: stackResult.data.id, stack_name: stackResult.data.name, status: stackResult.data.status, repository: data.repository, branch: data.branch }; console.log('Saving instance data:', instanceData); const saveResult = await saveInstanceData(instanceData); console.log('Save result:', saveResult); await updateStep(10, 'Saving Instance Data', 'Instance data saved successfully'); } catch (error) { console.error('Error saving instance data:', error); await updateStep(10, 'Saving Instance Data', `Error: ${error.message}`); throw error; } // Update the step to show success const saveDataStep = document.querySelectorAll('.step-item')[9]; saveDataStep.classList.remove('active'); saveDataStep.classList.add('completed'); saveDataStep.querySelector('.step-status').textContent = 'Successfully saved instance data'; // Add instance details const instanceDetails = document.createElement('div'); instanceDetails.className = 'mt-3'; instanceDetails.innerHTML = `
Instance Information
Property Value
Internal Port ${data.port}
Domains ${data.webAddresses.join(', ')}
`; saveDataStep.querySelector('.step-content').appendChild(instanceDetails); // After saving instance data, add the health check step await updateStep(11, 'Health Check', 'Verifying instance health...'); const healthResult = await checkInstanceHealth(`https://${data.webAddresses[0]}`); if (!healthResult.success) { throw new Error(`Health check failed: ${healthResult.error}`); } // Add a retry button if health check fails const healthStep = document.querySelectorAll('.step-item')[10]; if (!healthResult.success) { const retryButton = document.createElement('button'); retryButton.className = 'btn btn-sm btn-warning mt-2'; retryButton.innerHTML = ' Retry Health Check'; retryButton.onclick = async () => { retryButton.disabled = true; retryButton.innerHTML = ' Checking...'; const retryResult = await checkInstanceHealth(`https://${data.webAddresses[0]}`); if (retryResult.success) { healthStep.classList.remove('failed'); healthStep.classList.add('completed'); retryButton.remove(); } else { retryButton.disabled = false; retryButton.innerHTML = ' Retry Health Check'; } }; healthStep.querySelector('.step-content').appendChild(retryButton); } // After health check, add authentication step await updateStep(12, 'Instance Authentication', 'Setting up instance authentication...'); const authResult = await authenticateInstance(`https://${data.webAddresses[0]}`, data.instanceId); if (!authResult.success) { throw new Error(`Authentication failed: ${authResult.error}`); } // Update the auth step to show success const authStep = document.querySelectorAll('.step-item')[11]; authStep.classList.remove('active'); authStep.classList.add('completed'); authStep.querySelector('.step-status').textContent = authResult.alreadyAuthenticated ? 'Instance is already authenticated' : 'Successfully authenticated instance'; // Add authentication details const authDetails = document.createElement('div'); authDetails.className = 'mt-3'; authDetails.innerHTML = `
Authentication Details
Property Value
Status Authenticated
Default Admin administrator@docupulse.com
Connection Type Management API Key
Authentication Type ${authResult.alreadyAuthenticated ? 'Existing' : 'New'}
`; authStep.querySelector('.step-content').appendChild(authDetails); // Step 13: Apply Company Information await updateStep(13, 'Apply Company Information', 'Configuring company details...'); const companyResult = await applyCompanyInformation(`https://${data.webAddresses[0]}`, data.company); if (!companyResult.success) { throw new Error(`Company information application failed: ${companyResult.error}`); } // Update the company step to show success const companyStep = document.querySelectorAll('.step-item')[12]; companyStep.classList.remove('active'); companyStep.classList.add('completed'); companyStep.querySelector('.step-status').textContent = 'Successfully applied company information'; // Add company details const companyDetails = document.createElement('div'); companyDetails.className = 'mt-3'; companyDetails.innerHTML = `
Company Information Applied
Property Value
Company Name ${data.company.name || 'Not set'}
Industry ${data.company.industry || 'Not set'}
Email ${data.company.email || 'Not set'}
Website ${data.company.website || 'Not set'}
Address ${data.company.streetAddress || 'Not set'}, ${data.company.city || ''}, ${data.company.state || ''} ${data.company.zipCode || ''}
`; companyStep.querySelector('.step-content').appendChild(companyDetails); // Step 14: Apply Colors await updateStep(14, 'Apply Colors', 'Configuring color scheme...'); const colorsResult = await applyColors(`https://${data.webAddresses[0]}`, data.colors); if (!colorsResult.success) { throw new Error(`Colors application failed: ${colorsResult.error}`); } // Update the colors step to show success const colorsStep = document.querySelectorAll('.step-item')[13]; colorsStep.classList.remove('active'); colorsStep.classList.add('completed'); colorsStep.querySelector('.step-status').textContent = 'Successfully applied color scheme'; // Add colors details const colorsDetails = document.createElement('div'); colorsDetails.className = 'mt-3'; colorsDetails.innerHTML = `
Color Scheme Applied
Property Value Preview
Primary Color ${data.colors.primary || 'Not set'}
Secondary Color ${data.colors.secondary || 'Not set'}
`; colorsStep.querySelector('.step-content').appendChild(colorsDetails); // Step 15: Update Admin Credentials await updateStep(15, 'Update Admin Credentials', 'Setting up admin account...'); const credentialsResult = await updateAdminCredentials(`https://${data.webAddresses[0]}`, data.company.email); if (!credentialsResult.success) { throw new Error(`Admin credentials update failed: ${credentialsResult.error}`); } // Update the credentials step to show success const credentialsStep = document.querySelectorAll('.step-item')[14]; credentialsStep.classList.remove('active'); credentialsStep.classList.add('completed'); if (credentialsResult.data.already_updated) { credentialsStep.querySelector('.step-status').textContent = 'Admin credentials already updated'; } else { credentialsStep.querySelector('.step-status').textContent = 'Successfully updated admin credentials'; } // Add credentials details const credentialsDetails = document.createElement('div'); credentialsDetails.className = 'mt-3'; credentialsDetails.innerHTML = `
${credentialsResult.data.already_updated ? 'Admin Credentials Status' : 'Admin Credentials Updated'}
Property Value
Email Address ${credentialsResult.data.email || 'Not set'}
Username ${credentialsResult.data.username || 'administrator'}
Role Administrator
Password Setup Reset Link Pending
Status ${credentialsResult.data.already_updated ? 'Already Updated' : 'Updated'}
${credentialsResult.data.already_updated ? 'Note:' : 'Security Setup Required:'} ${credentialsResult.data.already_updated ? 'Admin credentials were already updated from the default settings.' : 'A secure password reset link will be sent to the admin email address in the completion email.'}
`; credentialsStep.querySelector('.step-content').appendChild(credentialsDetails); // Step 16: Copy SMTP Settings await updateStep(16, 'Copy SMTP Settings', 'Configuring email settings...'); const smtpResult = await copySmtpSettings(`https://${data.webAddresses[0]}`); if (!smtpResult.success) { throw new Error(`SMTP settings copy failed: ${smtpResult.error}`); } // Update the SMTP step to show success const smtpStep = document.querySelectorAll('.step-item')[15]; smtpStep.classList.remove('active'); smtpStep.classList.add('completed'); smtpStep.querySelector('.step-status').textContent = 'Successfully copied SMTP settings'; // Add SMTP details const smtpDetails = document.createElement('div'); smtpDetails.className = 'mt-3'; smtpDetails.innerHTML = `
SMTP Settings Copied
Property Value
SMTP Host ${smtpResult.data.smtp_host || 'Not set'}
SMTP Port ${smtpResult.data.smtp_port || 'Not set'}
Security ${smtpResult.data.smtp_security || 'None'}
Username ${smtpResult.data.smtp_username || 'Not set'}
From Email ${smtpResult.data.smtp_from_email || 'Not set'}
From Name ${smtpResult.data.smtp_from_name || 'Not set'}
Status Copied Successfully
Email Configuration Complete! The launched instance now has the same SMTP settings as the master instance and can send emails independently.
`; smtpStep.querySelector('.step-content').appendChild(smtpDetails); // Step 17: Send Completion Email await updateStep(17, 'Send Completion Email', 'Sending notification to client...'); const emailResult = await sendCompletionEmail(`https://${data.webAddresses[0]}`, data.company, credentialsResult.data); if (!emailResult.success) { throw new Error(`Email sending failed: ${emailResult.error}`); } // Update the email step to show success const emailStep = document.querySelectorAll('.step-item')[16]; emailStep.classList.remove('active'); emailStep.classList.add('completed'); emailStep.querySelector('.step-status').textContent = 'Successfully sent completion email'; // Add email details const emailDetails = document.createElement('div'); emailDetails.className = 'mt-3'; emailDetails.innerHTML = `
Completion Email Sent
Property Value
Recipient ${data.company.email || 'Not set'}
Subject Your DocuPulse Instance is Ready!
Status Sent Successfully
Instance URL https://${data.webAddresses[0]}
Password Reset Link Included

HTML Version
Plain Text Version
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!
Launch Complete! Your DocuPulse instance has been successfully deployed and configured. The client has been notified via email with all necessary login information and a secure password reset link.
`; emailStep.querySelector('.step-content').appendChild(emailDetails); } catch (error) { console.error('Launch failed:', error); await updateStep(17, 'Send Completion Email', `Error: ${error.message}`); showError(error.message); } } async function checkDNSRecords(domains) { const maxRetries = 30; // 30 retries * 10 seconds = 5 minutes const baseDelay = 10000; // 10 seconds base delay for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await fetch('/api/check-dns', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.csrfToken }, body: JSON.stringify({ domains: domains }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to check DNS records'); } const result = await response.json(); // Check if all domains are resolved const allResolved = Object.values(result.results).every(result => result.resolved); if (allResolved) { console.log(`DNS records resolved successfully on attempt ${attempt}`); return result; } // If not all domains are resolved and this isn't the last attempt, wait and retry if (attempt < maxRetries) { const delay = baseDelay * Math.pow(1.2, attempt - 1); // Exponential backoff const failedDomains = Object.entries(result.results) .filter(([_, result]) => !result.resolved) .map(([domain]) => domain); console.log(`Attempt ${attempt}/${maxRetries}: DNS not yet propagated for ${failedDomains.join(', ')}. Waiting ${Math.round(delay/1000)}s before retry...`); // Update the step description to show retry progress const currentStep = document.querySelector('.step-item.active'); if (currentStep) { const statusElement = currentStep.querySelector('.step-status'); statusElement.textContent = `Waiting for DNS propagation... (Attempt ${attempt}/${maxRetries})`; } await new Promise(resolve => setTimeout(resolve, delay)); } else { // Last attempt failed console.log(`DNS records failed to resolve after ${maxRetries} attempts`); return result; } } catch (error) { console.error(`Error checking DNS records (attempt ${attempt}):`, error); if (attempt === maxRetries) { throw error; } // Wait before retrying on error const delay = baseDelay * Math.pow(1.2, attempt - 1); console.log(`DNS check failed, retrying in ${Math.round(delay/1000)}s...`); // Update the step description to show retry progress const currentStep = document.querySelector('.step-item.active'); if (currentStep) { const statusElement = currentStep.querySelector('.step-status'); statusElement.textContent = `DNS check failed, retrying... (Attempt ${attempt}/${maxRetries})`; } await new Promise(resolve => setTimeout(resolve, delay)); } } } async function checkCloudflareConnection() { try { const response = await fetch('/api/check-cloudflare-connection', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.csrfToken } }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to check Cloudflare connection'); } return await response.json(); } catch (error) { console.error('Error checking Cloudflare connection:', error); throw error; } } async function createDNSRecords(domains) { try { const response = await fetch('/api/create-dns-records', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.csrfToken }, body: JSON.stringify({ domains: domains }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to create DNS records'); } return await response.json(); } catch (error) { console.error('Error creating DNS records:', error); throw error; } } async function checkNginxConnection() { try { // Get NGINX settings from the template const nginxSettings = { url: window.nginxSettings?.url || '', username: window.nginxSettings?.username || '', password: window.nginxSettings?.password || '' }; // Debug log the settings (without password) console.log('NGINX Settings:', { url: nginxSettings.url, username: nginxSettings.username, hasPassword: !!nginxSettings.password }); // Check if any required field is missing if (!nginxSettings.url || !nginxSettings.username || !nginxSettings.password) { return { success: false, error: 'NGINX settings are not configured. Please configure NGINX settings in the admin panel.' }; } // First, get the token const tokenResponse = await fetch(`${nginxSettings.url}/api/tokens`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: nginxSettings.username, secret: nginxSettings.password }) }); if (!tokenResponse.ok) { const errorText = await tokenResponse.text(); console.error('Token Error Response:', errorText); try { const errorJson = JSON.parse(errorText); throw new Error(`Failed to authenticate with NGINX: ${errorJson.message || errorText}`); } catch (e) { throw new Error(`Failed to authenticate with NGINX: ${errorText}`); } } const tokenData = await tokenResponse.json(); const token = tokenData.token; if (!token) { throw new Error('No token received from NGINX Proxy Manager'); } // Now test the connection using the token const response = await fetch(`${nginxSettings.url}/api/nginx/proxy-hosts`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'application/json' } }); if (!response.ok) { const errorText = await response.text(); console.error('NGINX connection error:', errorText); try { const errorJson = JSON.parse(errorText); throw new Error(errorJson.message || 'Failed to connect to NGINX Proxy Manager'); } catch (e) { throw new Error(`Failed to connect to NGINX Proxy Manager: ${errorText}`); } } return { success: true }; } catch (error) { console.error('Error checking NGINX connection:', error); return { success: false, error: error.message || 'Error checking NGINX connection' }; } } async function checkPortainerConnection() { try { // Get Portainer settings from the template const portainerSettings = { url: window.portainerSettings?.url || '', api_key: window.portainerSettings?.api_key || '' }; // Debug log the settings (without API key) console.log('Portainer Settings:', { url: portainerSettings.url, hasApiKey: !!portainerSettings.api_key }); // Check if any required field is missing if (!portainerSettings.url || !portainerSettings.api_key) { console.error('Missing Portainer settings:', portainerSettings); return { success: false, message: 'Portainer settings are not configured. Please configure Portainer settings in the admin panel.' }; } const response = await fetch('/api/admin/test-portainer-connection', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ url: portainerSettings.url, api_key: portainerSettings.api_key }) }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Failed to connect to Portainer'); } return { success: true, message: 'Successfully connected to Portainer' }; } catch (error) { console.error('Portainer connection error:', error); return { success: false, message: error.message || 'Failed to connect to Portainer' }; } } function updateStatus(step, message, type = 'info', details = '') { const statusElement = document.getElementById(`${step}Status`); const detailsElement = document.getElementById(`${step}Details`); if (statusElement) { // Remove any existing status classes statusElement.classList.remove('text-info', 'text-success', 'text-danger'); // Add appropriate class based on type switch (type) { case 'success': statusElement.classList.add('text-success'); break; case 'error': statusElement.classList.add('text-danger'); break; default: statusElement.classList.add('text-info'); } statusElement.textContent = message; } if (detailsElement) { detailsElement.innerHTML = details; } } async function createProxyHost(domains, port, sslCertificateId) { try { // Get NGINX settings from the template const nginxSettings = { url: window.nginxSettings?.url || '', username: window.nginxSettings?.username || '', password: window.nginxSettings?.password || '' }; console.log('NGINX Settings:', { ...nginxSettings, password: '***' }); // Update status to show we're getting the token updateStatus('proxy', 'Getting authentication token...', 'info'); // First, get the JWT token from NGINX const tokenResponse = await fetch(`${nginxSettings.url}/api/tokens`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: nginxSettings.username, secret: nginxSettings.password }) }); console.log('Token Response Status:', tokenResponse.status); console.log('Token Response Headers:', Object.fromEntries(tokenResponse.headers.entries())); if (!tokenResponse.ok) { const errorText = await tokenResponse.text(); console.error('Token Error Response:', errorText); try { const errorJson = JSON.parse(errorText); throw new Error(`Failed to authenticate with NGINX: ${errorJson.message || errorText}`); } catch (e) { throw new Error(`Failed to authenticate with NGINX: ${errorText}`); } } const tokenData = await tokenResponse.json(); console.log('Token Data:', { ...tokenData, token: tokenData.token ? '***' : null }); const token = tokenData.token; if (!token) { throw new Error('No token received from NGINX Proxy Manager'); } // Store the token in sessionStorage for later use sessionStorage.setItem('nginxToken', token); // Check if a proxy host already exists with the same properties const proxyHostsResponse = await fetch(`${nginxSettings.url}/api/nginx/proxy-hosts`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (!proxyHostsResponse.ok) { throw new Error('Failed to fetch existing proxy hosts'); } const proxyHosts = await proxyHostsResponse.json(); const existingProxy = proxyHosts.find(ph => { const sameDomains = Array.isArray(ph.domain_names) && ph.domain_names.length === domains.length && domains.every(d => ph.domain_names.includes(d)); return ( sameDomains && ph.forward_scheme === 'http' && ph.forward_host === '192.168.68.124' && parseInt(ph.forward_port) === parseInt(port) ); }); let result; if (existingProxy) { console.log('Found existing proxy host:', existingProxy); result = existingProxy; } else { // Update status to show we're creating the proxy host updateStatus('proxy', 'Creating proxy host configuration...', 'info'); const proxyHostData = { domain_names: domains, forward_scheme: 'http', forward_host: '192.168.68.124', forward_port: parseInt(port), ssl_forced: false, caching_enabled: true, block_exploits: true, allow_websocket_upgrade: true, http2_support: false, hsts_enabled: false, hsts_subdomains: false, certificate_id: sslCertificateId, meta: { letsencrypt_agree: true, dns_challenge: false } }; console.log('Creating proxy host with data:', proxyHostData); // Create the proxy host with NGINX const response = await fetch(`${nginxSettings.url}/api/nginx/proxy-hosts`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(proxyHostData) }); console.log('Proxy Host Response Status:', response.status); console.log('Proxy Host Response Headers:', Object.fromEntries(response.headers.entries())); if (!response.ok) { const errorText = await response.text(); console.error('Proxy Host Error Response:', errorText); try { const errorJson = JSON.parse(errorText); const errorMessage = errorJson.error?.message || errorText; // Check if the error is about a domain already being in use if (errorMessage.includes('is already in use')) { const domain = errorMessage.split(' ')[0]; throw new Error(`Domain ${domain} is already configured in NGINX Proxy Manager. Please remove it from NGINX Proxy Manager and try again.`); } throw new Error(`Failed to create proxy host: ${errorMessage}`); } catch (e) { if (e.message.includes('is already configured in NGINX Proxy Manager')) { throw e; // Re-throw the domain in use error } throw new Error(`Failed to create proxy host: ${errorText}`); } } result = await response.json(); console.log('Proxy Host Creation Result:', result); } // Create a detailed success message with NGINX Proxy results const successDetails = `
NGINX Proxy Results
Property Value
Proxy Host ID ${result.id || 'N/A'}
Domains ${domains.join(', ')}
Forward Scheme http
Forward Host 192.168.68.124
Forward Port ${parseInt(port)}
SSL Status Forced
SSL Certificate Using Certificate ID: ${sslCertificateId}
Security Features Block Exploits HSTS HTTP/2
Performance Caching WebSocket
`; // Update the proxy step to show success and add the results const proxyStep = document.querySelectorAll('.step-item')[5]; proxyStep.classList.remove('active'); proxyStep.classList.add('completed'); const statusText = proxyStep.querySelector('.step-status'); statusText.textContent = existingProxy ? 'Using existing proxy host' : 'Successfully created proxy host'; statusText.after(document.createRange().createContextualFragment(successDetails)); return { success: true, data: result }; } catch (error) { console.error('Error creating proxy host:', error); // Update status with error message updateStatus('proxy', `Failed: ${error.message}`, 'error'); return { success: false, error: error.message }; } } async function generateSSLCertificate(domains) { try { // Get NGINX settings from the template const nginxSettings = { url: window.nginxSettings?.url || '', username: window.nginxSettings?.username || '', password: window.nginxSettings?.password || '', email: window.nginxSettings?.email || '' }; // Get a fresh token from NGINX const tokenResponse = await fetch(`${nginxSettings.url}/api/tokens`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ identity: nginxSettings.username, secret: nginxSettings.password }) }); if (!tokenResponse.ok) { const errorText = await tokenResponse.text(); console.error('Token Error Response:', errorText); throw new Error(`Failed to authenticate with NGINX: ${errorText}`); } const tokenData = await tokenResponse.json(); const token = tokenData.token; if (!token) { throw new Error('No token received from NGINX Proxy Manager'); } // First, check if a certificate already exists for these domains const checkResponse = await fetch(`${nginxSettings.url}/api/nginx/certificates`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` } }); if (!checkResponse.ok) { throw new Error('Failed to check existing certificates'); } const existingCertificates = await checkResponse.json(); const existingCertificate = existingCertificates.find(cert => { const certDomains = cert.domain_names || []; return domains.every(domain => certDomains.includes(domain)) && certDomains.length === domains.length; }); let result; let usedExisting = false; if (existingCertificate) { console.log('Found existing certificate:', existingCertificate); result = existingCertificate; usedExisting = true; } else { // Create the SSL certificate directly with NGINX const requestBody = { domain_names: domains, meta: { letsencrypt_email: nginxSettings.email, letsencrypt_agree: true, dns_challenge: false }, provider: 'letsencrypt' }; console.log('Request Body:', requestBody); const response = await fetch(`${nginxSettings.url}/api/nginx/certificates`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(requestBody) }); console.log('Response Status:', response.status); console.log('Response Headers:', Object.fromEntries(response.headers.entries())); if (!response.ok) { const errorText = await response.text(); console.error('Certificate creation error:', errorText); throw new Error(`Failed to generate SSL certificate: ${errorText}`); } result = await response.json(); console.log('Certificate creation result:', result); } // Update the SSL step to show success const sslStep = document.querySelectorAll('.step-item')[4]; sslStep.classList.remove('active'); sslStep.classList.add('completed'); const sslStatusText = sslStep.querySelector('.step-status'); sslStatusText.textContent = usedExisting ? 'Using existing SSL certificate' : 'SSL certificate generated successfully'; // Always add SSL certificate details const sslDetails = document.createElement('div'); sslDetails.className = 'mt-3'; sslDetails.innerHTML = `
SSL Certificate Details
Property Value
Certificate ID ${result.id || 'N/A'}
Domains ${(result.domain_names || domains).join(', ')}
Provider ${result.provider || 'Let\'s Encrypt'}
`; sslStatusText.after(sslDetails); return { success: true, data: { certificate: result } }; } catch (error) { console.error('Error generating SSL certificate:', error); return { success: false, error: error.message }; } } function scrollToStep(stepElement) { const container = document.querySelector('.launch-steps-container'); const containerRect = container.getBoundingClientRect(); const elementRect = stepElement.getBoundingClientRect(); // Calculate the scroll position to center the element in the container const scrollTop = elementRect.top - containerRect.top - (containerRect.height / 2) + (elementRect.height / 2); // Smooth scroll to the element container.scrollTo({ top: container.scrollTop + scrollTop, behavior: 'smooth' }); } function updateStep(stepNumber, title, description) { return new Promise((resolve) => { // Update the current step in the header document.getElementById('currentStep').textContent = title; document.getElementById('stepDescription').textContent = description; // Calculate progress based on total number of steps (17 steps total) const totalSteps = 17; const progress = ((stepNumber - 1) / (totalSteps - 1)) * 100; const progressBar = document.getElementById('launchProgress'); progressBar.style.width = `${progress}%`; progressBar.textContent = `${Math.round(progress)}%`; // Update step items const steps = document.querySelectorAll('.step-item'); steps.forEach((item, index) => { const step = index + 1; item.classList.remove('active', 'completed', 'failed'); if (step < stepNumber) { item.classList.add('completed'); item.querySelector('.step-status').textContent = 'Completed'; } else if (step === stepNumber) { item.classList.add('active'); item.querySelector('.step-status').textContent = description; // Scroll to the active step scrollToStep(item); } }); // Simulate some work being done setTimeout(resolve, 1000); }); } function showError(message) { const errorContainer = document.getElementById('errorContainer'); const errorMessage = document.getElementById('errorMessage'); errorMessage.textContent = message; errorContainer.style.display = 'block'; // Update the current step to show error const currentStep = document.querySelector('.step-item.active'); if (currentStep) { currentStep.classList.add('failed'); currentStep.querySelector('.step-status').textContent = 'Failed: ' + message; } } function retryLaunch() { // Reload the page to start over window.location.reload(); } // Add new function to download docker-compose.yml async function downloadDockerCompose(repo, branch) { try { const response = await fetch('/api/admin/download-docker-compose', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ repository: repo, branch: branch }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.message || 'Failed to download docker-compose.yml'); } const result = await response.json(); return { success: true, content: result.content }; } catch (error) { console.error('Error downloading docker-compose.yml:', error); return { success: false, error: error.message }; } } // Add new function to deploy stack async function deployStack(dockerComposeContent, stackName, port) { const maxRetries = 30; // 30 retries * 10 seconds = 5 minutes const baseDelay = 10000; // 10 seconds base delay try { // First, attempt to deploy the stack const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10 * 60 * 1000); // 10 minutes timeout const response = await fetch('/api/admin/deploy-stack', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ name: `docupulse_${port}`, StackFileContent: dockerComposeContent, Env: [ { name: 'PORT', value: port.toString() }, { name: 'ISMASTER', value: 'false' } ] }), signal: controller.signal }); clearTimeout(timeoutId); // Clear the timeout if the request completes if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to deploy stack'); } const result = await response.json(); // If deployment was successful, wait for stack to come online if (result.success || result.data) { console.log('Stack deployment initiated, waiting for stack to come online...'); // Update status to show we're waiting for stack to come online const currentStep = document.querySelector('.step-item.active'); if (currentStep) { const statusElement = currentStep.querySelector('.step-status'); statusElement.textContent = 'Stack deployed, waiting for services to start...'; } // Wait and retry to check if stack is online for (let attempt = 1; attempt <= maxRetries; attempt++) { try { // Check stack status via Portainer API const stackCheckResponse = await fetch('/api/admin/check-stack-status', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ stack_name: `docupulse_${port}` }) }); if (stackCheckResponse.ok) { const stackStatus = await stackCheckResponse.json(); if (stackStatus.success && stackStatus.data.status === 'active') { console.log(`Stack came online successfully on attempt ${attempt}`); // Update status to show success if (currentStep) { const statusElement = currentStep.querySelector('.step-status'); statusElement.textContent = 'Stack deployed and online successfully'; } return { success: true, data: { ...result.data || result, status: 'active', attempt: attempt } }; } } // If not online yet and this isn't the last attempt, wait and retry if (attempt < maxRetries) { const delay = baseDelay * Math.pow(1.2, attempt - 1); // Exponential backoff console.log(`Attempt ${attempt}/${maxRetries}: Stack not yet online. Waiting ${Math.round(delay/1000)}s before retry...`); // Update the step description to show retry progress if (currentStep) { const statusElement = currentStep.querySelector('.step-status'); statusElement.textContent = `Waiting for stack to come online... (Attempt ${attempt}/${maxRetries})`; } await new Promise(resolve => setTimeout(resolve, delay)); } else { // Last attempt failed - stack might be online but API check failed console.log(`Stack status check failed after ${maxRetries} attempts, but deployment was successful`); // Update status to show partial success if (currentStep) { const statusElement = currentStep.querySelector('.step-status'); statusElement.textContent = 'Stack deployed (status check timeout)'; } return { success: true, data: { ...result.data || result, status: 'deployed', note: 'Status check timeout - stack may be online' } }; } } catch (error) { console.error(`Stack status check attempt ${attempt} failed:`, error); if (attempt === maxRetries) { // Last attempt failed, but deployment was successful console.log('Stack status check failed after all attempts, but deployment was successful'); if (currentStep) { const statusElement = currentStep.querySelector('.step-status'); statusElement.textContent = 'Stack deployed (status check failed)'; } return { success: true, data: { ...result.data || result, status: 'deployed', note: 'Status check failed - stack may be online' } }; } // Wait before retrying on error const delay = baseDelay * Math.pow(1.2, attempt - 1); console.log(`Stack check failed, retrying in ${Math.round(delay/1000)}s...`); if (currentStep) { const statusElement = currentStep.querySelector('.step-status'); statusElement.textContent = `Stack check failed, retrying... (Attempt ${attempt}/${maxRetries})`; } await new Promise(resolve => setTimeout(resolve, delay)); } } } // If we get here, deployment was successful but we couldn't verify status return { success: true, data: { ...result.data || result, status: 'deployed', note: 'Deployment successful, status unknown' } }; } catch (error) { console.error('Error deploying stack:', error); return { success: false, error: error.message }; } } // Add new function to save instance data async function saveInstanceData(instanceData) { try { console.log('Saving instance data:', instanceData); // First check if instance already exists const checkResponse = await fetch('/instances'); const text = await checkResponse.text(); const parser = new DOMParser(); const doc = parser.parseFromString(text, 'text/html'); // Look for an existing instance with the same name const existingInstance = Array.from(doc.querySelectorAll('table tbody tr')).find(row => { const nameCell = row.querySelector('td:first-child'); return nameCell && nameCell.textContent.trim() === instanceData.port; }); if (existingInstance) { console.log('Instance already exists:', instanceData.port); return { success: true, data: { name: instanceData.port, company: 'loading...', rooms_count: 0, conversations_count: 0, data_size: 0.0, payment_plan: 'Basic', main_url: `https://${instanceData.domains[0]}`, status: 'inactive', port: instanceData.port, stack_id: instanceData.stack_id, stack_name: instanceData.stack_name, repository: instanceData.repository, branch: instanceData.branch } }; } // If instance doesn't exist, create it const response = await fetch('/instances/add', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ name: instanceData.port, company: 'loading...', rooms_count: 0, conversations_count: 0, data_size: 0.0, payment_plan: 'Basic', main_url: `https://${instanceData.domains[0]}`, status: 'inactive', port: instanceData.port, stack_id: instanceData.stack_id, stack_name: instanceData.stack_name, repository: instanceData.repository, branch: instanceData.branch }) }); if (!response.ok) { const errorText = await response.text(); console.error('Error response:', errorText); throw new Error(`Failed to save instance data: ${response.status} ${response.statusText}`); } const result = await response.json(); console.log('Instance data saved:', result); return result; } catch (error) { console.error('Error saving instance data:', error); throw error; } } async function checkInstanceHealth(instanceUrl) { const maxRetries = 5; let currentAttempt = 1; while (currentAttempt <= maxRetries) { try { // First get the instance ID from the database const response = await fetch('/instances'); const text = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(text, 'text/html'); // Find the instance row that matches our URL const instanceRow = Array.from(doc.querySelectorAll('table tbody tr')).find(row => { const urlCell = row.querySelector('td:nth-child(7) a'); // URL is in the 7th column return urlCell && urlCell.textContent.trim() === instanceUrl; }); if (!instanceRow) { throw new Error('Instance not found in database'); } // Get the instance ID from the status badge's data attribute const statusBadge = instanceRow.querySelector('[data-instance-id]'); if (!statusBadge) { throw new Error('Could not find instance ID'); } const instanceId = statusBadge.dataset.instanceId; // Now use the instance ID to check status const statusResponse = await fetch(`/instances/${instanceId}/status`); if (!statusResponse.ok) { throw new Error(`Health check failed with status ${statusResponse.status}`); } const data = await statusResponse.json(); // Update the health check step const healthStep = document.querySelectorAll('.step-item')[8]; // Adjust index based on your steps healthStep.classList.remove('active'); healthStep.classList.add('completed'); const statusText = healthStep.querySelector('.step-status'); if (data.status === 'active') { statusText.textContent = `Instance is healthy (Attempt ${currentAttempt}/${maxRetries})`; return { success: true, data: data }; } else { throw new Error('Instance is not healthy'); } } catch (error) { console.error(`Health check attempt ${currentAttempt} failed:`, error); // Update status to show current attempt const healthStep = document.querySelectorAll('.step-item')[8]; const statusText = healthStep.querySelector('.step-status'); statusText.textContent = `Health check failed (Attempt ${currentAttempt}/${maxRetries}): ${error.message}`; if (currentAttempt === maxRetries) { return { success: false, error: `Health check failed after ${maxRetries} attempts: ${error.message}` }; } // Wait 5 seconds before next attempt await new Promise(resolve => setTimeout(resolve, 5000)); currentAttempt++; } } } async function authenticateInstance(instanceUrl, instanceId) { try { // First check if instance is already authenticated const instancesResponse = await fetch('/instances'); const text = await instancesResponse.text(); const parser = new DOMParser(); const doc = parser.parseFromString(text, 'text/html'); // Find the instance with matching URL const instanceRow = Array.from(doc.querySelectorAll('table tbody tr')).find(row => { const urlCell = row.querySelector('td:nth-child(7) a'); // URL is in the 7th column return urlCell && urlCell.textContent.trim() === instanceUrl; }); if (!instanceRow) { throw new Error('Instance not found in database'); } // Get the instance ID from the status badge's data attribute const statusBadge = instanceRow.querySelector('[data-instance-id]'); if (!statusBadge) { throw new Error('Could not find instance ID'); } const dbInstanceId = statusBadge.dataset.instanceId; // Check if already authenticated const authStatusResponse = await fetch(`/instances/${dbInstanceId}/auth-status`); if (!authStatusResponse.ok) { throw new Error('Failed to check authentication status'); } const authStatus = await authStatusResponse.json(); if (authStatus.authenticated) { console.log('Instance is already authenticated'); return { success: true, message: 'Instance is already authenticated', alreadyAuthenticated: true }; } console.log('Attempting login to:', `${instanceUrl}/api/admin/login`); // First login to get token const loginResponse = await fetch(`${instanceUrl}/api/admin/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ email: 'administrator@docupulse.com', password: 'changeme' }) }); if (!loginResponse.ok) { const errorText = await loginResponse.text(); throw new Error(`Login failed: ${errorText}`); } const loginData = await loginResponse.json(); if (loginData.status !== 'success' || !loginData.token) { throw new Error('Login failed: Invalid response from server'); } const token = loginData.token; // Then create management API key const keyResponse = await fetch(`${instanceUrl}/api/admin/management-api-key`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ name: `Connection from ${window.location.hostname}` }) }); if (!keyResponse.ok) { const errorText = await keyResponse.text(); throw new Error(`Failed to create API key: ${errorText}`); } const keyData = await keyResponse.json(); if (!keyData.api_key) { throw new Error('No API key received from server'); } // Save the token to our database const saveResponse = await fetch(`/instances/${dbInstanceId}/save-token`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ token: keyData.api_key }) }); if (!saveResponse.ok) { const errorText = await saveResponse.text(); throw new Error(`Failed to save token: ${errorText}`); } return { success: true, message: 'Successfully authenticated instance', alreadyAuthenticated: false }; } catch (error) { console.error('Authentication error:', error); return { success: false, error: error.message }; } } async function applyCompanyInformation(instanceUrl, company) { try { console.log('Applying company information to:', instanceUrl); const response = await fetch('/api/admin/apply-company-information', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ instance_url: instanceUrl, company_data: company }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Failed to apply company information: ${errorText}`); } const result = await response.json(); console.log('Company information applied successfully:', result); return { success: true, message: result.message, data: result.data }; } catch (error) { console.error('Error applying company information:', error); return { success: false, error: error.message }; } } async function applyColors(instanceUrl, colors) { try { console.log('Applying colors to:', instanceUrl); const response = await fetch('/api/admin/apply-colors', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ instance_url: instanceUrl, colors_data: colors }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Failed to apply colors: ${errorText}`); } const result = await response.json(); console.log('Colors applied successfully:', result); return { success: true, message: result.message, data: result.data }; } catch (error) { console.error('Error applying colors:', error); return { success: false, error: error.message }; } } async function updateAdminCredentials(instanceUrl, email) { try { console.log('Updating admin credentials for:', instanceUrl); const response = await fetch('/api/admin/update-admin-credentials', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ instance_url: instanceUrl, email: email }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Failed to update admin credentials: ${errorText}`); } const result = await response.json(); console.log('Admin credentials updated successfully:', result); return { success: true, message: result.message, data: result.data }; } catch (error) { console.error('Error updating admin credentials:', error); return { success: false, error: error.message }; } } async function copySmtpSettings(instanceUrl) { try { console.log('Copying SMTP settings to:', instanceUrl); const response = await fetch('/api/admin/copy-smtp-settings', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ instance_url: instanceUrl }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Failed to copy SMTP settings: ${errorText}`); } const result = await response.json(); console.log('SMTP settings copied successfully:', result); return { success: true, message: result.message, data: result.data }; } catch (error) { console.error('Error copying SMTP settings:', error); return { success: false, error: error.message }; } } async function sendCompletionEmail(instanceUrl, company, credentials) { try { console.log('Sending completion email to:', company.email); const response = await fetch('/api/admin/send-completion-email', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ instance_url: instanceUrl, company_data: company, credentials_data: credentials }) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Failed to send completion email: ${errorText}`); } const result = await response.json(); console.log('Email sent successfully:', result); return { success: true, message: result.message, data: result.data }; } catch (error) { console.error('Error sending completion email:', error); return { success: false, error: error.message }; } }