improved launch process using cloudflare
This commit is contained in:
@@ -16,6 +16,30 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
function initializeSteps() {
|
||||
const stepsContainer = document.getElementById('stepsContainer');
|
||||
|
||||
// Add Cloudflare connection check step
|
||||
const cloudflareStep = document.createElement('div');
|
||||
cloudflareStep.className = 'step-item';
|
||||
cloudflareStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-cloud"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Checking Cloudflare Connection</h5>
|
||||
<p class="step-status">Verifying Cloudflare API connection...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(cloudflareStep);
|
||||
|
||||
// Add DNS record creation step
|
||||
const dnsCreateStep = document.createElement('div');
|
||||
dnsCreateStep.className = 'step-item';
|
||||
dnsCreateStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-plus-circle"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Creating DNS Records</h5>
|
||||
<p class="step-status">Setting up domain DNS records in Cloudflare...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(dnsCreateStep);
|
||||
|
||||
// Add DNS check step
|
||||
const dnsStep = document.createElement('div');
|
||||
dnsStep.className = 'step-item';
|
||||
@@ -199,8 +223,72 @@ function initializeSteps() {
|
||||
|
||||
async function startLaunch(data) {
|
||||
try {
|
||||
// Step 1: Check DNS records
|
||||
await updateStep(1, 'Checking DNS Records', 'Verifying domain configurations...');
|
||||
// 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 = `
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title mb-3">DNS Record Creation Results</h6>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Status</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${Object.entries(dnsCreateResult.results).map(([domain, result]) => `
|
||||
<tr>
|
||||
<td>${domain}</td>
|
||||
<td>
|
||||
<span class="badge bg-${result.status === 'created' || result.status === 'updated' ? 'success' : 'danger'}">
|
||||
${result.status}
|
||||
</span>
|
||||
</td>
|
||||
<td>${result.message}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
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
|
||||
@@ -213,7 +301,7 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// Update the step to show success
|
||||
const dnsStep = document.querySelectorAll('.step-item')[0];
|
||||
const dnsStep = document.querySelectorAll('.step-item')[2];
|
||||
dnsStep.classList.remove('active');
|
||||
dnsStep.classList.add('completed');
|
||||
|
||||
@@ -259,8 +347,8 @@ async function startLaunch(data) {
|
||||
statusText.textContent = 'DNS records verified successfully';
|
||||
statusText.after(detailsSection);
|
||||
|
||||
// Step 2: Check NGINX connection
|
||||
await updateStep(2, 'Checking NGINX Connection', 'Verifying connection to NGINX Proxy Manager...');
|
||||
// Step 4: Check NGINX connection
|
||||
await updateStep(4, 'Checking NGINX Connection', 'Verifying connection to NGINX Proxy Manager...');
|
||||
const nginxResult = await checkNginxConnection();
|
||||
|
||||
if (!nginxResult.success) {
|
||||
@@ -268,29 +356,29 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// Update the step to show success
|
||||
const nginxStep = document.querySelectorAll('.step-item')[1];
|
||||
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 3: Generate SSL Certificate
|
||||
await updateStep(3, 'Generating SSL Certificate', 'Setting up secure HTTPS connection...');
|
||||
// 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 4: Create Proxy Host
|
||||
await updateStep(4, 'Creating Proxy Host', 'Setting up NGINX proxy host configuration...');
|
||||
// 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 5: Check Portainer connection
|
||||
await updateStep(5, 'Checking Portainer Connection', 'Verifying connection to Portainer...');
|
||||
// Step 7: Check Portainer connection
|
||||
await updateStep(7, 'Checking Portainer Connection', 'Verifying connection to Portainer...');
|
||||
const portainerResult = await checkPortainerConnection();
|
||||
|
||||
if (!portainerResult.success) {
|
||||
@@ -298,13 +386,13 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// Update the step to show success
|
||||
const portainerStep = document.querySelectorAll('.step-item')[4];
|
||||
const portainerStep = document.querySelectorAll('.step-item')[6];
|
||||
portainerStep.classList.remove('active');
|
||||
portainerStep.classList.add('completed');
|
||||
portainerStep.querySelector('.step-status').textContent = portainerResult.message;
|
||||
|
||||
// Step 6: Download Docker Compose
|
||||
await updateStep(6, 'Downloading Docker Compose', 'Fetching docker-compose.yml from repository...');
|
||||
// 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) {
|
||||
@@ -312,7 +400,7 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// Update the step to show success
|
||||
const dockerComposeStep = document.querySelectorAll('.step-item')[5];
|
||||
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';
|
||||
@@ -334,8 +422,8 @@ async function startLaunch(data) {
|
||||
};
|
||||
dockerComposeStep.querySelector('.step-content').appendChild(downloadButton);
|
||||
|
||||
// Step 7: Deploy Stack
|
||||
await updateStep(7, 'Deploying Stack', 'Launching your application stack...');
|
||||
// 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) {
|
||||
@@ -343,7 +431,7 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// Update the step to show success
|
||||
const stackDeployStep = document.querySelectorAll('.step-item')[6];
|
||||
const stackDeployStep = document.querySelectorAll('.step-item')[8];
|
||||
stackDeployStep.classList.remove('active');
|
||||
stackDeployStep.classList.add('completed');
|
||||
stackDeployStep.querySelector('.step-status').textContent =
|
||||
@@ -392,7 +480,7 @@ async function startLaunch(data) {
|
||||
stackDeployStep.querySelector('.step-content').appendChild(stackDetails);
|
||||
|
||||
// Save instance data
|
||||
await updateStep(8, 'Saving Instance Data', 'Storing instance information...');
|
||||
await updateStep(10, 'Saving Instance Data', 'Storing instance information...');
|
||||
try {
|
||||
const instanceData = {
|
||||
name: data.instanceName,
|
||||
@@ -407,15 +495,15 @@ async function startLaunch(data) {
|
||||
console.log('Saving instance data:', instanceData);
|
||||
const saveResult = await saveInstanceData(instanceData);
|
||||
console.log('Save result:', saveResult);
|
||||
await updateStep(8, 'Saving Instance Data', 'Instance data saved successfully');
|
||||
await updateStep(10, 'Saving Instance Data', 'Instance data saved successfully');
|
||||
} catch (error) {
|
||||
console.error('Error saving instance data:', error);
|
||||
await updateStep(8, 'Saving Instance Data', `Error: ${error.message}`);
|
||||
await updateStep(10, 'Saving Instance Data', `Error: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Update the step to show success
|
||||
const saveDataStep = document.querySelectorAll('.step-item')[7];
|
||||
const saveDataStep = document.querySelectorAll('.step-item')[9];
|
||||
saveDataStep.classList.remove('active');
|
||||
saveDataStep.classList.add('completed');
|
||||
saveDataStep.querySelector('.step-status').textContent = 'Successfully saved instance data';
|
||||
@@ -453,7 +541,7 @@ async function startLaunch(data) {
|
||||
saveDataStep.querySelector('.step-content').appendChild(instanceDetails);
|
||||
|
||||
// After saving instance data, add the health check step
|
||||
await updateStep(9, 'Health Check', 'Verifying instance health...');
|
||||
await updateStep(11, 'Health Check', 'Verifying instance health...');
|
||||
const healthResult = await checkInstanceHealth(`https://${data.webAddresses[0]}`);
|
||||
|
||||
if (!healthResult.success) {
|
||||
@@ -461,7 +549,7 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// Add a retry button if health check fails
|
||||
const healthStep = document.querySelectorAll('.step-item')[8];
|
||||
const healthStep = document.querySelectorAll('.step-item')[10];
|
||||
if (!healthResult.success) {
|
||||
const retryButton = document.createElement('button');
|
||||
retryButton.className = 'btn btn-sm btn-warning mt-2';
|
||||
@@ -483,7 +571,7 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// After health check, add authentication step
|
||||
await updateStep(10, 'Instance Authentication', 'Setting up instance authentication...');
|
||||
await updateStep(12, 'Instance Authentication', 'Setting up instance authentication...');
|
||||
const authResult = await authenticateInstance(`https://${data.webAddresses[0]}`, data.instanceId);
|
||||
|
||||
if (!authResult.success) {
|
||||
@@ -491,7 +579,7 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// Update the auth step to show success
|
||||
const authStep = document.querySelectorAll('.step-item')[9];
|
||||
const authStep = document.querySelectorAll('.step-item')[11];
|
||||
authStep.classList.remove('active');
|
||||
authStep.classList.add('completed');
|
||||
authStep.querySelector('.step-status').textContent = authResult.alreadyAuthenticated ?
|
||||
@@ -538,8 +626,8 @@ async function startLaunch(data) {
|
||||
`;
|
||||
authStep.querySelector('.step-content').appendChild(authDetails);
|
||||
|
||||
// Step 11: Apply Company Information
|
||||
await updateStep(11, 'Apply Company Information', 'Configuring company details...');
|
||||
// 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) {
|
||||
@@ -547,7 +635,7 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// Update the company step to show success
|
||||
const companyStep = document.querySelectorAll('.step-item')[10];
|
||||
const companyStep = document.querySelectorAll('.step-item')[12];
|
||||
companyStep.classList.remove('active');
|
||||
companyStep.classList.add('completed');
|
||||
companyStep.querySelector('.step-status').textContent = 'Successfully applied company information';
|
||||
@@ -596,8 +684,8 @@ async function startLaunch(data) {
|
||||
`;
|
||||
companyStep.querySelector('.step-content').appendChild(companyDetails);
|
||||
|
||||
// Step 12: Apply Colors
|
||||
await updateStep(12, 'Apply Colors', 'Configuring color scheme...');
|
||||
// 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) {
|
||||
@@ -605,7 +693,7 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// Update the colors step to show success
|
||||
const colorsStep = document.querySelectorAll('.step-item')[11];
|
||||
const colorsStep = document.querySelectorAll('.step-item')[13];
|
||||
colorsStep.classList.remove('active');
|
||||
colorsStep.classList.add('completed');
|
||||
colorsStep.querySelector('.step-status').textContent = 'Successfully applied color scheme';
|
||||
@@ -645,8 +733,8 @@ async function startLaunch(data) {
|
||||
`;
|
||||
colorsStep.querySelector('.step-content').appendChild(colorsDetails);
|
||||
|
||||
// Step 13: Update Admin Credentials
|
||||
await updateStep(13, 'Update Admin Credentials', 'Setting up admin account...');
|
||||
// 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) {
|
||||
@@ -654,7 +742,7 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// Update the credentials step to show success
|
||||
const credentialsStep = document.querySelectorAll('.step-item')[12];
|
||||
const credentialsStep = document.querySelectorAll('.step-item')[14];
|
||||
credentialsStep.classList.remove('active');
|
||||
credentialsStep.classList.add('completed');
|
||||
|
||||
@@ -719,8 +807,8 @@ async function startLaunch(data) {
|
||||
`;
|
||||
credentialsStep.querySelector('.step-content').appendChild(credentialsDetails);
|
||||
|
||||
// Step 14: Copy SMTP Settings
|
||||
await updateStep(14, 'Copy SMTP Settings', 'Configuring email settings...');
|
||||
// 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) {
|
||||
@@ -728,7 +816,7 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// Update the SMTP step to show success
|
||||
const smtpStep = document.querySelectorAll('.step-item')[13];
|
||||
const smtpStep = document.querySelectorAll('.step-item')[15];
|
||||
smtpStep.classList.remove('active');
|
||||
smtpStep.classList.add('completed');
|
||||
smtpStep.querySelector('.step-status').textContent = 'Successfully copied SMTP settings';
|
||||
@@ -789,8 +877,8 @@ async function startLaunch(data) {
|
||||
`;
|
||||
smtpStep.querySelector('.step-content').appendChild(smtpDetails);
|
||||
|
||||
// Step 15: Send Completion Email
|
||||
await updateStep(15, 'Send Completion Email', 'Sending notification to client...');
|
||||
// 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) {
|
||||
@@ -798,7 +886,7 @@ async function startLaunch(data) {
|
||||
}
|
||||
|
||||
// Update the email step to show success
|
||||
const emailStep = document.querySelectorAll('.step-item')[14];
|
||||
const emailStep = document.querySelectorAll('.step-item')[16];
|
||||
emailStep.classList.remove('active');
|
||||
emailStep.classList.add('completed');
|
||||
emailStep.querySelector('.step-status').textContent = 'Successfully sent completion email';
|
||||
@@ -983,31 +1071,128 @@ Thank you for choosing DocuPulse!
|
||||
|
||||
} catch (error) {
|
||||
console.error('Launch failed:', error);
|
||||
await updateStep(15, 'Send Completion Email', `Error: ${error.message}`);
|
||||
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-dns', {
|
||||
const response = await fetch('/api/check-cloudflare-connection', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify({ domains })
|
||||
'X-CSRF-Token': window.csrfToken
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to check DNS records');
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Failed to check Cloudflare connection');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('DNS check result:', result);
|
||||
return result;
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error checking DNS records:', 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;
|
||||
}
|
||||
}
|
||||
@@ -1390,7 +1575,7 @@ async function createProxyHost(domains, port, sslCertificateId) {
|
||||
`;
|
||||
|
||||
// Update the proxy step to show success and add the results
|
||||
const proxyStep = document.querySelectorAll('.step-item')[3];
|
||||
const proxyStep = document.querySelectorAll('.step-item')[5];
|
||||
proxyStep.classList.remove('active');
|
||||
proxyStep.classList.add('completed');
|
||||
const statusText = proxyStep.querySelector('.step-status');
|
||||
@@ -1509,7 +1694,7 @@ async function generateSSLCertificate(domains) {
|
||||
}
|
||||
|
||||
// Update the SSL step to show success
|
||||
const sslStep = document.querySelectorAll('.step-item')[2];
|
||||
const sslStep = document.querySelectorAll('.step-item')[4];
|
||||
sslStep.classList.remove('active');
|
||||
sslStep.classList.add('completed');
|
||||
const sslStatusText = sslStep.querySelector('.step-status');
|
||||
@@ -1589,8 +1774,8 @@ function updateStep(stepNumber, title, description) {
|
||||
document.getElementById('currentStep').textContent = title;
|
||||
document.getElementById('stepDescription').textContent = description;
|
||||
|
||||
// Calculate progress based on total number of steps (14 steps total)
|
||||
const totalSteps = 14;
|
||||
// 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}%`;
|
||||
@@ -1674,7 +1859,11 @@ async function downloadDockerCompose(repo, branch) {
|
||||
|
||||
// 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
|
||||
|
||||
@@ -1709,10 +1898,135 @@ async function deployStack(dockerComposeContent, stackName, port) {
|
||||
}
|
||||
|
||||
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.data || result,
|
||||
status: 'deployed',
|
||||
note: 'Deployment successful, status unknown'
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error deploying stack:', error);
|
||||
return {
|
||||
|
||||
@@ -435,6 +435,100 @@ async function saveGitConnection(event, provider) {
|
||||
saveModal.show();
|
||||
}
|
||||
|
||||
// Test Cloudflare Connection
|
||||
async function testCloudflareConnection() {
|
||||
const saveModal = new bootstrap.Modal(document.getElementById('saveConnectionModal'));
|
||||
const messageElement = document.getElementById('saveConnectionMessage');
|
||||
messageElement.textContent = 'Testing connection...';
|
||||
messageElement.className = '';
|
||||
saveModal.show();
|
||||
|
||||
try {
|
||||
const email = document.getElementById('cloudflareEmail').value;
|
||||
const apiKey = document.getElementById('cloudflareApiKey').value;
|
||||
const zoneId = document.getElementById('cloudflareZone').value;
|
||||
const serverIp = document.getElementById('cloudflareServerIp').value;
|
||||
|
||||
if (!email || !apiKey || !zoneId || !serverIp) {
|
||||
throw new Error('Please fill in all required fields');
|
||||
}
|
||||
|
||||
const data = {
|
||||
email: email,
|
||||
api_key: apiKey,
|
||||
zone_id: zoneId,
|
||||
server_ip: serverIp
|
||||
};
|
||||
|
||||
const response = await fetch('/settings/test-cloudflare-connection', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Connection test failed');
|
||||
}
|
||||
|
||||
messageElement.textContent = 'Connection test successful!';
|
||||
messageElement.className = 'text-success';
|
||||
} catch (error) {
|
||||
messageElement.textContent = error.message || 'Connection test failed';
|
||||
messageElement.className = 'text-danger';
|
||||
}
|
||||
}
|
||||
|
||||
// Save Cloudflare Connection
|
||||
async function saveCloudflareConnection(event) {
|
||||
event.preventDefault();
|
||||
const saveModal = new bootstrap.Modal(document.getElementById('saveConnectionModal'));
|
||||
const messageElement = document.getElementById('saveConnectionMessage');
|
||||
messageElement.textContent = '';
|
||||
messageElement.className = '';
|
||||
|
||||
try {
|
||||
const email = document.getElementById('cloudflareEmail').value;
|
||||
const apiKey = document.getElementById('cloudflareApiKey').value;
|
||||
const zoneId = document.getElementById('cloudflareZone').value;
|
||||
const serverIp = document.getElementById('cloudflareServerIp').value;
|
||||
|
||||
if (!email || !apiKey || !zoneId || !serverIp) {
|
||||
throw new Error('Please fill in all required fields');
|
||||
}
|
||||
|
||||
const response = await fetch('/settings/save-cloudflare-connection', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
api_key: apiKey,
|
||||
zone_id: zoneId,
|
||||
server_ip: serverIp
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Failed to save settings');
|
||||
}
|
||||
|
||||
messageElement.textContent = 'Settings saved successfully!';
|
||||
messageElement.className = 'text-success';
|
||||
} catch (error) {
|
||||
messageElement.textContent = error.message || 'Failed to save settings';
|
||||
messageElement.className = 'text-danger';
|
||||
}
|
||||
|
||||
saveModal.show();
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const gitSettings = JSON.parse(document.querySelector('meta[name="git-settings"]').getAttribute('content'));
|
||||
|
||||
Reference in New Issue
Block a user