much improved launch process

This commit is contained in:
2025-06-23 14:50:37 +02:00
parent 033f82eb2b
commit 40b1a63cf5
4 changed files with 225 additions and 74 deletions

View File

@@ -499,14 +499,44 @@ async function startLaunch(data) {
`;
stackDeployStepElement.querySelector('.step-content').appendChild(stackProgressDiv);
const stackResult = await deployStack(dockerComposeResult.content, data.instanceName, data.port);
const stackResult = await deployStack(dockerComposeResult.content, `docupulse_${data.port}`, data.port);
launchReport.steps.push({
step: 'Stack Deployment',
status: stackResult.success ? 'success' : 'error',
details: stackResult
});
// Handle different stack deployment scenarios
if (!stackResult.success) {
throw new Error(stackResult.error || 'Failed to deploy stack');
// Check if this is a timeout but the stack might still be deploying
if (stackResult.error && stackResult.error.includes('timed out')) {
console.log('Stack deployment timed out, but may still be in progress');
// Update the step to show warning instead of error
const stackDeployStep = document.querySelectorAll('.step-item')[8];
stackDeployStep.classList.remove('active');
stackDeployStep.classList.add('warning');
stackDeployStep.querySelector('.step-status').textContent = 'Stack deployment timed out but may still be in progress';
// Add a note about the timeout
const timeoutNote = document.createElement('div');
timeoutNote.className = 'alert alert-warning mt-2';
timeoutNote.innerHTML = `
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Note:</strong> The stack deployment request timed out, but the deployment may still be in progress.
You can check the status in your Portainer dashboard or wait a few minutes and refresh this page.
`;
stackDeployStep.querySelector('.step-content').appendChild(timeoutNote);
// Continue with the process using the available data
stackResult.data = stackResult.data || {
name: `docupulse_${data.port}`,
status: 'creating',
id: null
};
} else {
throw new Error(stackResult.error || 'Failed to deploy stack');
}
}
// Update the step to show success
@@ -2146,6 +2176,18 @@ async function checkInstanceHealth(instanceUrl) {
attempts: currentAttempt,
elapsedTime: elapsedTime
};
} else if (data.status === 'inactive') {
console.log(`Stack ${stackName} is inactive, continuing to poll...`);
lastKnownStatus = 'inactive';
if (progressText) {
progressText.textContent = `Stack is starting up (${attempts} attempts, ${Math.round(elapsed / 1000)}s elapsed)...`;
}
} else if (data.status === 'starting') {
console.log(`Stack ${stackName} is starting up, continuing to poll...`);
lastKnownStatus = 'starting';
if (progressText) {
progressText.textContent = `Stack is initializing (${attempts} attempts, ${Math.round(elapsed / 1000)}s elapsed)...`;
}
} else {
throw new Error('Instance is not healthy');
}
@@ -2597,14 +2639,20 @@ async function deployStack(dockerComposeContent, stackName, port) {
// 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'
}
};
// Update progress to show that we're now polling
const progressBar = document.getElementById('stackProgress');
const progressText = document.getElementById('stackProgressText');
if (progressBar && progressText) {
progressBar.style.width = '25%';
progressBar.textContent = '25%';
progressText.textContent = 'Stack creation initiated (timed out, but continuing to monitor)...';
}
// Start polling immediately since the stack creation was initiated
console.log('Starting to poll for stack status after 504 timeout...');
const pollResult = await pollStackStatus(`docupulse_${port}`, 15 * 60 * 1000); // 15 minutes max
return pollResult;
}
if (!response.ok) {
@@ -2618,7 +2666,7 @@ async function deployStack(dockerComposeContent, stackName, port) {
// 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
const pollResult = await pollStackStatus(`docupulse_${port}`, 10 * 60 * 1000); // 10 minutes max
return pollResult;
}
@@ -2638,12 +2686,26 @@ async function deployStack(dockerComposeContent, stackName, port) {
}
// Function to poll stack status
async function pollStackStatus(stackName, maxWaitTime = 10 * 60 * 1000) {
async function pollStackStatus(stackName, maxWaitTime = 15 * 60 * 1000) {
const startTime = Date.now();
const pollInterval = 5000; // 5 seconds
let attempts = 0;
let lastKnownStatus = 'unknown';
console.log(`Starting to poll stack status for: ${stackName}`);
// Validate stack name
if (!stackName || typeof stackName !== 'string') {
console.error('Invalid stack name provided to pollStackStatus:', stackName);
return {
success: false,
error: `Invalid stack name: ${stackName}`,
data: {
name: stackName,
status: 'error'
}
};
}
console.log(`Starting to poll stack status for: ${stackName} (max wait: ${maxWaitTime / 1000}s)`);
// Update progress indicator
const progressBar = document.getElementById('stackProgress');
@@ -2653,25 +2715,29 @@ async function pollStackStatus(stackName, maxWaitTime = 10 * 60 * 1000) {
attempts++;
console.log(`Polling attempt ${attempts} for stack: ${stackName}`);
// Update progress
// Update progress - start at 25% if we came from a 504 timeout, otherwise start at 0%
const elapsed = Date.now() - startTime;
const progress = Math.min((elapsed / maxWaitTime) * 100, 95); // Cap at 95% until complete
const baseProgress = progressBar && progressBar.style.width === '25%' ? 25 : 0;
const progress = Math.min(baseProgress + (elapsed / maxWaitTime) * 70, 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 requestBody = {
stack_name: stackName
};
console.log(`Sending stack status check request:`, requestBody);
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
})
body: JSON.stringify(requestBody),
timeout: 30000 // 30 second timeout for status checks
});
if (response.ok) {
@@ -2684,7 +2750,9 @@ async function pollStackStatus(stackName, maxWaitTime = 10 * 60 * 1000) {
if (progressBar && progressText) {
progressBar.style.width = '100%';
progressBar.textContent = '100%';
progressText.textContent = 'Stack is now active!';
progressBar.classList.remove('progress-bar-animated');
progressBar.classList.add('bg-success');
progressText.textContent = 'Stack is now active and running!';
}
return {
success: true,
@@ -2696,35 +2764,57 @@ async function pollStackStatus(stackName, maxWaitTime = 10 * 60 * 1000) {
};
} else if (result.data && result.data.status === 'partial') {
console.log(`Stack ${stackName} is partially running, continuing to poll...`);
lastKnownStatus = 'partial';
if (progressText) {
progressText.textContent = `Stack is partially running (attempt ${attempts})...`;
progressText.textContent = `Stack is partially running (${attempts} attempts, ${Math.round(elapsed / 1000)}s elapsed)...`;
}
} else if (result.data && result.data.status === 'inactive') {
console.log(`Stack ${stackName} is inactive, continuing to poll...`);
lastKnownStatus = 'inactive';
if (progressText) {
progressText.textContent = `Stack is starting up (attempt ${attempts})...`;
progressText.textContent = `Stack is starting up (${attempts} attempts, ${Math.round(elapsed / 1000)}s elapsed)...`;
}
} else if (result.data && result.data.status === 'starting') {
console.log(`Stack ${stackName} exists and is starting up - continuing to next step`);
// Stack exists, we can continue - no need to wait for all services
if (progressBar && progressText) {
progressBar.style.width = '100%';
progressBar.textContent = '100%';
progressBar.classList.remove('progress-bar-animated');
progressBar.classList.add('bg-success');
progressText.textContent = 'Stack created successfully!';
}
return {
success: true,
data: {
name: stackName,
id: result.data.stack_id,
status: 'starting'
}
};
} else {
console.log(`Stack ${stackName} status unknown, continuing to poll...`);
lastKnownStatus = 'unknown';
if (progressText) {
progressText.textContent = `Checking stack status (attempt ${attempts})...`;
progressText.textContent = `Checking stack status (${attempts} attempts, ${Math.round(elapsed / 1000)}s elapsed)...`;
}
}
} else if (response.status === 404) {
console.log(`Stack ${stackName} not found yet, continuing to poll...`);
lastKnownStatus = 'not_found';
if (progressText) {
progressText.textContent = `Stack not found yet (attempt ${attempts})...`;
progressText.textContent = `Stack not found yet (${attempts} attempts, ${Math.round(elapsed / 1000)}s elapsed)...`;
}
} else {
console.log(`Stack status check failed with status ${response.status}, continuing to poll...`);
if (progressText) {
progressText.textContent = `Status check failed (attempt ${attempts})...`;
progressText.textContent = `Status check failed (${attempts} attempts, ${Math.round(elapsed / 1000)}s elapsed)...`;
}
}
} catch (error) {
console.error(`Error polling stack status (attempt ${attempts}):`, error);
if (progressText) {
progressText.textContent = `Error checking status (attempt ${attempts})...`;
progressText.textContent = `Error checking status (${attempts} attempts, ${Math.round(elapsed / 1000)}s elapsed)...`;
}
}
@@ -2738,10 +2828,17 @@ async function pollStackStatus(stackName, maxWaitTime = 10 * 60 * 1000) {
progressBar.style.width = '100%';
progressBar.classList.remove('progress-bar-animated');
progressBar.classList.add('bg-warning');
progressText.textContent = 'Stack deployment timed out';
progressText.textContent = `Stack deployment timed out after ${Math.round(maxWaitTime / 1000)}s. Last known status: ${lastKnownStatus}`;
}
// Return a more informative error message
const statusMessage = lastKnownStatus !== 'unknown' ? ` (last known status: ${lastKnownStatus})` : '';
return {
success: false,
error: `Stack deployment timed out after ${maxWaitTime / 1000} seconds. The stack may still be deploying.`
error: `Stack deployment timed out after ${Math.round(maxWaitTime / 1000)} seconds${statusMessage}. The stack may still be deploying in the background.`,
data: {
name: stackName,
status: lastKnownStatus
}
};
}