diff --git a/routes/launch_api.py b/routes/launch_api.py
index 323bd32..fb55099 100644
--- a/routes/launch_api.py
+++ b/routes/launch_api.py
@@ -849,9 +849,12 @@ def deploy_stack():
if stack['Name'] == data['name']:
current_app.logger.info(f"Found existing stack: {stack['Name']} (ID: {stack['Id']})")
return jsonify({
- 'name': stack['Name'],
- 'id': stack['Id'],
- 'status': 'existing'
+ 'success': True,
+ 'data': {
+ 'name': stack['Name'],
+ 'id': stack['Id'],
+ 'status': 'existing'
+ }
})
# If no existing stack found, proceed with creation
@@ -864,7 +867,7 @@ def deploy_stack():
# Add endpointId as a query parameter
params = {'endpointId': endpoint_id}
- # Set a longer timeout for stack creation (10 minutes)
+ # Use a shorter timeout for stack creation initiation (2 minutes)
create_response = requests.post(
url,
headers={
@@ -874,7 +877,7 @@ def deploy_stack():
},
params=params,
json=request_body,
- timeout=600 # 10 minutes timeout for stack creation
+ timeout=120 # 2 minutes timeout for stack creation initiation
)
# Log the response details
@@ -894,15 +897,20 @@ def deploy_stack():
return jsonify({'error': f'Failed to create stack: {error_message}'}), 500
stack_info = create_response.json()
+ current_app.logger.info(f"Stack creation initiated: {stack_info['Name']} (ID: {stack_info['Id']})")
+
return jsonify({
- 'name': stack_info['Name'],
- 'id': stack_info['Id'],
- 'status': 'created'
+ 'success': True,
+ 'data': {
+ 'name': stack_info['Name'],
+ 'id': stack_info['Id'],
+ 'status': 'creating'
+ }
})
except requests.exceptions.Timeout:
- current_app.logger.error("Request timed out while deploying stack")
- return jsonify({'error': 'Request timed out while deploying stack. The operation may still be in progress.'}), 504
+ current_app.logger.error("Request timed out while initiating stack deployment")
+ return jsonify({'error': 'Request timed out while initiating stack deployment. The operation may still be in progress.'}), 504
except Exception as e:
current_app.logger.error(f"Error deploying stack: {str(e)}")
return jsonify({'error': str(e)}), 500
diff --git a/static/js/launch_progress.js b/static/js/launch_progress.js
index af34ca8..a0999e8 100644
--- a/static/js/launch_progress.js
+++ b/static/js/launch_progress.js
@@ -481,6 +481,24 @@ async function startLaunch(data) {
// Step 9: Deploy Stack
await updateStep(9, 'Deploying Stack', 'Launching your application stack...');
+
+ // Add progress indicator for stack deployment
+ const stackDeployStepElement = document.querySelectorAll('.step-item')[8];
+ const stackProgressDiv = document.createElement('div');
+ stackProgressDiv.className = 'mt-2';
+ stackProgressDiv.innerHTML = `
+
+ Initiating stack deployment...
+ `;
+ stackDeployStepElement.querySelector('.step-content').appendChild(stackProgressDiv);
+
const stackResult = await deployStack(dockerComposeResult.content, data.instanceName, data.port);
launchReport.steps.push({
step: 'Stack Deployment',
@@ -498,6 +516,8 @@ async function startLaunch(data) {
stackDeployStep.querySelector('.step-status').textContent =
stackResult.data.status === 'existing' ?
'Using existing stack' :
+ stackResult.data.status === 'active' ?
+ 'Successfully deployed and activated stack' :
'Successfully deployed stack';
// Add stack details
@@ -522,13 +542,13 @@ async function startLaunch(data) {
| Stack ID |
- ${stackResult.data.id} |
+ ${stackResult.data.id || 'Will be determined during deployment'} |
| Status |
-
- ${stackResult.data.status === 'existing' ? 'Existing' : 'Deployed'}
+
+ ${stackResult.data.status === 'existing' ? 'Existing' : stackResult.data.status === 'active' ? 'Active' : 'Deployed'}
|
@@ -547,7 +567,7 @@ async function startLaunch(data) {
name: data.instanceName,
port: data.port,
domains: data.webAddresses,
- stack_id: stackResult.data.id,
+ stack_id: stackResult.data.id || null, // May be null if we got a 504
stack_name: stackResult.data.name,
status: stackResult.data.status,
repository: data.repository,
@@ -2006,7 +2026,7 @@ async function saveInstanceData(instanceData) {
main_url: `https://${instanceData.domains[0]}`,
status: 'inactive',
port: instanceData.port,
- stack_id: instanceData.stack_id,
+ stack_id: instanceData.stack_id || '', // Use empty string if null
stack_name: instanceData.stack_name,
repository: instanceData.repository,
branch: instanceData.branch
@@ -2031,7 +2051,7 @@ async function saveInstanceData(instanceData) {
main_url: `https://${instanceData.domains[0]}`,
status: 'inactive',
port: instanceData.port,
- stack_id: instanceData.stack_id,
+ stack_id: instanceData.stack_id || '', // Use empty string if null
stack_name: instanceData.stack_name,
repository: instanceData.repository,
branch: instanceData.branch
@@ -2536,9 +2556,6 @@ async function checkStackExists(stackName) {
async function deployStack(dockerComposeContent, stackName, port) {
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: {
@@ -2574,22 +2591,41 @@ async function deployStack(dockerComposeContent, stackName, port) {
value: new Date().toISOString()
}
]
- }),
- signal: controller.signal
+ })
});
- clearTimeout(timeoutId); // Clear the timeout if the request completes
+ // Handle 504 Gateway Timeout as successful initiation
+ if (response.status === 504) {
+ console.log('Received 504 Gateway Timeout - stack creation may still be in progress');
+ return {
+ success: true,
+ data: {
+ name: stackName,
+ id: null, // Will be determined during polling
+ status: 'creating'
+ }
+ };
+ }
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to deploy stack');
}
- // Return success result with response data
const result = await response.json();
+ console.log('Stack deployment initiated:', result);
+
+ // If stack is being created, poll for status
+ if (result.data.status === 'creating') {
+ console.log('Stack is being created, polling for status...');
+ const pollResult = await pollStackStatus(stackName, 10 * 60 * 1000); // 10 minutes max
+ return pollResult;
+ }
+
+ // Return success result with response data
return {
success: true,
- data: result
+ data: result.data
};
} catch (error) {
@@ -2599,4 +2635,113 @@ async function deployStack(dockerComposeContent, stackName, port) {
error: error.message
};
}
+}
+
+// Function to poll stack status
+async function pollStackStatus(stackName, maxWaitTime = 10 * 60 * 1000) {
+ const startTime = Date.now();
+ const pollInterval = 5000; // 5 seconds
+ let attempts = 0;
+
+ console.log(`Starting to poll stack status for: ${stackName}`);
+
+ // Update progress indicator
+ const progressBar = document.getElementById('stackProgress');
+ const progressText = document.getElementById('stackProgressText');
+
+ while (Date.now() - startTime < maxWaitTime) {
+ attempts++;
+ console.log(`Polling attempt ${attempts} for stack: ${stackName}`);
+
+ // Update progress
+ const elapsed = Date.now() - startTime;
+ const progress = Math.min((elapsed / maxWaitTime) * 100, 95); // Cap at 95% until complete
+ if (progressBar && progressText) {
+ progressBar.style.width = `${progress}%`;
+ progressBar.textContent = `${Math.round(progress)}%`;
+ progressText.textContent = `Checking stack status (attempt ${attempts})...`;
+ }
+
+ try {
+ const response = 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: stackName
+ })
+ });
+
+ if (response.ok) {
+ const result = await response.json();
+ console.log(`Stack status check result:`, result);
+
+ if (result.data && result.data.status === 'active') {
+ console.log(`Stack ${stackName} is now active!`);
+ // Update progress to 100%
+ if (progressBar && progressText) {
+ progressBar.style.width = '100%';
+ progressBar.textContent = '100%';
+ progressText.textContent = 'Stack is now active!';
+ }
+ return {
+ success: true,
+ data: {
+ name: stackName,
+ id: result.data.stack_id,
+ status: 'active'
+ }
+ };
+ } else if (result.data && result.data.status === 'partial') {
+ console.log(`Stack ${stackName} is partially running, continuing to poll...`);
+ if (progressText) {
+ progressText.textContent = `Stack is partially running (attempt ${attempts})...`;
+ }
+ } else if (result.data && result.data.status === 'inactive') {
+ console.log(`Stack ${stackName} is inactive, continuing to poll...`);
+ if (progressText) {
+ progressText.textContent = `Stack is starting up (attempt ${attempts})...`;
+ }
+ } else {
+ console.log(`Stack ${stackName} status unknown, continuing to poll...`);
+ if (progressText) {
+ progressText.textContent = `Checking stack status (attempt ${attempts})...`;
+ }
+ }
+ } else if (response.status === 404) {
+ console.log(`Stack ${stackName} not found yet, continuing to poll...`);
+ if (progressText) {
+ progressText.textContent = `Stack not found yet (attempt ${attempts})...`;
+ }
+ } else {
+ console.log(`Stack status check failed with status ${response.status}, continuing to poll...`);
+ if (progressText) {
+ progressText.textContent = `Status check failed (attempt ${attempts})...`;
+ }
+ }
+ } catch (error) {
+ console.error(`Error polling stack status (attempt ${attempts}):`, error);
+ if (progressText) {
+ progressText.textContent = `Error checking status (attempt ${attempts})...`;
+ }
+ }
+
+ // Wait before next poll
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
+ }
+
+ // If we get here, we've timed out
+ console.error(`Stack status polling timed out after ${maxWaitTime / 1000} seconds`);
+ if (progressBar && progressText) {
+ progressBar.style.width = '100%';
+ progressBar.classList.remove('progress-bar-animated');
+ progressBar.classList.add('bg-warning');
+ progressText.textContent = 'Stack deployment timed out';
+ }
+ return {
+ success: false,
+ error: `Stack deployment timed out after ${maxWaitTime / 1000} seconds. The stack may still be deploying.`
+ };
}
\ No newline at end of file