first 4 steps of launch
This commit is contained in:
Binary file not shown.
@@ -920,4 +920,239 @@ def save_git_connection(current_user):
|
|||||||
return jsonify({'message': 'Settings saved successfully'})
|
return jsonify({'message': 'Settings saved successfully'})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@admin_api.route('/create-proxy-host', methods=['POST'])
|
||||||
|
@csrf.exempt
|
||||||
|
@token_required
|
||||||
|
def create_proxy_host(current_user):
|
||||||
|
if not current_user.is_admin:
|
||||||
|
return jsonify({'error': 'Unauthorized'}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
domains = data.get('domains')
|
||||||
|
scheme = data.get('scheme', 'http')
|
||||||
|
forward_ip = data.get('forward_ip')
|
||||||
|
forward_port = data.get('forward_port')
|
||||||
|
|
||||||
|
if not domains or not forward_ip or not forward_port:
|
||||||
|
return jsonify({'error': 'Missing required fields'}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get NGINX settings
|
||||||
|
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
||||||
|
if not nginx_settings:
|
||||||
|
return jsonify({'error': 'NGINX settings not configured'}), 400
|
||||||
|
|
||||||
|
# First, get the JWT token
|
||||||
|
token_response = requests.post(
|
||||||
|
f"{nginx_settings['url'].rstrip('/')}/api/tokens",
|
||||||
|
json={
|
||||||
|
'identity': nginx_settings['username'],
|
||||||
|
'secret': nginx_settings['password']
|
||||||
|
},
|
||||||
|
headers={'Content-Type': 'application/json'},
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
if token_response.status_code != 200:
|
||||||
|
return jsonify({'error': 'Failed to authenticate with NGINX Proxy Manager'}), 400
|
||||||
|
|
||||||
|
token_data = token_response.json()
|
||||||
|
token = token_data.get('token')
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
return jsonify({'error': 'No token received from NGINX Proxy Manager'}), 400
|
||||||
|
|
||||||
|
# Create the proxy host
|
||||||
|
proxy_host_data = {
|
||||||
|
'domain_names': domains,
|
||||||
|
'forward_scheme': scheme,
|
||||||
|
'forward_host': forward_ip,
|
||||||
|
'forward_port': int(forward_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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
f"{nginx_settings['url'].rstrip('/')}/api/nginx/proxy-hosts",
|
||||||
|
json=proxy_host_data,
|
||||||
|
headers={
|
||||||
|
'Authorization': f'Bearer {token}',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Proxy host created successfully',
|
||||||
|
'data': response.json()
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
error_data = response.json()
|
||||||
|
return jsonify({
|
||||||
|
'error': f'Failed to create proxy host: {error_data.get("message", "Unknown error")}'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@admin_api.route('/create-ssl-certificate', methods=['POST'])
|
||||||
|
@csrf.exempt
|
||||||
|
@token_required
|
||||||
|
def create_ssl_certificate(current_user):
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
current_app.logger.info(f"Received request data: {data}")
|
||||||
|
|
||||||
|
domains = data.get('domains')
|
||||||
|
proxy_host_id = data.get('proxy_host_id')
|
||||||
|
nginx_url = data.get('nginx_url')
|
||||||
|
|
||||||
|
current_app.logger.info(f"Extracted data - domains: {domains}, proxy_host_id: {proxy_host_id}, nginx_url: {nginx_url}")
|
||||||
|
|
||||||
|
if not all([domains, proxy_host_id, nginx_url]):
|
||||||
|
missing_fields = []
|
||||||
|
if not domains: missing_fields.append('domains')
|
||||||
|
if not proxy_host_id: missing_fields.append('proxy_host_id')
|
||||||
|
if not nginx_url: missing_fields.append('nginx_url')
|
||||||
|
|
||||||
|
current_app.logger.error(f"Missing required fields: {missing_fields}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': f'Missing required fields: {", ".join(missing_fields)}'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Get NGINX settings
|
||||||
|
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
||||||
|
if not nginx_settings:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'NGINX settings not configured'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# First, get the JWT token
|
||||||
|
token_response = requests.post(
|
||||||
|
f"{nginx_settings['url'].rstrip('/')}/api/tokens",
|
||||||
|
json={
|
||||||
|
'identity': nginx_settings['username'],
|
||||||
|
'secret': nginx_settings['password']
|
||||||
|
},
|
||||||
|
headers={'Content-Type': 'application/json'},
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
|
||||||
|
if token_response.status_code != 200:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'Failed to authenticate with NGINX Proxy Manager'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
token_data = token_response.json()
|
||||||
|
token = token_data.get('token')
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'No token received from NGINX Proxy Manager'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Create the SSL certificate
|
||||||
|
ssl_request_data = {
|
||||||
|
'provider': 'letsencrypt',
|
||||||
|
'domain_names': domains,
|
||||||
|
'meta': {
|
||||||
|
'letsencrypt_agree': True,
|
||||||
|
'dns_challenge': False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_app.logger.info(f"Making SSL certificate request to {nginx_url}/api/nginx/ssl with data: {ssl_request_data}")
|
||||||
|
|
||||||
|
ssl_response = requests.post(
|
||||||
|
f"{nginx_url}/api/nginx/ssl",
|
||||||
|
headers={
|
||||||
|
'Authorization': f'Bearer {token}',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
json=ssl_request_data
|
||||||
|
)
|
||||||
|
|
||||||
|
current_app.logger.info(f"SSL certificate response status: {ssl_response.status_code}")
|
||||||
|
current_app.logger.info(f"SSL certificate response headers: {dict(ssl_response.headers)}")
|
||||||
|
|
||||||
|
if not ssl_response.ok:
|
||||||
|
error_text = ssl_response.text
|
||||||
|
current_app.logger.error(f"Failed to create SSL certificate: {error_text}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': f'Failed to create SSL certificate: {error_text}'
|
||||||
|
}), ssl_response.status_code
|
||||||
|
|
||||||
|
ssl_data = ssl_response.json()
|
||||||
|
current_app.logger.info(f"SSL certificate created successfully: {ssl_data}")
|
||||||
|
|
||||||
|
# Get the certificate ID
|
||||||
|
cert_id = ssl_data.get('id')
|
||||||
|
if not cert_id:
|
||||||
|
current_app.logger.error("No certificate ID received in response")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'No certificate ID received'
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
# Update the proxy host with the certificate
|
||||||
|
update_request_data = {
|
||||||
|
'ssl_certificate_id': cert_id
|
||||||
|
}
|
||||||
|
current_app.logger.info(f"Updating proxy host {proxy_host_id} with data: {update_request_data}")
|
||||||
|
|
||||||
|
update_response = requests.put(
|
||||||
|
f"{nginx_url}/api/nginx/proxy-hosts/{proxy_host_id}",
|
||||||
|
headers={
|
||||||
|
'Authorization': f'Bearer {token}',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
json=update_request_data
|
||||||
|
)
|
||||||
|
|
||||||
|
current_app.logger.info(f"Update response status: {update_response.status_code}")
|
||||||
|
current_app.logger.info(f"Update response headers: {dict(update_response.headers)}")
|
||||||
|
|
||||||
|
if not update_response.ok:
|
||||||
|
error_text = update_response.text
|
||||||
|
current_app.logger.error(f"Failed to update proxy host: {error_text}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': f'Failed to update proxy host: {error_text}'
|
||||||
|
}), update_response.status_code
|
||||||
|
|
||||||
|
update_data = update_response.json()
|
||||||
|
current_app.logger.info(f"Proxy host updated successfully: {update_data}")
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'data': {
|
||||||
|
'certificate': ssl_data,
|
||||||
|
'proxy_host': update_data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"Error in create_ssl_certificate: {str(e)}")
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}), 500
|
||||||
@@ -18,6 +18,7 @@ import json
|
|||||||
import smtplib
|
import smtplib
|
||||||
import requests
|
import requests
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
import socket
|
||||||
|
|
||||||
# Set up logging to show in console
|
# Set up logging to show in console
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@@ -1681,4 +1682,40 @@ def init_routes(main_bp):
|
|||||||
flash('This page is only available in master instances.', 'error')
|
flash('This page is only available in master instances.', 'error')
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
return render_template('main/launch_progress.html')
|
# Get NGINX settings
|
||||||
|
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
||||||
|
|
||||||
|
return render_template('main/launch_progress.html', nginx_settings=nginx_settings)
|
||||||
|
|
||||||
|
@main_bp.route('/api/check-dns', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
@require_password_change
|
||||||
|
def check_dns():
|
||||||
|
if not os.environ.get('MASTER', 'false').lower() == 'true':
|
||||||
|
return jsonify({'error': 'Unauthorized'}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
if not data or 'domains' not in data:
|
||||||
|
return jsonify({'error': 'No domains provided'}), 400
|
||||||
|
|
||||||
|
domains = data['domains']
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
for domain in domains:
|
||||||
|
try:
|
||||||
|
# Try to resolve the domain
|
||||||
|
ip_address = socket.gethostbyname(domain)
|
||||||
|
results[domain] = {
|
||||||
|
'resolved': True,
|
||||||
|
'ip': ip_address
|
||||||
|
}
|
||||||
|
except socket.gaierror:
|
||||||
|
results[domain] = {
|
||||||
|
'resolved': False,
|
||||||
|
'error': 'No DNS record found'
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'results': results
|
||||||
|
})
|
||||||
@@ -30,54 +30,8 @@
|
|||||||
style="width: 0%">0%</div>
|
style="width: 0%">0%</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="launch-steps">
|
<div id="stepsContainer">
|
||||||
<div class="step-item" data-step="1">
|
<!-- Your custom steps will be added here -->
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<div class="text-center mt-4">
|
<div class="text-center mt-4">
|
||||||
@@ -96,10 +50,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.launch-steps {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-item {
|
.step-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -184,49 +134,648 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the steps
|
||||||
|
initializeSteps();
|
||||||
|
|
||||||
// Start the launch process
|
// Start the launch process
|
||||||
startLaunch(launchData);
|
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) {
|
async function startLaunch(data) {
|
||||||
try {
|
try {
|
||||||
// Step 1: Clone Repository
|
// Step 1: Check DNS records
|
||||||
await updateStep(1, 'Cloning Repository', 'Fetching code from repository...');
|
await updateStep(1, 'Checking DNS Records', 'Verifying domain configurations...');
|
||||||
const cloneResult = await cloneRepository(data.repository, data.branch);
|
const dnsResult = await checkDNSRecords(data.webAddresses);
|
||||||
if (!cloneResult.success) throw new Error(cloneResult.error);
|
|
||||||
|
// 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
|
// Update the step to show success
|
||||||
await updateStep(2, 'Creating Container', 'Setting up Docker container...');
|
const dnsStep = document.querySelector('.step-item');
|
||||||
const containerResult = await createContainer(data);
|
dnsStep.classList.remove('active');
|
||||||
if (!containerResult.success) throw new Error(containerResult.error);
|
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
|
// Step 2: Check NGINX connection
|
||||||
await updateStep(3, 'Configuring Network', 'Setting up network and ports...');
|
await updateStep(2, 'Checking NGINX Connection', 'Verifying connection to NGINX Proxy Manager...');
|
||||||
const networkResult = await configureNetwork(data);
|
const nginxResult = await checkNginxConnection();
|
||||||
if (!networkResult.success) throw new Error(networkResult.error);
|
|
||||||
|
if (!nginxResult.success) {
|
||||||
|
throw new Error(nginxResult.error || 'Failed to connect to NGINX Proxy Manager');
|
||||||
|
}
|
||||||
|
|
||||||
// Step 4: Setup Database
|
// Update the step to show success
|
||||||
await updateStep(4, 'Setting Up Database', 'Initializing database...');
|
const nginxStep = document.querySelectorAll('.step-item')[1];
|
||||||
const dbResult = await setupDatabase(data);
|
nginxStep.classList.remove('active');
|
||||||
if (!dbResult.success) throw new Error(dbResult.error);
|
nginxStep.classList.add('completed');
|
||||||
|
nginxStep.querySelector('.step-status').textContent = 'Successfully connected to NGINX Proxy Manager';
|
||||||
|
|
||||||
// Step 5: Apply Customization
|
// Step 3: Create Proxy Host
|
||||||
await updateStep(5, 'Applying Customization', 'Applying your custom settings...');
|
await updateStep(3, 'Creating Proxy Host', 'Setting up NGINX proxy host configuration...');
|
||||||
const customResult = await applyCustomization(data);
|
const proxyResult = await createProxyHost(data.webAddresses, data.port);
|
||||||
if (!customResult.success) throw new Error(customResult.error);
|
|
||||||
|
if (!proxyResult.success) {
|
||||||
|
throw new Error(proxyResult.error || 'Failed to create proxy host');
|
||||||
|
}
|
||||||
|
|
||||||
// Step 6: Finalize
|
// Step 4: Generate SSL Certificate
|
||||||
await updateStep(6, 'Finalizing Setup', 'Completing the setup...');
|
await updateStep(4, 'Generating SSL Certificate', 'Setting up secure HTTPS connection...');
|
||||||
const finalResult = await finalizeSetup(data);
|
const sslResult = await generateSSLCertificate(data.webAddresses, proxyResult.data.id);
|
||||||
if (!finalResult.success) throw new Error(finalResult.error);
|
|
||||||
|
if (!sslResult.success) {
|
||||||
|
throw new Error(sslResult.error || 'Failed to generate SSL certificate');
|
||||||
|
}
|
||||||
|
|
||||||
// Launch complete
|
|
||||||
completeLaunch();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error.message);
|
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) {
|
function updateStep(stepNumber, title, description) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// Update the current step in the header
|
// Update the current step in the header
|
||||||
@@ -240,7 +789,8 @@ function updateStep(stepNumber, title, description) {
|
|||||||
progressBar.textContent = `${progress}%`;
|
progressBar.textContent = `${progress}%`;
|
||||||
|
|
||||||
// Update step items
|
// Update step items
|
||||||
document.querySelectorAll('.step-item').forEach((item, index) => {
|
const steps = document.querySelectorAll('.step-item');
|
||||||
|
steps.forEach((item, index) => {
|
||||||
const step = index + 1;
|
const step = index + 1;
|
||||||
item.classList.remove('active', 'completed', 'failed');
|
item.classList.remove('active', 'completed', 'failed');
|
||||||
|
|
||||||
@@ -254,7 +804,7 @@ function updateStep(stepNumber, title, description) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Simulate some work being done
|
// 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() {
|
function retryLaunch() {
|
||||||
// Reload the page to start over
|
// Reload the page to start over
|
||||||
window.location.reload();
|
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>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{% macro smtp_settings_tab(smtp_settings, csrf_token) %}
|
{% macro smtp_settings_tab(smtp_settings, csrf_token) %}
|
||||||
<div class="container-fluid">
|
<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 }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||||
|
|
||||||
<!-- SMTP Server Settings -->
|
<!-- SMTP Server Settings -->
|
||||||
|
|||||||
Reference in New Issue
Block a user