first 4 steps of launch

This commit is contained in:
2025-06-14 18:18:43 +02:00
parent 68940e87f9
commit 6b87fd6fc1
5 changed files with 905 additions and 146 deletions

View File

@@ -30,54 +30,8 @@
style="width: 0%">0%</div>
</div>
<div class="launch-steps">
<div class="step-item" data-step="1">
<div class="step-icon"><i class="fas fa-code-branch"></i></div>
<div class="step-content">
<h5>Cloning Repository</h5>
<p class="step-status">Waiting...</p>
</div>
</div>
<div class="step-item" data-step="2">
<div class="step-icon"><i class="fas fa-server"></i></div>
<div class="step-content">
<h5>Creating Container</h5>
<p class="step-status">Waiting...</p>
</div>
</div>
<div class="step-item" data-step="3">
<div class="step-icon"><i class="fas fa-network-wired"></i></div>
<div class="step-content">
<h5>Configuring Network</h5>
<p class="step-status">Waiting...</p>
</div>
</div>
<div class="step-item" data-step="4">
<div class="step-icon"><i class="fas fa-database"></i></div>
<div class="step-content">
<h5>Setting Up Database</h5>
<p class="step-status">Waiting...</p>
</div>
</div>
<div class="step-item" data-step="5">
<div class="step-icon"><i class="fas fa-paint-brush"></i></div>
<div class="step-content">
<h5>Applying Customization</h5>
<p class="step-status">Waiting...</p>
</div>
</div>
<div class="step-item" data-step="6">
<div class="step-icon"><i class="fas fa-check-circle"></i></div>
<div class="step-content">
<h5>Finalizing Setup</h5>
<p class="step-status">Waiting...</p>
</div>
</div>
<div id="stepsContainer">
<!-- Your custom steps will be added here -->
</div>
<div class="text-center mt-4">
@@ -96,10 +50,6 @@
</div>
<style>
.launch-steps {
margin-top: 2rem;
}
.step-item {
display: flex;
align-items: flex-start;
@@ -184,49 +134,648 @@ document.addEventListener('DOMContentLoaded', function() {
return;
}
// Initialize the steps
initializeSteps();
// Start the launch process
startLaunch(launchData);
});
function initializeSteps() {
const stepsContainer = document.getElementById('stepsContainer');
// 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 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 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);
}
async function startLaunch(data) {
try {
// Step 1: Clone Repository
await updateStep(1, 'Cloning Repository', 'Fetching code from repository...');
const cloneResult = await cloneRepository(data.repository, data.branch);
if (!cloneResult.success) throw new Error(cloneResult.error);
// Step 1: Check DNS records
await updateStep(1, 'Checking DNS Records', 'Verifying domain configurations...');
const dnsResult = await checkDNSRecords(data.webAddresses);
// Check if any domains failed to resolve
const failedDomains = Object.entries(dnsResult.results)
.filter(([_, result]) => !result.resolved)
.map(([domain]) => domain);
if (failedDomains.length > 0) {
throw new Error(`DNS records not found for: ${failedDomains.join(', ')}`);
}
// Step 2: Create Container
await updateStep(2, 'Creating Container', 'Setting up Docker container...');
const containerResult = await createContainer(data);
if (!containerResult.success) throw new Error(containerResult.error);
// Update the step to show success
const dnsStep = document.querySelector('.step-item');
dnsStep.classList.remove('active');
dnsStep.classList.add('completed');
// Create a details section for DNS results
const detailsSection = document.createElement('div');
detailsSection.className = 'mt-3';
detailsSection.innerHTML = `
<div class="card">
<div class="card-body">
<h6 class="card-title mb-3">DNS Check Results</h6>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Domain</th>
<th>Status</th>
<th>IP Address</th>
<th>TTL</th>
</tr>
</thead>
<tbody>
${Object.entries(dnsResult.results).map(([domain, result]) => `
<tr>
<td>${domain}</td>
<td>
<span class="badge bg-${result.resolved ? 'success' : 'danger'}">
${result.resolved ? 'Resolved' : 'Not Found'}
</span>
</td>
<td>${result.ip || 'N/A'}</td>
<td>${result.ttl || 'N/A'}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
</div>
`;
// Add the details section after the status text
const statusText = dnsStep.querySelector('.step-status');
statusText.textContent = 'DNS records verified successfully';
statusText.after(detailsSection);
// Step 3: Configure Network
await updateStep(3, 'Configuring Network', 'Setting up network and ports...');
const networkResult = await configureNetwork(data);
if (!networkResult.success) throw new Error(networkResult.error);
// Step 2: Check NGINX connection
await updateStep(2, 'Checking NGINX Connection', 'Verifying connection to NGINX Proxy Manager...');
const nginxResult = await checkNginxConnection();
if (!nginxResult.success) {
throw new Error(nginxResult.error || 'Failed to connect to NGINX Proxy Manager');
}
// Step 4: Setup Database
await updateStep(4, 'Setting Up Database', 'Initializing database...');
const dbResult = await setupDatabase(data);
if (!dbResult.success) throw new Error(dbResult.error);
// Update the step to show success
const nginxStep = document.querySelectorAll('.step-item')[1];
nginxStep.classList.remove('active');
nginxStep.classList.add('completed');
nginxStep.querySelector('.step-status').textContent = 'Successfully connected to NGINX Proxy Manager';
// Step 5: Apply Customization
await updateStep(5, 'Applying Customization', 'Applying your custom settings...');
const customResult = await applyCustomization(data);
if (!customResult.success) throw new Error(customResult.error);
// Step 3: Create Proxy Host
await updateStep(3, 'Creating Proxy Host', 'Setting up NGINX proxy host configuration...');
const proxyResult = await createProxyHost(data.webAddresses, data.port);
if (!proxyResult.success) {
throw new Error(proxyResult.error || 'Failed to create proxy host');
}
// Step 6: Finalize
await updateStep(6, 'Finalizing Setup', 'Completing the setup...');
const finalResult = await finalizeSetup(data);
if (!finalResult.success) throw new Error(finalResult.error);
// Step 4: Generate SSL Certificate
await updateStep(4, 'Generating SSL Certificate', 'Setting up secure HTTPS connection...');
const sslResult = await generateSSLCertificate(data.webAddresses, proxyResult.data.id);
if (!sslResult.success) {
throw new Error(sslResult.error || 'Failed to generate SSL certificate');
}
// Launch complete
completeLaunch();
} catch (error) {
showError(error.message);
}
}
async function checkDNSRecords(domains) {
try {
const response = await fetch('/api/check-dns', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ domains })
});
if (!response.ok) {
throw new Error('Failed to check DNS records');
}
const result = await response.json();
console.log('DNS check result:', result); // Add logging to debug
return result;
} catch (error) {
console.error('Error checking DNS records:', error);
throw error;
}
}
async function checkNginxConnection() {
try {
// Get NGINX settings from the template
const nginxSettings = {
url: '{{ nginx_settings.url if nginx_settings else "" }}',
username: '{{ nginx_settings.username if nginx_settings else "" }}',
password: '{{ nginx_settings.password if nginx_settings else "" }}'
};
// Debug log the settings (without password)
console.log('NGINX Settings:', {
url: nginxSettings.url,
username: nginxSettings.username,
hasPassword: !!nginxSettings.password
});
// Check if any required field is missing
if (!nginxSettings.url || !nginxSettings.username || !nginxSettings.password) {
return {
success: false,
error: 'NGINX settings are not configured. Please configure NGINX settings in the admin panel.'
};
}
const response = await fetch('/api/admin/test-nginx-connection', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify(nginxSettings)
});
if (!response.ok) {
const error = await response.json();
console.error('NGINX connection error:', error);
return {
success: false,
error: error.error || 'Failed to connect to NGINX Proxy Manager'
};
}
return { success: true };
} catch (error) {
console.error('Error checking NGINX connection:', error);
return {
success: false,
error: error.message || 'Error checking NGINX connection'
};
}
}
function updateStatus(step, message, type = 'info', details = '') {
const statusElement = document.getElementById(`${step}Status`);
const detailsElement = document.getElementById(`${step}Details`);
if (statusElement) {
// Remove any existing status classes
statusElement.classList.remove('text-info', 'text-success', 'text-danger');
// Add appropriate class based on type
switch (type) {
case 'success':
statusElement.classList.add('text-success');
break;
case 'error':
statusElement.classList.add('text-danger');
break;
default:
statusElement.classList.add('text-info');
}
statusElement.textContent = message;
}
if (detailsElement) {
detailsElement.innerHTML = details;
}
}
async function createProxyHost(domains, port) {
try {
// Get NGINX settings from the template
const nginxSettings = {
url: '{{ nginx_settings.url if nginx_settings else "" }}',
username: '{{ nginx_settings.username if nginx_settings else "" }}',
password: '{{ nginx_settings.password if nginx_settings else "" }}'
};
console.log('NGINX Settings:', { ...nginxSettings, password: '***' });
// Update status to show we're getting the token
updateStatus('proxy', 'Getting authentication token...', 'info');
// First, get the JWT token from NGINX
const tokenResponse = await fetch(`${nginxSettings.url}/api/tokens`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
identity: nginxSettings.username,
secret: nginxSettings.password
})
});
console.log('Token Response Status:', tokenResponse.status);
console.log('Token Response Headers:', Object.fromEntries(tokenResponse.headers.entries()));
if (!tokenResponse.ok) {
const errorText = await tokenResponse.text();
console.error('Token Error Response:', errorText);
try {
const errorJson = JSON.parse(errorText);
throw new Error(`Failed to authenticate with NGINX: ${errorJson.message || errorText}`);
} catch (e) {
throw new Error(`Failed to authenticate with NGINX: ${errorText}`);
}
}
const tokenData = await tokenResponse.json();
console.log('Token Data:', { ...tokenData, token: tokenData.token ? '***' : null });
const token = tokenData.token;
if (!token) {
throw new Error('No token received from NGINX Proxy Manager');
}
// Store the token in sessionStorage for later use
sessionStorage.setItem('nginxToken', token);
// Check if a proxy host already exists with the same properties
const proxyHostsResponse = await fetch(`${nginxSettings.url}/api/nginx/proxy-hosts`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (!proxyHostsResponse.ok) {
throw new Error('Failed to fetch existing proxy hosts');
}
const proxyHosts = await proxyHostsResponse.json();
const existingProxy = proxyHosts.find(ph => {
const sameDomains = Array.isArray(ph.domain_names) &&
ph.domain_names.length === domains.length &&
domains.every(d => ph.domain_names.includes(d));
return (
sameDomains &&
ph.forward_scheme === 'http' &&
ph.forward_host === '192.168.68.124' &&
parseInt(ph.forward_port) === parseInt(port)
);
});
let result;
if (existingProxy) {
console.log('Found existing proxy host:', existingProxy);
result = existingProxy;
} else {
// Update status to show we're creating the proxy host
updateStatus('proxy', 'Creating proxy host configuration...', 'info');
const proxyHostData = {
domain_names: domains,
forward_scheme: 'http',
forward_host: '192.168.68.124',
forward_port: parseInt(port),
ssl_forced: true,
caching_enabled: true,
block_exploits: true,
allow_websocket_upgrade: true,
http2_support: true,
hsts_enabled: true,
hsts_subdomains: true,
meta: {
letsencrypt_agree: true,
dns_challenge: false
}
};
console.log('Creating proxy host with data:', proxyHostData);
// Create the proxy host with NGINX
const response = await fetch(`${nginxSettings.url}/api/nginx/proxy-hosts`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(proxyHostData)
});
console.log('Proxy Host Response Status:', response.status);
console.log('Proxy Host Response Headers:', Object.fromEntries(response.headers.entries()));
if (!response.ok) {
const errorText = await response.text();
console.error('Proxy Host Error Response:', errorText);
try {
const errorJson = JSON.parse(errorText);
const errorMessage = errorJson.error?.message || errorText;
// Check if the error is about a domain already being in use
if (errorMessage.includes('is already in use')) {
const domain = errorMessage.split(' ')[0];
throw new Error(`Domain ${domain} is already configured in NGINX Proxy Manager. Please remove it from NGINX Proxy Manager and try again.`);
}
throw new Error(`Failed to create proxy host: ${errorMessage}`);
} catch (e) {
if (e.message.includes('is already configured in NGINX Proxy Manager')) {
throw e; // Re-throw the domain in use error
}
throw new Error(`Failed to create proxy host: ${errorText}`);
}
}
result = await response.json();
console.log('Proxy Host Creation Result:', result);
}
// Create a detailed success message with NGINX Proxy results
const successDetails = `
<div class="mt-3">
<div class="card">
<div class="card-body">
<h6 class="card-title mb-3">NGINX Proxy Results</h6>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Property</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Proxy Host ID</td>
<td>${result.id || 'N/A'}</td>
</tr>
<tr>
<td>Domains</td>
<td>${domains.join(', ')}</td>
</tr>
<tr>
<td>Forward Scheme</td>
<td>http</td>
</tr>
<tr>
<td>Forward Host</td>
<td>192.168.68.124</td>
</tr>
<tr>
<td>Forward Port</td>
<td>${parseInt(port)}</td>
</tr>
<tr>
<td>SSL Status</td>
<td>
<span class="badge bg-success">Forced</span>
</td>
</tr>
<tr>
<td>Security Features</td>
<td>
<span class="badge bg-success me-1">Block Exploits</span>
<span class="badge bg-success me-1">HSTS</span>
<span class="badge bg-success">HTTP/2</span>
</td>
</tr>
<tr>
<td>Performance</td>
<td>
<span class="badge bg-success me-1">Caching</span>
<span class="badge bg-success">WebSocket</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
`;
// Update the proxy step to show success and add the results
const proxyStep = document.querySelectorAll('.step-item')[2];
proxyStep.classList.remove('active');
proxyStep.classList.add('completed');
const statusText = proxyStep.querySelector('.step-status');
statusText.textContent = existingProxy ? 'Using existing proxy host' : 'Successfully created proxy host';
statusText.after(document.createRange().createContextualFragment(successDetails));
return {
success: true,
data: result
};
} catch (error) {
console.error('Error creating proxy host:', error);
// Update status with error message
updateStatus('proxy', `Failed: ${error.message}`, 'error');
return {
success: false,
error: error.message
};
}
}
async function generateSSLCertificate(domains, proxyHostId) {
try {
// Get NGINX settings from the template
const nginxSettings = {
url: '{{ nginx_settings.url if nginx_settings else "" }}',
username: '{{ nginx_settings.username if nginx_settings else "" }}',
password: '{{ nginx_settings.password if nginx_settings else "" }}'
};
// Get a fresh token from NGINX
const tokenResponse = await fetch(`${nginxSettings.url}/api/tokens`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
identity: nginxSettings.username,
secret: nginxSettings.password
})
});
if (!tokenResponse.ok) {
const errorText = await tokenResponse.text();
console.error('Token Error Response:', errorText);
throw new Error(`Failed to authenticate with NGINX: ${errorText}`);
}
const tokenData = await tokenResponse.json();
const token = tokenData.token;
if (!token) {
throw new Error('No token received from NGINX Proxy Manager');
}
// First, check if a certificate already exists for these domains
const checkResponse = await fetch(`${nginxSettings.url}/api/nginx/certificates`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (!checkResponse.ok) {
throw new Error('Failed to check existing certificates');
}
const existingCertificates = await checkResponse.json();
const existingCertificate = existingCertificates.find(cert => {
const certDomains = cert.domain_names || [];
return domains.every(domain => certDomains.includes(domain)) &&
certDomains.length === domains.length;
});
let result;
let usedExisting = false;
if (existingCertificate) {
console.log('Found existing certificate:', existingCertificate);
result = existingCertificate;
usedExisting = true;
} else {
// Create the SSL certificate directly with NGINX
const requestBody = {
domain_names: domains,
meta: {
letsencrypt_email: '{{ nginx_settings.email if nginx_settings else "" }}',
letsencrypt_agree: true,
dns_challenge: false
},
provider: 'letsencrypt'
};
console.log('Request Body:', requestBody);
const response = await fetch(`${nginxSettings.url}/api/nginx/certificates`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(requestBody)
});
console.log('Response Status:', response.status);
console.log('Response Headers:', Object.fromEntries(response.headers.entries()));
if (!response.ok) {
const errorText = await response.text();
console.error('Certificate creation error:', errorText);
throw new Error(`Failed to generate SSL certificate: ${errorText}`);
}
result = await response.json();
console.log('Certificate creation result:', result);
}
// Update the SSL step to show success
const sslStep = document.querySelectorAll('.step-item')[3];
sslStep.classList.remove('active');
sslStep.classList.add('completed');
const sslStatusText = sslStep.querySelector('.step-status');
sslStatusText.textContent = usedExisting ?
'Using existing SSL certificate' :
'SSL certificate generated successfully';
// Always add SSL certificate details
const sslDetails = document.createElement('div');
sslDetails.className = 'mt-3';
sslDetails.innerHTML = `
<div class="card">
<div class="card-body">
<h6 class="card-title mb-3">SSL Certificate Details</h6>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Property</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Certificate ID</td>
<td>${result.id || 'N/A'}</td>
</tr>
<tr>
<td>Domains</td>
<td>${(result.domain_names || domains).join(', ')}</td>
</tr>
<tr>
<td>Provider</td>
<td>${result.provider || 'Let\'s Encrypt'}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
`;
sslStatusText.after(sslDetails);
return {
success: true,
data: {
certificate: result
}
};
} catch (error) {
console.error('Error generating SSL certificate:', error);
return {
success: false,
error: error.message
};
}
}
function updateStep(stepNumber, title, description) {
return new Promise((resolve) => {
// Update the current step in the header
@@ -240,7 +789,8 @@ function updateStep(stepNumber, title, description) {
progressBar.textContent = `${progress}%`;
// Update step items
document.querySelectorAll('.step-item').forEach((item, index) => {
const steps = document.querySelectorAll('.step-item');
steps.forEach((item, index) => {
const step = index + 1;
item.classList.remove('active', 'completed', 'failed');
@@ -254,7 +804,7 @@ function updateStep(stepNumber, title, description) {
});
// Simulate some work being done
setTimeout(resolve, 2000);
setTimeout(resolve, 1000);
});
}
@@ -273,72 +823,9 @@ function showError(message) {
}
}
function completeLaunch() {
// Update progress to 100%
const progressBar = document.getElementById('launchProgress');
progressBar.style.width = '100%';
progressBar.textContent = '100%';
// Mark all steps as completed
document.querySelectorAll('.step-item').forEach(item => {
item.classList.add('completed');
item.querySelector('.step-status').textContent = 'Completed';
});
// Update header
document.getElementById('currentStep').textContent = 'Launch Complete!';
document.getElementById('stepDescription').textContent = 'Your instance is ready to use';
// Show success message and redirect button
const successMessage = document.createElement('div');
successMessage.className = 'alert alert-success text-center mt-4';
successMessage.innerHTML = `
<h5><i class="fas fa-check-circle"></i> Success!</h5>
<p>Your instance has been successfully launched.</p>
<a href="/instances" class="btn btn-success">
<i class="fas fa-arrow-right"></i> Go to Instances
</a>
`;
document.querySelector('.card-body').appendChild(successMessage);
// Clear the launch data from sessionStorage
sessionStorage.removeItem('instanceLaunchData');
}
function retryLaunch() {
// Reload the page to start over
window.location.reload();
}
// Mock API functions (replace these with actual API calls)
async function cloneRepository(repo, branch) {
// Simulate API call
return { success: true };
}
async function createContainer(data) {
// Simulate API call
return { success: true };
}
async function configureNetwork(data) {
// Simulate API call
return { success: true };
}
async function setupDatabase(data) {
// Simulate API call
return { success: true };
}
async function applyCustomization(data) {
// Simulate API call
return { success: true };
}
async function finalizeSetup(data) {
// Simulate API call
return { success: true };
}
</script>
{% endblock %}

View File

@@ -1,6 +1,6 @@
{% macro smtp_settings_tab(smtp_settings, csrf_token) %}
<div class="container-fluid">
<form id="smtpSettingsForm" method="POST" action="{{ url_for('settings.save_smtp_settings') }}">
<form id="smtpSettingsForm" method="POST" action="{{ url_for('main.update_smtp_settings') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<!-- SMTP Server Settings -->