Update start and better volume names
This commit is contained in:
@@ -4,6 +4,7 @@ let editInstanceModal;
|
||||
let addExistingInstanceModal;
|
||||
let authModal;
|
||||
let launchStepsModal;
|
||||
let updateInstanceModal;
|
||||
let currentStep = 1;
|
||||
|
||||
// Update the total number of steps
|
||||
@@ -15,6 +16,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
addExistingInstanceModal = new bootstrap.Modal(document.getElementById('addExistingInstanceModal'));
|
||||
authModal = new bootstrap.Modal(document.getElementById('authModal'));
|
||||
launchStepsModal = new bootstrap.Modal(document.getElementById('launchStepsModal'));
|
||||
updateInstanceModal = new bootstrap.Modal(document.getElementById('updateInstanceModal'));
|
||||
|
||||
// Initialize tooltips
|
||||
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
@@ -1774,4 +1776,168 @@ async function confirmDeleteInstance() {
|
||||
confirmDeleteBtn.className = 'btn btn-danger';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update Instance Functions
|
||||
function showUpdateInstanceModal(instanceId, stackName, instanceUrl) {
|
||||
document.getElementById('update_instance_id').value = instanceId;
|
||||
document.getElementById('update_stack_name').value = stackName;
|
||||
document.getElementById('update_instance_url').value = instanceUrl;
|
||||
|
||||
// Load repositories for the update modal
|
||||
loadUpdateRepositories();
|
||||
|
||||
updateInstanceModal.show();
|
||||
}
|
||||
|
||||
async function loadUpdateRepositories() {
|
||||
const repoSelect = document.getElementById('updateRepoSelect');
|
||||
const branchSelect = document.getElementById('updateBranchSelect');
|
||||
|
||||
try {
|
||||
// Reset branch select
|
||||
branchSelect.innerHTML = '<option value="">Select a repository first</option>';
|
||||
branchSelect.disabled = true;
|
||||
|
||||
const gitSettings = window.gitSettings || {};
|
||||
if (!gitSettings.url || !gitSettings.token) {
|
||||
throw new Error('No Git settings found. Please configure Git in the settings page.');
|
||||
}
|
||||
|
||||
// Load repositories using the correct existing endpoint
|
||||
const repoResponse = await fetch('/api/admin/list-gitea-repos', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url: gitSettings.url,
|
||||
token: gitSettings.token
|
||||
})
|
||||
});
|
||||
|
||||
if (!repoResponse.ok) {
|
||||
throw new Error('Failed to load repositories');
|
||||
}
|
||||
|
||||
const data = await repoResponse.json();
|
||||
|
||||
if (data.repositories && data.repositories.length > 0) {
|
||||
repoSelect.innerHTML = '<option value="">Select a repository</option>' +
|
||||
data.repositories.map(repo =>
|
||||
`<option value="${repo.full_name}" ${repo.full_name === gitSettings.repo ? 'selected' : ''}>${repo.full_name}</option>`
|
||||
).join('');
|
||||
repoSelect.disabled = false;
|
||||
|
||||
// If we have a saved repository, load its branches
|
||||
if (gitSettings.repo) {
|
||||
loadUpdateBranches(gitSettings.repo);
|
||||
}
|
||||
} else {
|
||||
repoSelect.innerHTML = '<option value="">No repositories found</option>';
|
||||
repoSelect.disabled = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading repositories for update:', error);
|
||||
repoSelect.innerHTML = `<option value="">Error: ${error.message}</option>`;
|
||||
repoSelect.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUpdateBranches(repoId) {
|
||||
const branchSelect = document.getElementById('updateBranchSelect');
|
||||
|
||||
if (!repoId) {
|
||||
branchSelect.innerHTML = '<option value="">Select a repository first</option>';
|
||||
branchSelect.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const gitSettings = window.gitSettings || {};
|
||||
if (!gitSettings.url || !gitSettings.token) {
|
||||
throw new Error('No Git settings found. Please configure Git in the settings page.');
|
||||
}
|
||||
|
||||
const response = await fetch('/api/admin/list-gitea-branches', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url: gitSettings.url,
|
||||
token: gitSettings.token,
|
||||
repo: repoId
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load branches');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.branches && data.branches.length > 0) {
|
||||
branchSelect.innerHTML = '<option value="">Select a branch</option>' +
|
||||
data.branches.map(branch =>
|
||||
`<option value="${branch.name}" ${branch.name === 'master' ? 'selected' : ''}>${branch.name}</option>`
|
||||
).join('');
|
||||
branchSelect.disabled = false;
|
||||
} else {
|
||||
branchSelect.innerHTML = '<option value="">No branches found</option>';
|
||||
branchSelect.disabled = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading branches for update:', error);
|
||||
branchSelect.innerHTML = `<option value="">Error: ${error.message}</option>`;
|
||||
branchSelect.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function startInstanceUpdate() {
|
||||
const instanceId = document.getElementById('update_instance_id').value;
|
||||
const stackName = document.getElementById('update_stack_name').value;
|
||||
const instanceUrl = document.getElementById('update_instance_url').value;
|
||||
const repoId = document.getElementById('updateRepoSelect').value;
|
||||
const branch = document.getElementById('updateBranchSelect').value;
|
||||
|
||||
if (!repoId || !branch) {
|
||||
alert('Please select both a repository and a branch.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Store update data in sessionStorage for the launch progress page
|
||||
const updateData = {
|
||||
instanceId: instanceId,
|
||||
stackName: stackName,
|
||||
instanceUrl: instanceUrl,
|
||||
repository: repoId,
|
||||
branch: branch,
|
||||
isUpdate: true
|
||||
};
|
||||
sessionStorage.setItem('instanceUpdateData', JSON.stringify(updateData));
|
||||
|
||||
// Close the modal
|
||||
updateInstanceModal.hide();
|
||||
|
||||
// Redirect to launch progress page with update parameters
|
||||
window.location.href = `/instances/launch-progress?update=true&instance_id=${instanceId}&repo=${repoId}&branch=${encodeURIComponent(branch)}`;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error starting instance update:', error);
|
||||
alert('Error starting update: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Add event listeners for update modal
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const updateRepoSelect = document.getElementById('updateRepoSelect');
|
||||
if (updateRepoSelect) {
|
||||
updateRepoSelect.addEventListener('change', function() {
|
||||
loadUpdateBranches(this.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,236 +1,280 @@
|
||||
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;
|
||||
}
|
||||
// Check if this is an update operation
|
||||
if (window.isUpdate && window.updateInstanceId && window.updateRepoId && window.updateBranch) {
|
||||
// This is an update operation
|
||||
const updateData = {
|
||||
instanceId: window.updateInstanceId,
|
||||
repository: window.updateRepoId,
|
||||
branch: window.updateBranch,
|
||||
isUpdate: true
|
||||
};
|
||||
|
||||
// Initialize the steps
|
||||
initializeSteps();
|
||||
|
||||
// Start the update process
|
||||
startUpdate(updateData);
|
||||
} else {
|
||||
// This is a new launch operation
|
||||
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);
|
||||
// Initialize the steps
|
||||
initializeSteps();
|
||||
|
||||
// Start the launch process
|
||||
startLaunch(launchData);
|
||||
}
|
||||
});
|
||||
|
||||
function initializeSteps() {
|
||||
const stepsContainer = document.getElementById('stepsContainer');
|
||||
const isUpdate = window.isUpdate;
|
||||
|
||||
// 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);
|
||||
if (isUpdate) {
|
||||
// For updates, show fewer steps
|
||||
const steps = [
|
||||
{ icon: 'fab fa-docker', title: 'Checking Portainer Connection', description: 'Verifying connection to Portainer...' },
|
||||
{ icon: 'fas fa-file-code', title: 'Downloading Docker Compose', description: 'Fetching docker-compose.yml from repository...' },
|
||||
{ icon: 'fab fa-docker', title: 'Deploying Updated Stack', description: 'Deploying the updated application stack...' },
|
||||
{ icon: 'fas fa-save', title: 'Updating Instance Data', description: 'Updating instance information...' },
|
||||
{ icon: 'fas fa-heartbeat', title: 'Health Check', description: 'Verifying updated instance health...' },
|
||||
{ icon: 'fas fa-check-circle', title: 'Update Complete', description: 'Instance has been successfully updated!' }
|
||||
];
|
||||
|
||||
steps.forEach((step, index) => {
|
||||
const stepElement = document.createElement('div');
|
||||
stepElement.className = 'step-item';
|
||||
stepElement.innerHTML = `
|
||||
<div class="step-icon"><i class="${step.icon}"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>${step.title}</h5>
|
||||
<p class="step-status">${step.description}</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(stepElement);
|
||||
});
|
||||
} else {
|
||||
// For new launches, show all steps
|
||||
// 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';
|
||||
dnsStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-globe"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Checking DNS Records</h5>
|
||||
<p class="step-status">Verifying domain configurations...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(dnsStep);
|
||||
// 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';
|
||||
dnsStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-globe"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Checking DNS Records</h5>
|
||||
<p class="step-status">Verifying domain configurations...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(dnsStep);
|
||||
|
||||
// Add NGINX connection check step
|
||||
const nginxStep = document.createElement('div');
|
||||
nginxStep.className = 'step-item';
|
||||
nginxStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-network-wired"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Checking NGINX Connection</h5>
|
||||
<p class="step-status">Verifying connection to NGINX Proxy Manager...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(nginxStep);
|
||||
// Add NGINX connection check step
|
||||
const nginxStep = document.createElement('div');
|
||||
nginxStep.className = 'step-item';
|
||||
nginxStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-network-wired"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Checking NGINX Connection</h5>
|
||||
<p class="step-status">Verifying connection to NGINX Proxy Manager...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(nginxStep);
|
||||
|
||||
// Add SSL Certificate generation step
|
||||
const sslStep = document.createElement('div');
|
||||
sslStep.className = 'step-item';
|
||||
sslStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-lock"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Generating SSL Certificate</h5>
|
||||
<p class="step-status">Setting up secure HTTPS connection...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(sslStep);
|
||||
// Add SSL Certificate generation step
|
||||
const sslStep = document.createElement('div');
|
||||
sslStep.className = 'step-item';
|
||||
sslStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-lock"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Generating SSL Certificate</h5>
|
||||
<p class="step-status">Setting up secure HTTPS connection...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(sslStep);
|
||||
|
||||
// Add Proxy Host creation step
|
||||
const proxyStep = document.createElement('div');
|
||||
proxyStep.className = 'step-item';
|
||||
proxyStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-server"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Creating Proxy Host</h5>
|
||||
<p class="step-status">Setting up NGINX proxy host configuration...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(proxyStep);
|
||||
// Add Proxy Host creation step
|
||||
const proxyStep = document.createElement('div');
|
||||
proxyStep.className = 'step-item';
|
||||
proxyStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-server"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Creating Proxy Host</h5>
|
||||
<p class="step-status">Setting up NGINX proxy host configuration...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(proxyStep);
|
||||
|
||||
// Add Portainer connection check step
|
||||
const portainerStep = document.createElement('div');
|
||||
portainerStep.className = 'step-item';
|
||||
portainerStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fab fa-docker"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Checking Portainer Connection</h5>
|
||||
<p class="step-status">Verifying connection to Portainer...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(portainerStep);
|
||||
// Add Portainer connection check step
|
||||
const portainerStep = document.createElement('div');
|
||||
portainerStep.className = 'step-item';
|
||||
portainerStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fab fa-docker"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Checking Portainer Connection</h5>
|
||||
<p class="step-status">Verifying connection to Portainer...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(portainerStep);
|
||||
|
||||
// Add Docker Compose download step
|
||||
const dockerComposeStep = document.createElement('div');
|
||||
dockerComposeStep.className = 'step-item';
|
||||
dockerComposeStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-file-code"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Downloading Docker Compose</h5>
|
||||
<p class="step-status">Fetching docker-compose.yml from repository...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(dockerComposeStep);
|
||||
// Add Docker Compose download step
|
||||
const dockerComposeStep = document.createElement('div');
|
||||
dockerComposeStep.className = 'step-item';
|
||||
dockerComposeStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-file-code"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Downloading Docker Compose</h5>
|
||||
<p class="step-status">Fetching docker-compose.yml from repository...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(dockerComposeStep);
|
||||
|
||||
// Add Portainer stack deployment step
|
||||
const stackDeployStep = document.createElement('div');
|
||||
stackDeployStep.className = 'step-item';
|
||||
stackDeployStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fab fa-docker"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Deploying Stack</h5>
|
||||
<p class="step-status">Launching your application stack...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(stackDeployStep);
|
||||
// Add Portainer stack deployment step
|
||||
const stackDeployStep = document.createElement('div');
|
||||
stackDeployStep.className = 'step-item';
|
||||
stackDeployStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fab fa-docker"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Deploying Stack</h5>
|
||||
<p class="step-status">Launching your application stack...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(stackDeployStep);
|
||||
|
||||
// Add Save Instance Data step
|
||||
const saveDataStep = document.createElement('div');
|
||||
saveDataStep.className = 'step-item';
|
||||
saveDataStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-save"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Saving Instance Data</h5>
|
||||
<p class="step-status">Storing instance information...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(saveDataStep);
|
||||
// Add Save Instance Data step
|
||||
const saveDataStep = document.createElement('div');
|
||||
saveDataStep.className = 'step-item';
|
||||
saveDataStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-save"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Saving Instance Data</h5>
|
||||
<p class="step-status">Storing instance information...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(saveDataStep);
|
||||
|
||||
// Add Health Check step
|
||||
const healthStep = document.createElement('div');
|
||||
healthStep.className = 'step-item';
|
||||
healthStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-heartbeat"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Health Check</h5>
|
||||
<p class="step-status">Verifying instance health...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(healthStep);
|
||||
// Add Health Check step
|
||||
const healthStep = document.createElement('div');
|
||||
healthStep.className = 'step-item';
|
||||
healthStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-heartbeat"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Health Check</h5>
|
||||
<p class="step-status">Verifying instance health...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(healthStep);
|
||||
|
||||
// Add Authentication step
|
||||
const authStep = document.createElement('div');
|
||||
authStep.className = 'step-item';
|
||||
authStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-key"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Instance Authentication</h5>
|
||||
<p class="step-status">Setting up instance authentication...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(authStep);
|
||||
// Add Authentication step
|
||||
const authStep = document.createElement('div');
|
||||
authStep.className = 'step-item';
|
||||
authStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-key"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Instance Authentication</h5>
|
||||
<p class="step-status">Setting up instance authentication...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(authStep);
|
||||
|
||||
// Add Apply Company Information step
|
||||
const companyStep = document.createElement('div');
|
||||
companyStep.className = 'step-item';
|
||||
companyStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-building"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Apply Company Information</h5>
|
||||
<p class="step-status">Configuring company details...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(companyStep);
|
||||
// Add Apply Company Information step
|
||||
const companyStep = document.createElement('div');
|
||||
companyStep.className = 'step-item';
|
||||
companyStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-building"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Apply Company Information</h5>
|
||||
<p class="step-status">Configuring company details...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(companyStep);
|
||||
|
||||
// Add Apply Colors step
|
||||
const colorsStep = document.createElement('div');
|
||||
colorsStep.className = 'step-item';
|
||||
colorsStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-palette"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Apply Colors</h5>
|
||||
<p class="step-status">Configuring color scheme...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(colorsStep);
|
||||
// Add Apply Colors step
|
||||
const colorsStep = document.createElement('div');
|
||||
colorsStep.className = 'step-item';
|
||||
colorsStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-palette"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Apply Colors</h5>
|
||||
<p class="step-status">Configuring color scheme...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(colorsStep);
|
||||
|
||||
// Add Update Admin Credentials step
|
||||
const credentialsStep = document.createElement('div');
|
||||
credentialsStep.className = 'step-item';
|
||||
credentialsStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-user-shield"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Update Admin Credentials</h5>
|
||||
<p class="step-status">Setting up admin account...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(credentialsStep);
|
||||
// Add Update Admin Credentials step
|
||||
const credentialsStep = document.createElement('div');
|
||||
credentialsStep.className = 'step-item';
|
||||
credentialsStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-user-shield"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Update Admin Credentials</h5>
|
||||
<p class="step-status">Setting up admin account...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(credentialsStep);
|
||||
|
||||
// Add Copy SMTP Settings step
|
||||
const smtpStep = document.createElement('div');
|
||||
smtpStep.className = 'step-item';
|
||||
smtpStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-envelope-open"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Copy SMTP Settings</h5>
|
||||
<p class="step-status">Configuring email settings...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(smtpStep);
|
||||
// Add Copy SMTP Settings step
|
||||
const smtpStep = document.createElement('div');
|
||||
smtpStep.className = 'step-item';
|
||||
smtpStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-envelope"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Copy SMTP Settings</h5>
|
||||
<p class="step-status">Configuring email settings...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(smtpStep);
|
||||
|
||||
// Add Send Completion Email step
|
||||
const emailStep = document.createElement('div');
|
||||
emailStep.className = 'step-item';
|
||||
emailStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-envelope"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Send Completion Email</h5>
|
||||
<p class="step-status">Sending notification to client...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(emailStep);
|
||||
// Add Send Completion Email step
|
||||
const emailStep = document.createElement('div');
|
||||
emailStep.className = 'step-item';
|
||||
emailStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-paper-plane"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Send Completion Email</h5>
|
||||
<p class="step-status">Sending completion notification...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(emailStep);
|
||||
|
||||
// Add Download Launch Report step
|
||||
const reportStep = document.createElement('div');
|
||||
reportStep.className = 'step-item';
|
||||
reportStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-file-download"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Download Launch Report</h5>
|
||||
<p class="step-status">Preparing launch report...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(reportStep);
|
||||
// Add Download Report step
|
||||
const reportStep = document.createElement('div');
|
||||
reportStep.className = 'step-item';
|
||||
reportStep.innerHTML = `
|
||||
<div class="step-icon"><i class="fas fa-download"></i></div>
|
||||
<div class="step-content">
|
||||
<h5>Download Launch Report</h5>
|
||||
<p class="step-status">Preparing launch report...</p>
|
||||
</div>
|
||||
`;
|
||||
stepsContainer.appendChild(reportStep);
|
||||
}
|
||||
}
|
||||
|
||||
async function startLaunch(data) {
|
||||
@@ -467,7 +511,28 @@ async function startLaunch(data) {
|
||||
downloadButton.className = 'btn btn-sm btn-primary mt-2';
|
||||
downloadButton.innerHTML = '<i class="fas fa-download me-1"></i> Download docker-compose.yml';
|
||||
downloadButton.onclick = () => {
|
||||
const blob = new Blob([dockerComposeResult.content], { type: 'text/yaml' });
|
||||
// Generate the modified docker-compose content with updated volume names
|
||||
let modifiedContent = dockerComposeResult.content;
|
||||
const stackName = generateStackName(data.port);
|
||||
|
||||
// Extract timestamp from stack name (format: docupulse_PORT_TIMESTAMP)
|
||||
const stackNameParts = stackName.split('_');
|
||||
if (stackNameParts.length >= 3) {
|
||||
const timestamp = stackNameParts.slice(2).join('_'); // Get everything after port
|
||||
const baseName = `docupulse_${data.port}_${timestamp}`;
|
||||
|
||||
// Replace volume names to match stack naming convention
|
||||
modifiedContent = modifiedContent.replace(
|
||||
/name: docupulse_\$\{PORT:-10335\}_postgres_data/g,
|
||||
`name: ${baseName}_postgres_data`
|
||||
);
|
||||
modifiedContent = modifiedContent.replace(
|
||||
/name: docupulse_\$\{PORT:-10335\}_uploads/g,
|
||||
`name: ${baseName}_uploads`
|
||||
);
|
||||
}
|
||||
|
||||
const blob = new Blob([modifiedContent], { type: 'text/yaml' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
@@ -554,6 +619,19 @@ async function startLaunch(data) {
|
||||
// Add stack details
|
||||
const stackDetails = document.createElement('div');
|
||||
stackDetails.className = 'mt-3';
|
||||
|
||||
// Calculate the volume names based on the stack name
|
||||
const stackNameParts = stackResult.data.name.split('_');
|
||||
let volumeNames = [];
|
||||
if (stackNameParts.length >= 3) {
|
||||
const timestamp = stackNameParts.slice(2).join('_');
|
||||
const baseName = `docupulse_${data.port}_${timestamp}`;
|
||||
volumeNames = [
|
||||
`${baseName}_postgres_data`,
|
||||
`${baseName}_uploads`
|
||||
];
|
||||
}
|
||||
|
||||
stackDetails.innerHTML = `
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
@@ -583,9 +661,25 @@ async function startLaunch(data) {
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Volume Names</td>
|
||||
<td>
|
||||
<div class="small">
|
||||
${volumeNames.length > 0 ? volumeNames.map(name =>
|
||||
`<code class="text-primary">${name}</code>`
|
||||
).join('<br>') : 'Using default volume names'}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
${volumeNames.length > 0 ? `
|
||||
<div class="alert alert-info mt-3">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<strong>Volume Naming Convention:</strong> Volumes have been named using the same timestamp as the stack for easy identification and management.
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1249,6 +1343,182 @@ Thank you for choosing DocuPulse!
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle instance updates
|
||||
async function startUpdate(data) {
|
||||
console.log('Starting instance update:', data);
|
||||
|
||||
try {
|
||||
// Update the header to reflect this is an update
|
||||
const headerTitle = document.querySelector('.header h1');
|
||||
const headerDescription = document.querySelector('.header p');
|
||||
if (headerTitle) headerTitle.textContent = 'Updating Instance';
|
||||
if (headerDescription) headerDescription.textContent = 'Updating your DocuPulse instance with the latest version';
|
||||
|
||||
// Initialize launch report for update
|
||||
const launchReport = {
|
||||
type: 'update',
|
||||
timestamp: new Date().toISOString(),
|
||||
instanceId: data.instanceId,
|
||||
repository: data.repository,
|
||||
branch: data.branch,
|
||||
steps: []
|
||||
};
|
||||
|
||||
// Step 1: Check Portainer Connection
|
||||
await updateStep(1, 'Checking Portainer Connection', 'Verifying connection to Portainer...');
|
||||
const portainerResult = await checkPortainerConnection();
|
||||
if (!portainerResult.success) {
|
||||
throw new Error(`Portainer connection failed: ${portainerResult.error}`);
|
||||
}
|
||||
launchReport.steps.push({
|
||||
step: 'Portainer Connection',
|
||||
status: 'success',
|
||||
details: portainerResult
|
||||
});
|
||||
|
||||
// Step 2: Download Docker Compose
|
||||
await updateStep(2, 'Downloading Docker Compose', 'Fetching docker-compose.yml from repository...');
|
||||
const dockerComposeResult = await downloadDockerCompose(data.repository, data.branch);
|
||||
if (!dockerComposeResult.success) {
|
||||
throw new Error(`Failed to download Docker Compose: ${dockerComposeResult.error}`);
|
||||
}
|
||||
launchReport.steps.push({
|
||||
step: 'Docker Compose Download',
|
||||
status: 'success',
|
||||
details: dockerComposeResult
|
||||
});
|
||||
|
||||
// Step 3: Deploy Updated Stack
|
||||
await updateStep(3, 'Deploying Updated Stack', 'Deploying the updated application stack...');
|
||||
|
||||
// Get the existing instance information to extract port
|
||||
const instanceResponse = await fetch(`/api/instances/${data.instanceId}`);
|
||||
if (!instanceResponse.ok) {
|
||||
throw new Error('Failed to get instance information');
|
||||
}
|
||||
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);
|
||||
|
||||
const stackResult = await deployStack(dockerComposeResult.content, newStackName, port);
|
||||
if (!stackResult.success) {
|
||||
throw new Error(`Failed to deploy updated stack: ${stackResult.error}`);
|
||||
}
|
||||
launchReport.steps.push({
|
||||
step: 'Stack Deployment',
|
||||
status: 'success',
|
||||
details: stackResult
|
||||
});
|
||||
|
||||
// Step 4: Update Instance Data
|
||||
await updateStep(4, 'Updating Instance Data', 'Updating instance information...');
|
||||
const updateData = {
|
||||
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,
|
||||
status: stackResult.data.status,
|
||||
repository: data.repository,
|
||||
branch: data.branch,
|
||||
deployed_version: dockerComposeResult.latest_tag || dockerComposeResult.commit_hash || 'unknown',
|
||||
deployed_branch: data.branch,
|
||||
payment_plan: instanceData.instance.payment_plan || 'Basic'
|
||||
};
|
||||
|
||||
const saveResult = await saveInstanceData(updateData);
|
||||
if (!saveResult.success) {
|
||||
throw new Error(`Failed to update instance data: ${saveResult.error}`);
|
||||
}
|
||||
launchReport.steps.push({
|
||||
step: 'Instance Data Update',
|
||||
status: 'success',
|
||||
details: saveResult
|
||||
});
|
||||
|
||||
// Step 5: Health Check
|
||||
await updateStep(5, 'Health Check', 'Verifying updated instance health...');
|
||||
const healthResult = await checkInstanceHealth(instanceData.instance.main_url);
|
||||
if (!healthResult.success) {
|
||||
throw new Error(`Health check failed: ${healthResult.error}`);
|
||||
}
|
||||
launchReport.steps.push({
|
||||
step: 'Health Check',
|
||||
status: 'success',
|
||||
details: healthResult
|
||||
});
|
||||
|
||||
// Update completed successfully
|
||||
await updateStep(6, 'Update Complete', 'Instance has been successfully updated!');
|
||||
|
||||
// Show success message
|
||||
const successStep = document.querySelectorAll('.step-item')[5];
|
||||
successStep.classList.remove('active');
|
||||
successStep.classList.add('completed');
|
||||
successStep.querySelector('.step-status').textContent = 'Instance updated successfully!';
|
||||
|
||||
// Add success details
|
||||
const successDetails = document.createElement('div');
|
||||
successDetails.className = 'mt-3';
|
||||
|
||||
// Calculate the volume names based on the stack name
|
||||
const stackNameParts = newStackName.split('_');
|
||||
let volumeNames = [];
|
||||
if (stackNameParts.length >= 3) {
|
||||
const timestamp = stackNameParts.slice(2).join('_');
|
||||
const baseName = `docupulse_${port}_${timestamp}`;
|
||||
volumeNames = [
|
||||
`${baseName}_postgres_data`,
|
||||
`${baseName}_uploads`
|
||||
];
|
||||
}
|
||||
|
||||
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>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<strong>Repository:</strong> ${data.repository}<br>
|
||||
<strong>Branch:</strong> ${data.branch}<br>
|
||||
<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>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>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
successStep.querySelector('.step-content').appendChild(successDetails);
|
||||
|
||||
// Add button to return to instances page
|
||||
const returnButton = document.createElement('button');
|
||||
returnButton.className = 'btn btn-primary mt-3';
|
||||
returnButton.innerHTML = '<i class="fas fa-arrow-left me-2"></i>Return to Instances';
|
||||
returnButton.onclick = () => window.location.href = '/instances';
|
||||
successStep.querySelector('.step-content').appendChild(returnButton);
|
||||
|
||||
// Store the update report
|
||||
sessionStorage.setItem('instanceUpdateReport', JSON.stringify(launchReport));
|
||||
|
||||
} catch (error) {
|
||||
console.error('Update failed:', error);
|
||||
await updateStep(6, 'Update Failed', `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
|
||||
@@ -2645,6 +2915,28 @@ async function deployStack(dockerComposeContent, stackName, port) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update volume names in docker-compose content to match stack naming convention
|
||||
let modifiedDockerComposeContent = dockerComposeContent;
|
||||
|
||||
// Extract timestamp from stack name (format: docupulse_PORT_TIMESTAMP)
|
||||
const stackNameParts = stackName.split('_');
|
||||
if (stackNameParts.length >= 3) {
|
||||
const timestamp = stackNameParts.slice(2).join('_'); // Get everything after port
|
||||
const baseName = `docupulse_${port}_${timestamp}`;
|
||||
|
||||
// Replace volume names to match stack naming convention
|
||||
modifiedDockerComposeContent = modifiedDockerComposeContent.replace(
|
||||
/name: docupulse_\$\{PORT:-10335\}_postgres_data/g,
|
||||
`name: ${baseName}_postgres_data`
|
||||
);
|
||||
modifiedDockerComposeContent = modifiedDockerComposeContent.replace(
|
||||
/name: docupulse_\$\{PORT:-10335\}_uploads/g,
|
||||
`name: ${baseName}_uploads`
|
||||
);
|
||||
|
||||
console.log(`Updated volume names to match stack naming convention: ${baseName}`);
|
||||
}
|
||||
|
||||
// First, attempt to deploy the stack
|
||||
const response = await fetch('/api/admin/deploy-stack', {
|
||||
method: 'POST',
|
||||
@@ -2654,7 +2946,7 @@ async function deployStack(dockerComposeContent, stackName, port) {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: stackName,
|
||||
StackFileContent: dockerComposeContent,
|
||||
StackFileContent: modifiedDockerComposeContent,
|
||||
Env: [
|
||||
{
|
||||
name: 'PORT',
|
||||
@@ -2718,8 +3010,8 @@ async function deployStack(dockerComposeContent, stackName, port) {
|
||||
console.log('Received 504 Gateway Timeout - stack creation may still be in progress');
|
||||
|
||||
// Update progress to show that we're now polling
|
||||
const progressBar = document.getElementById('stackProgress');
|
||||
const progressText = document.getElementById('stackProgressText');
|
||||
const progressBar = document.getElementById('launchProgress');
|
||||
const progressText = document.getElementById('stepDescription');
|
||||
if (progressBar && progressText) {
|
||||
progressBar.style.width = '25%';
|
||||
progressBar.textContent = '25%';
|
||||
@@ -2733,8 +3025,38 @@ async function deployStack(dockerComposeContent, stackName, port) {
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Failed to deploy stack');
|
||||
let errorMessage = 'Failed to deploy stack';
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
errorMessage = errorData.error || errorMessage;
|
||||
} catch (parseError) {
|
||||
// If JSON parsing fails, try to get text content
|
||||
try {
|
||||
const errorText = await response.text();
|
||||
if (errorText.includes('504 Gateway Time-out') || errorText.includes('504 Gateway Timeout')) {
|
||||
console.log('Received 504 Gateway Timeout - stack creation 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 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(stackName, 15 * 60 * 1000); // 15 minutes max
|
||||
return pollResult;
|
||||
} else {
|
||||
errorMessage = `HTTP ${response.status}: ${errorText}`;
|
||||
}
|
||||
} catch (textError) {
|
||||
errorMessage = `HTTP ${response.status}: Failed to parse response`;
|
||||
}
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
@@ -2785,8 +3107,8 @@ async function pollStackStatus(stackName, maxWaitTime = 15 * 60 * 1000) {
|
||||
console.log(`Starting to poll stack status for: ${stackName} (max wait: ${maxWaitTime / 1000}s)`);
|
||||
|
||||
// Update progress indicator
|
||||
const progressBar = document.getElementById('stackProgress');
|
||||
const progressText = document.getElementById('stackProgressText');
|
||||
const progressBar = document.getElementById('launchProgress');
|
||||
const progressText = document.getElementById('stepDescription');
|
||||
|
||||
while (Date.now() - startTime < maxWaitTime) {
|
||||
attempts++;
|
||||
|
||||
Reference in New Issue
Block a user