better update?

This commit is contained in:
2025-06-25 15:07:35 +02:00
parent 81675af837
commit 77032062a1
2 changed files with 415 additions and 99 deletions

View File

@@ -1395,7 +1395,7 @@ async function startUpdate(data) {
// Step 3: Deploy Updated Stack
await updateStep(3, 'Deploying Updated Stack', 'Deploying the updated application stack...');
// Get the existing instance information to extract port
// Get the existing instance information to extract port and stack details
const instanceResponse = await fetch(`/api/instances/${data.instanceId}`);
if (!instanceResponse.ok) {
throw new Error('Failed to get instance information');
@@ -1403,15 +1403,18 @@ async function startUpdate(data) {
const instanceData = await instanceResponse.json();
const port = instanceData.instance.name; // Assuming the instance name is the port
// Generate new stack name with timestamp
const newStackName = generateStackName(port);
// Check if we have an existing stack ID
if (!instanceData.instance.portainer_stack_id) {
throw new Error('No existing stack found for this instance. Cannot perform update.');
}
const stackResult = await deployStack(dockerComposeResult.content, newStackName, port);
// Update the existing stack instead of creating a new one
const stackResult = await updateStack(dockerComposeResult.content, instanceData.instance.portainer_stack_id, port);
if (!stackResult.success) {
throw new Error(`Failed to deploy updated stack: ${stackResult.error}`);
throw new Error(`Failed to update stack: ${stackResult.error}`);
}
launchReport.steps.push({
step: 'Stack Deployment',
step: 'Stack Update',
status: 'success',
details: stackResult
});
@@ -1422,8 +1425,8 @@ async function startUpdate(data) {
name: instanceData.instance.name,
port: port,
domains: instanceData.instance.main_url ? [instanceData.instance.main_url.replace(/^https?:\/\//, '')] : [],
stack_id: stackResult.data.id || null,
stack_name: newStackName,
stack_id: instanceData.instance.portainer_stack_id, // Keep the same stack ID
stack_name: instanceData.instance.portainer_stack_name, // Keep the same stack name
status: stackResult.data.status,
repository: data.repository,
branch: data.branch,
@@ -1468,7 +1471,7 @@ async function startUpdate(data) {
successDetails.className = 'mt-3';
// Calculate the volume names based on the stack name
const stackNameParts = newStackName.split('_');
const stackNameParts = stackResult.data.name.split('_');
let volumeNames = [];
if (stackNameParts.length >= 3) {
const timestamp = stackNameParts.slice(2).join('_');
@@ -1482,7 +1485,7 @@ async function startUpdate(data) {
successDetails.innerHTML = `
<div class="alert alert-success">
<h6><i class="fas fa-check-circle me-2"></i>Update Completed Successfully!</h6>
<p class="mb-2">Your instance has been updated with the latest version from the repository.</p>
<p class="mb-2">Your instance has been updated with the latest version from the repository. All existing data and volumes have been preserved.</p>
<div class="row">
<div class="col-md-6">
<strong>Repository:</strong> ${data.repository}<br>
@@ -1490,18 +1493,16 @@ async function startUpdate(data) {
<strong>New Version:</strong> ${dockerComposeResult.latest_tag || dockerComposeResult.commit_hash || 'unknown'}
</div>
<div class="col-md-6">
<strong>New Stack Name:</strong> ${newStackName}<br>
<strong>Stack Name:</strong> ${stackResult.data.name}<br>
<strong>Instance URL:</strong> <a href="${instanceData.instance.main_url}" target="_blank">${instanceData.instance.main_url}</a>
</div>
</div>
${volumeNames.length > 0 ? `
<div class="mt-3">
<strong>New Volume Names:</strong>
<div class="small mt-1">
${volumeNames.map(name => `<code class="text-primary">${name}</code>`).join('<br>')}
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<strong>Data Preservation:</strong> All existing data, volumes, and configurations have been preserved during this update.
</div>
</div>
` : ''}
</div>
`;
successStep.querySelector('.step-content').appendChild(successDetails);
@@ -3129,26 +3130,26 @@ async function deployStack(dockerComposeContent, stackName, port) {
}
// Function to poll stack status
async function pollStackStatus(stackName, maxWaitTime = 15 * 60 * 1000) {
async function pollStackStatus(stackIdentifier, maxWaitTime = 15 * 60 * 1000) {
const startTime = Date.now();
const pollInterval = 5000; // 5 seconds
let attempts = 0;
let lastKnownStatus = 'unknown';
// Validate stack name
if (!stackName || typeof stackName !== 'string') {
console.error('Invalid stack name provided to pollStackStatus:', stackName);
// Validate stack identifier (can be name or ID)
if (!stackIdentifier || typeof stackIdentifier !== 'string') {
console.error('Invalid stack identifier provided to pollStackStatus:', stackIdentifier);
return {
success: false,
error: `Invalid stack name: ${stackName}`,
error: `Invalid stack identifier: ${stackIdentifier}`,
data: {
name: stackName,
name: stackIdentifier,
status: 'error'
}
};
}
console.log(`Starting to poll stack status for: ${stackName} (max wait: ${maxWaitTime / 1000}s)`);
console.log(`Starting to poll stack status for: ${stackIdentifier} (max wait: ${maxWaitTime / 1000}s)`);
// Update progress indicator
const progressBar = document.getElementById('launchProgress');
@@ -3156,7 +3157,7 @@ async function pollStackStatus(stackName, maxWaitTime = 15 * 60 * 1000) {
while (Date.now() - startTime < maxWaitTime) {
attempts++;
console.log(`Polling attempt ${attempts} for stack: ${stackName}`);
console.log(`Polling attempt ${attempts} for stack: ${stackIdentifier}`);
// Update progress - start at 25% if we came from a 504 timeout, otherwise start at 0%
const elapsed = Date.now() - startTime;
@@ -3168,9 +3169,12 @@ async function pollStackStatus(stackName, maxWaitTime = 15 * 60 * 1000) {
}
try {
const requestBody = {
stack_name: stackName
};
// Determine if this is a stack ID (numeric) or stack name
const isStackId = /^\d+$/.test(stackIdentifier);
const requestBody = isStackId ?
{ stack_id: stackIdentifier } :
{ stack_name: stackIdentifier };
console.log(`Sending stack status check request:`, requestBody);
const response = await fetch('/api/admin/check-stack-status', {
@@ -3188,7 +3192,7 @@ async function pollStackStatus(stackName, maxWaitTime = 15 * 60 * 1000) {
console.log(`Stack status check result:`, result);
if (result.data && result.data.status === 'active') {
console.log(`Stack ${stackName} is now active!`);
console.log(`Stack ${stackIdentifier} is now active!`);
// Update progress to 100%
if (progressBar && progressText) {
progressBar.style.width = '100%';
@@ -3200,25 +3204,25 @@ async function pollStackStatus(stackName, maxWaitTime = 15 * 60 * 1000) {
return {
success: true,
data: {
name: stackName,
id: result.data.stack_id,
name: result.data.name || stackIdentifier,
id: result.data.stack_id || stackIdentifier,
status: 'active'
}
};
} else if (result.data && result.data.status === 'partial') {
console.log(`Stack ${stackName} is partially running, continuing to poll...`);
console.log(`Stack ${stackIdentifier} is partially running, continuing to poll...`);
lastKnownStatus = 'partial';
if (progressText) {
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...`);
console.log(`Stack ${stackIdentifier} 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 (result.data && result.data.status === 'starting') {
console.log(`Stack ${stackName} exists and is starting up - continuing to next step`);
console.log(`Stack ${stackIdentifier} 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%';
@@ -3230,20 +3234,20 @@ async function pollStackStatus(stackName, maxWaitTime = 15 * 60 * 1000) {
return {
success: true,
data: {
name: stackName,
id: result.data.stack_id,
name: result.data.name || stackIdentifier,
id: result.data.stack_id || stackIdentifier,
status: 'starting'
}
};
} else {
console.log(`Stack ${stackName} status unknown, continuing to poll...`);
console.log(`Stack ${stackIdentifier} status unknown, continuing to poll...`);
lastKnownStatus = 'unknown';
if (progressText) {
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...`);
console.log(`Stack ${stackIdentifier} not found yet, continuing to poll...`);
lastKnownStatus = 'not_found';
if (progressText) {
progressText.textContent = `Stack not found yet (${attempts} attempts, ${Math.round(elapsed / 1000)}s elapsed)...`;
@@ -3280,11 +3284,11 @@ async function pollStackStatus(stackName, maxWaitTime = 15 * 60 * 1000) {
success: false,
error: `Stack deployment timed out after ${Math.round(maxWaitTime / 1000)} seconds${statusMessage}. The stack may still be deploying in the background.`,
data: {
name: stackName,
name: stackIdentifier,
status: lastKnownStatus
}
};
}
}
// Helper function to generate unique stack names with timestamp
function generateStackName(port) {
@@ -3296,4 +3300,163 @@ function generateStackName(port) {
now.getMinutes().toString().padStart(2, '0') +
now.getSeconds().toString().padStart(2, '0');
return `docupulse_${port}_${timestamp}`;
}
// Add new function to update existing stack
async function updateStack(dockerComposeContent, stackId, port) {
try {
console.log('Updating existing stack:', stackId);
console.log('Port:', port);
console.log('Modified docker-compose content length:', dockerComposeContent.length);
const response = await fetch('/api/admin/update-stack', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({
stack_id: stackId,
StackFileContent: dockerComposeContent,
Env: [
{
name: 'PORT',
value: port.toString()
},
{
name: 'ISMASTER',
value: 'false'
},
{
name: 'APP_VERSION',
value: window.currentDeploymentVersion || 'unknown'
},
{
name: 'GIT_COMMIT',
value: window.currentDeploymentCommit || 'unknown'
},
{
name: 'GIT_BRANCH',
value: window.currentDeploymentBranch || 'unknown'
},
{
name: 'DEPLOYED_AT',
value: new Date().toISOString()
}
]
})
});
console.log('Update response status:', response.status);
console.log('Update response ok:', response.ok);
console.log('Update response headers:', Object.fromEntries(response.headers.entries()));
// Handle 504 Gateway Timeout as successful initiation
if (response.status === 504) {
console.log('Received 504 Gateway Timeout - stack update may still be in progress');
// Update progress to show that we're now polling
const progressBar = document.getElementById('launchProgress');
const progressText = document.getElementById('stepDescription');
if (progressBar && progressText) {
progressBar.style.width = '25%';
progressBar.textContent = '25%';
progressText.textContent = 'Stack update initiated (timed out, but continuing to monitor)...';
}
// Start polling immediately since the stack update was initiated
console.log('Starting to poll for stack status after 504 timeout...');
const pollResult = await pollStackStatus(stackId, 15 * 60 * 1000); // 15 minutes max
return pollResult;
}
if (!response.ok) {
let errorMessage = 'Failed to update stack';
console.log('Response not ok, status:', response.status);
try {
const errorData = await response.json();
errorMessage = errorData.error || errorMessage;
console.log('Parsed error data:', errorData);
} catch (parseError) {
console.log('Failed to parse JSON error, trying text:', parseError);
// If JSON parsing fails, try to get text content
try {
const errorText = await response.text();
console.log('Error text content:', errorText);
if (errorText.includes('504 Gateway Time-out') || errorText.includes('504 Gateway Timeout')) {
console.log('Received 504 Gateway Timeout - stack update may still be in progress');
// Update progress to show that we're now polling
const progressBar = document.getElementById('launchProgress');
const progressText = document.getElementById('stepDescription');
if (progressBar && progressText) {
progressBar.style.width = '25%';
progressBar.textContent = '25%';
progressText.textContent = 'Stack update initiated (timed out, but continuing to monitor)...';
}
// Start polling immediately since the stack update was initiated
console.log('Starting to poll for stack status after 504 timeout...');
const pollResult = await pollStackStatus(stackId, 15 * 60 * 1000); // 15 minutes max
return pollResult;
} else {
errorMessage = `HTTP ${response.status}: ${errorText}`;
}
} catch (textError) {
console.log('Failed to get error text:', textError);
errorMessage = `HTTP ${response.status}: Failed to parse response`;
}
}
console.log('Throwing error:', errorMessage);
throw new Error(errorMessage);
}
const result = await response.json();
console.log('Stack update initiated:', result);
// If stack is being updated, poll for status
if (result.data.status === 'updating') {
console.log('Stack is being updated, polling for status...');
const pollResult = await pollStackStatus(stackId, 10 * 60 * 1000); // 10 minutes max
return pollResult;
}
// Return success result with response data
return {
success: true,
data: result.data
};
} catch (error) {
console.error('Error updating stack:', error);
// Check if this is a 504 timeout error that should be handled as a success
if (error.message && (
error.message.includes('504 Gateway Time-out') ||
error.message.includes('504 Gateway Timeout') ||
error.message.includes('timed out')
)) {
console.log('Detected 504 timeout in catch block - treating as successful initiation');
// Update progress to show that we're now polling
const progressBar = document.getElementById('launchProgress');
const progressText = document.getElementById('stepDescription');
if (progressBar && progressText) {
progressBar.style.width = '25%';
progressBar.textContent = '25%';
progressText.textContent = 'Stack update initiated (timed out, but continuing to monitor)...';
}
// Start polling immediately since the stack update was initiated
console.log('Starting to poll for stack status after 504 timeout from catch block...');
const pollResult = await pollStackStatus(stackId, 15 * 60 * 1000); // 15 minutes max
return pollResult;
}
return {
success: false,
error: error.message
};
}
}