improved launch process using cloudflare
This commit is contained in:
Binary file not shown.
@@ -848,6 +848,125 @@ def deploy_stack():
|
|||||||
current_app.logger.error(f"Error deploying stack: {str(e)}")
|
current_app.logger.error(f"Error deploying stack: {str(e)}")
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@launch_api.route('/check-stack-status', methods=['POST'])
|
||||||
|
@csrf.exempt
|
||||||
|
def check_stack_status():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
if not data or 'stack_name' not in data:
|
||||||
|
return jsonify({'error': 'Missing stack_name field'}), 400
|
||||||
|
|
||||||
|
# Get Portainer settings
|
||||||
|
portainer_settings = KeyValueSettings.get_value('portainer_settings')
|
||||||
|
if not portainer_settings:
|
||||||
|
return jsonify({'error': 'Portainer settings not configured'}), 400
|
||||||
|
|
||||||
|
# Get Portainer endpoint ID (assuming it's the first endpoint)
|
||||||
|
endpoint_response = requests.get(
|
||||||
|
f"{portainer_settings['url'].rstrip('/')}/api/endpoints",
|
||||||
|
headers={
|
||||||
|
'X-API-Key': portainer_settings['api_key'],
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if not endpoint_response.ok:
|
||||||
|
return jsonify({'error': 'Failed to get Portainer endpoints'}), 500
|
||||||
|
|
||||||
|
endpoints = endpoint_response.json()
|
||||||
|
if not endpoints:
|
||||||
|
return jsonify({'error': 'No Portainer endpoints found'}), 400
|
||||||
|
|
||||||
|
endpoint_id = endpoints[0]['Id']
|
||||||
|
|
||||||
|
# Get stack information
|
||||||
|
stacks_url = f"{portainer_settings['url'].rstrip('/')}/api/stacks"
|
||||||
|
stacks_response = requests.get(
|
||||||
|
stacks_url,
|
||||||
|
headers={
|
||||||
|
'X-API-Key': portainer_settings['api_key'],
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
params={'filters': json.dumps({'Name': data['stack_name']})},
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if not stacks_response.ok:
|
||||||
|
return jsonify({'error': 'Failed to get stack information'}), 500
|
||||||
|
|
||||||
|
stacks = stacks_response.json()
|
||||||
|
target_stack = None
|
||||||
|
|
||||||
|
for stack in stacks:
|
||||||
|
if stack['Name'] == data['stack_name']:
|
||||||
|
target_stack = stack
|
||||||
|
break
|
||||||
|
|
||||||
|
if not target_stack:
|
||||||
|
return jsonify({'error': f'Stack {data["stack_name"]} not found'}), 404
|
||||||
|
|
||||||
|
# Get stack services to check their status
|
||||||
|
services_url = f"{portainer_settings['url'].rstrip('/')}/api/endpoints/{endpoint_id}/docker/services"
|
||||||
|
services_response = requests.get(
|
||||||
|
services_url,
|
||||||
|
headers={
|
||||||
|
'X-API-Key': portainer_settings['api_key'],
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
params={'filters': json.dumps({'label': f'com.docker.stack.namespace={data["stack_name"]}'})},
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
if not services_response.ok:
|
||||||
|
return jsonify({'error': 'Failed to get stack services'}), 500
|
||||||
|
|
||||||
|
services = services_response.json()
|
||||||
|
|
||||||
|
# Check if all services are running
|
||||||
|
all_running = True
|
||||||
|
service_statuses = []
|
||||||
|
|
||||||
|
for service in services:
|
||||||
|
replicas_running = service.get('Spec', {}).get('Mode', {}).get('Replicated', {}).get('Replicas', 0)
|
||||||
|
replicas_actual = service.get('ServiceStatus', {}).get('RunningTasks', 0)
|
||||||
|
|
||||||
|
service_status = {
|
||||||
|
'name': service.get('Spec', {}).get('Name', 'Unknown'),
|
||||||
|
'replicas_expected': replicas_running,
|
||||||
|
'replicas_running': replicas_actual,
|
||||||
|
'status': 'running' if replicas_actual >= replicas_running else 'not_running'
|
||||||
|
}
|
||||||
|
|
||||||
|
service_statuses.append(service_status)
|
||||||
|
|
||||||
|
if replicas_actual < replicas_running:
|
||||||
|
all_running = False
|
||||||
|
|
||||||
|
# Determine overall stack status
|
||||||
|
if all_running and len(services) > 0:
|
||||||
|
status = 'active'
|
||||||
|
elif len(services) > 0:
|
||||||
|
status = 'partial'
|
||||||
|
else:
|
||||||
|
status = 'inactive'
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'data': {
|
||||||
|
'stack_name': data['stack_name'],
|
||||||
|
'stack_id': target_stack['Id'],
|
||||||
|
'status': status,
|
||||||
|
'services': service_statuses,
|
||||||
|
'total_services': len(services),
|
||||||
|
'running_services': len([s for s in service_statuses if s['status'] == 'running'])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f"Error checking stack status: {str(e)}")
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
@launch_api.route('/save-instance', methods=['POST'])
|
@launch_api.route('/save-instance', methods=['POST'])
|
||||||
@csrf.exempt
|
@csrf.exempt
|
||||||
def save_instance():
|
def save_instance():
|
||||||
@@ -1559,18 +1678,26 @@ def copy_smtp_settings():
|
|||||||
if not jwt_token:
|
if not jwt_token:
|
||||||
return jsonify({'error': 'No JWT token received'}), 400
|
return jsonify({'error': 'No JWT token received'}), 400
|
||||||
|
|
||||||
|
# Prepare SMTP settings data for the API
|
||||||
|
api_smtp_data = {
|
||||||
|
'smtp_host': smtp_settings.get('smtp_host'),
|
||||||
|
'smtp_port': smtp_settings.get('smtp_port'),
|
||||||
|
'smtp_username': smtp_settings.get('smtp_username'),
|
||||||
|
'smtp_password': smtp_settings.get('smtp_password'),
|
||||||
|
'smtp_security': smtp_settings.get('smtp_security'),
|
||||||
|
'smtp_from_email': smtp_settings.get('smtp_from_email'),
|
||||||
|
'smtp_from_name': smtp_settings.get('smtp_from_name')
|
||||||
|
}
|
||||||
|
|
||||||
# Copy SMTP settings to the launched instance
|
# Copy SMTP settings to the launched instance
|
||||||
smtp_response = requests.post(
|
smtp_response = requests.put(
|
||||||
f"{instance_url.rstrip('/')}/api/admin/key-value",
|
f"{instance_url.rstrip('/')}/api/admin/settings",
|
||||||
headers={
|
headers={
|
||||||
'Authorization': f'Bearer {jwt_token}',
|
'Authorization': f'Bearer {jwt_token}',
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
json={
|
json=api_smtp_data,
|
||||||
'key': 'smtp_settings',
|
|
||||||
'value': smtp_settings
|
|
||||||
},
|
|
||||||
timeout=10
|
timeout=10
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1582,7 +1709,7 @@ def copy_smtp_settings():
|
|||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'message': 'SMTP settings copied successfully',
|
'message': 'SMTP settings copied successfully',
|
||||||
'data': smtp_settings
|
'data': api_smtp_data
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
221
routes/main.py
221
routes/main.py
@@ -391,12 +391,14 @@ def init_routes(main_bp):
|
|||||||
portainer_settings = KeyValueSettings.get_value('portainer_settings')
|
portainer_settings = KeyValueSettings.get_value('portainer_settings')
|
||||||
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
||||||
git_settings = KeyValueSettings.get_value('git_settings')
|
git_settings = KeyValueSettings.get_value('git_settings')
|
||||||
|
cloudflare_settings = KeyValueSettings.get_value('cloudflare_settings')
|
||||||
|
|
||||||
return render_template('main/instances.html',
|
return render_template('main/instances.html',
|
||||||
instances=instances,
|
instances=instances,
|
||||||
portainer_settings=portainer_settings,
|
portainer_settings=portainer_settings,
|
||||||
nginx_settings=nginx_settings,
|
nginx_settings=nginx_settings,
|
||||||
git_settings=git_settings)
|
git_settings=git_settings,
|
||||||
|
cloudflare_settings=cloudflare_settings)
|
||||||
|
|
||||||
@main_bp.route('/instances/add', methods=['POST'])
|
@main_bp.route('/instances/add', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@@ -950,6 +952,7 @@ def init_routes(main_bp):
|
|||||||
portainer_settings = KeyValueSettings.get_value('portainer_settings')
|
portainer_settings = KeyValueSettings.get_value('portainer_settings')
|
||||||
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
||||||
git_settings = KeyValueSettings.get_value('git_settings')
|
git_settings = KeyValueSettings.get_value('git_settings')
|
||||||
|
cloudflare_settings = KeyValueSettings.get_value('cloudflare_settings')
|
||||||
|
|
||||||
# Get management API key for the connections tab
|
# Get management API key for the connections tab
|
||||||
management_api_key = ManagementAPIKey.query.filter_by(is_active=True).first()
|
management_api_key = ManagementAPIKey.query.filter_by(is_active=True).first()
|
||||||
@@ -1020,6 +1023,7 @@ def init_routes(main_bp):
|
|||||||
portainer_settings=portainer_settings,
|
portainer_settings=portainer_settings,
|
||||||
nginx_settings=nginx_settings,
|
nginx_settings=nginx_settings,
|
||||||
git_settings=git_settings,
|
git_settings=git_settings,
|
||||||
|
cloudflare_settings=cloudflare_settings,
|
||||||
csrf_token=generate_csrf())
|
csrf_token=generate_csrf())
|
||||||
|
|
||||||
@main_bp.route('/settings/update-smtp', methods=['POST'])
|
@main_bp.route('/settings/update-smtp', methods=['POST'])
|
||||||
@@ -1678,6 +1682,77 @@ def init_routes(main_bp):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': f'Connection failed: {str(e)}'}), 400
|
return jsonify({'error': f'Connection failed: {str(e)}'}), 400
|
||||||
|
|
||||||
|
@main_bp.route('/settings/save-cloudflare-connection', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def save_cloudflare_connection():
|
||||||
|
if not current_user.is_admin:
|
||||||
|
return jsonify({'error': 'Unauthorized'}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
email = data.get('email')
|
||||||
|
api_key = data.get('api_key')
|
||||||
|
zone_id = data.get('zone_id')
|
||||||
|
server_ip = data.get('server_ip')
|
||||||
|
|
||||||
|
if not email or not api_key or not zone_id or not server_ip:
|
||||||
|
return jsonify({'error': 'Missing required fields'}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Save Cloudflare settings
|
||||||
|
KeyValueSettings.set_value('cloudflare_settings', {
|
||||||
|
'email': email,
|
||||||
|
'api_key': api_key,
|
||||||
|
'zone_id': zone_id,
|
||||||
|
'server_ip': server_ip
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({'message': 'Settings saved successfully'})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@main_bp.route('/settings/test-cloudflare-connection', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def test_cloudflare_connection():
|
||||||
|
if not current_user.is_admin:
|
||||||
|
return jsonify({'error': 'Unauthorized'}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
email = data.get('email')
|
||||||
|
api_key = data.get('api_key')
|
||||||
|
zone_id = data.get('zone_id')
|
||||||
|
server_ip = data.get('server_ip')
|
||||||
|
|
||||||
|
if not email or not api_key or not zone_id or not server_ip:
|
||||||
|
return jsonify({'error': 'Missing required fields'}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Test Cloudflare connection by getting zone details
|
||||||
|
headers = {
|
||||||
|
'X-Auth-Email': email,
|
||||||
|
'X-Auth-Key': api_key,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Try to get zone information
|
||||||
|
response = requests.get(
|
||||||
|
f'https://api.cloudflare.com/client/v4/zones/{zone_id}',
|
||||||
|
headers=headers,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
zone_data = response.json()
|
||||||
|
if zone_data.get('success'):
|
||||||
|
return jsonify({'message': 'Connection successful'})
|
||||||
|
else:
|
||||||
|
return jsonify({'error': f'API error: {zone_data.get("errors", [{}])[0].get("message", "Unknown error")}'}), 400
|
||||||
|
else:
|
||||||
|
return jsonify({'error': f'Connection failed: HTTP {response.status_code}'}), 400
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': f'Connection failed: {str(e)}'}), 400
|
||||||
|
|
||||||
@main_bp.route('/instances/launch-progress')
|
@main_bp.route('/instances/launch-progress')
|
||||||
@login_required
|
@login_required
|
||||||
@require_password_change
|
@require_password_change
|
||||||
@@ -1690,10 +1765,13 @@ def init_routes(main_bp):
|
|||||||
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
||||||
# Get Portainer settings
|
# Get Portainer settings
|
||||||
portainer_settings = KeyValueSettings.get_value('portainer_settings')
|
portainer_settings = KeyValueSettings.get_value('portainer_settings')
|
||||||
|
# Get Cloudflare settings
|
||||||
|
cloudflare_settings = KeyValueSettings.get_value('cloudflare_settings')
|
||||||
|
|
||||||
return render_template('main/launch_progress.html',
|
return render_template('main/launch_progress.html',
|
||||||
nginx_settings=nginx_settings,
|
nginx_settings=nginx_settings,
|
||||||
portainer_settings=portainer_settings)
|
portainer_settings=portainer_settings,
|
||||||
|
cloudflare_settings=cloudflare_settings)
|
||||||
|
|
||||||
@main_bp.route('/api/check-dns', methods=['POST'])
|
@main_bp.route('/api/check-dns', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@@ -1728,6 +1806,145 @@ def init_routes(main_bp):
|
|||||||
'results': results
|
'results': results
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@main_bp.route('/api/check-cloudflare-connection', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
@require_password_change
|
||||||
|
def check_cloudflare_connection():
|
||||||
|
if not os.environ.get('MASTER', 'false').lower() == 'true':
|
||||||
|
return jsonify({'error': 'Unauthorized'}), 403
|
||||||
|
|
||||||
|
# Get Cloudflare settings
|
||||||
|
cloudflare_settings = KeyValueSettings.get_value('cloudflare_settings')
|
||||||
|
if not cloudflare_settings:
|
||||||
|
return jsonify({'error': 'Cloudflare settings not configured'}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Test Cloudflare connection by getting zone details
|
||||||
|
headers = {
|
||||||
|
'X-Auth-Email': cloudflare_settings['email'],
|
||||||
|
'X-Auth-Key': cloudflare_settings['api_key'],
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Try to get zone information
|
||||||
|
response = requests.get(
|
||||||
|
f'https://api.cloudflare.com/client/v4/zones/{cloudflare_settings["zone_id"]}',
|
||||||
|
headers=headers,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
zone_data = response.json()
|
||||||
|
if zone_data.get('success'):
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'message': 'Cloudflare connection successful',
|
||||||
|
'zone_name': zone_data['result']['name']
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return jsonify({'error': f'API error: {zone_data.get("errors", [{}])[0].get("message", "Unknown error")}'}), 400
|
||||||
|
else:
|
||||||
|
return jsonify({'error': f'Connection failed: HTTP {response.status_code}'}), 400
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': f'Connection failed: {str(e)}'}), 400
|
||||||
|
|
||||||
|
@main_bp.route('/api/create-dns-records', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
@require_password_change
|
||||||
|
def create_dns_records():
|
||||||
|
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']
|
||||||
|
|
||||||
|
# Get Cloudflare settings
|
||||||
|
cloudflare_settings = KeyValueSettings.get_value('cloudflare_settings')
|
||||||
|
if not cloudflare_settings:
|
||||||
|
return jsonify({'error': 'Cloudflare settings not configured'}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
headers = {
|
||||||
|
'X-Auth-Email': cloudflare_settings['email'],
|
||||||
|
'X-Auth-Key': cloudflare_settings['api_key'],
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
for domain in domains:
|
||||||
|
# Check if DNS record already exists
|
||||||
|
response = requests.get(
|
||||||
|
f'https://api.cloudflare.com/client/v4/zones/{cloudflare_settings["zone_id"]}/dns_records',
|
||||||
|
headers=headers,
|
||||||
|
params={'name': domain},
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
dns_data = response.json()
|
||||||
|
existing_records = dns_data.get('result', [])
|
||||||
|
|
||||||
|
# Filter for A records
|
||||||
|
a_records = [record for record in existing_records if record['type'] == 'A' and record['name'] == domain]
|
||||||
|
|
||||||
|
if a_records:
|
||||||
|
# Update existing A record
|
||||||
|
record_id = a_records[0]['id']
|
||||||
|
update_data = {
|
||||||
|
'type': 'A',
|
||||||
|
'name': domain,
|
||||||
|
'content': cloudflare_settings['server_ip'],
|
||||||
|
'ttl': 1, # Auto TTL
|
||||||
|
'proxied': True
|
||||||
|
}
|
||||||
|
|
||||||
|
update_response = requests.put(
|
||||||
|
f'https://api.cloudflare.com/client/v4/zones/{cloudflare_settings["zone_id"]}/dns_records/{record_id}',
|
||||||
|
headers=headers,
|
||||||
|
json=update_data,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if update_response.status_code == 200:
|
||||||
|
results[domain] = {'status': 'updated', 'message': 'DNS record updated'}
|
||||||
|
else:
|
||||||
|
results[domain] = {'status': 'error', 'message': f'Failed to update DNS record: {update_response.status_code}'}
|
||||||
|
else:
|
||||||
|
# Create new A record
|
||||||
|
create_data = {
|
||||||
|
'type': 'A',
|
||||||
|
'name': domain,
|
||||||
|
'content': cloudflare_settings['server_ip'],
|
||||||
|
'ttl': 1, # Auto TTL
|
||||||
|
'proxied': True
|
||||||
|
}
|
||||||
|
|
||||||
|
create_response = requests.post(
|
||||||
|
f'https://api.cloudflare.com/client/v4/zones/{cloudflare_settings["zone_id"]}/dns_records',
|
||||||
|
headers=headers,
|
||||||
|
json=create_data,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if create_response.status_code == 200:
|
||||||
|
results[domain] = {'status': 'created', 'message': 'DNS record created'}
|
||||||
|
else:
|
||||||
|
results[domain] = {'status': 'error', 'message': f'Failed to create DNS record: {create_response.status_code}'}
|
||||||
|
else:
|
||||||
|
results[domain] = {'status': 'error', 'message': f'Failed to check existing records: {response.status_code}'}
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'results': results
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': f'DNS operation failed: {str(e)}'}), 400
|
||||||
|
|
||||||
@main_bp.route('/api/mails/<int:mail_id>')
|
@main_bp.route('/api/mails/<int:mail_id>')
|
||||||
@login_required
|
@login_required
|
||||||
def get_mail_details(mail_id):
|
def get_mail_details(mail_id):
|
||||||
|
|||||||
@@ -16,6 +16,30 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
function initializeSteps() {
|
function initializeSteps() {
|
||||||
const stepsContainer = document.getElementById('stepsContainer');
|
const stepsContainer = document.getElementById('stepsContainer');
|
||||||
|
|
||||||
|
// 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
|
// Add DNS check step
|
||||||
const dnsStep = document.createElement('div');
|
const dnsStep = document.createElement('div');
|
||||||
dnsStep.className = 'step-item';
|
dnsStep.className = 'step-item';
|
||||||
@@ -199,8 +223,72 @@ function initializeSteps() {
|
|||||||
|
|
||||||
async function startLaunch(data) {
|
async function startLaunch(data) {
|
||||||
try {
|
try {
|
||||||
// Step 1: Check DNS records
|
// Step 1: Check Cloudflare connection
|
||||||
await updateStep(1, 'Checking DNS Records', 'Verifying domain configurations...');
|
await updateStep(1, 'Checking Cloudflare Connection', 'Verifying Cloudflare API connection...');
|
||||||
|
const cloudflareResult = await checkCloudflareConnection();
|
||||||
|
|
||||||
|
if (!cloudflareResult.success) {
|
||||||
|
throw new Error(cloudflareResult.error || 'Failed to connect to Cloudflare');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the step to show success
|
||||||
|
const cloudflareStep = document.querySelectorAll('.step-item')[0];
|
||||||
|
cloudflareStep.classList.remove('active');
|
||||||
|
cloudflareStep.classList.add('completed');
|
||||||
|
cloudflareStep.querySelector('.step-status').textContent = `Successfully connected to Cloudflare (${cloudflareResult.zone_name})`;
|
||||||
|
|
||||||
|
// Step 2: Create DNS records
|
||||||
|
await updateStep(2, 'Creating DNS Records', 'Setting up domain DNS records in Cloudflare...');
|
||||||
|
const dnsCreateResult = await createDNSRecords(data.webAddresses);
|
||||||
|
|
||||||
|
if (!dnsCreateResult.success) {
|
||||||
|
throw new Error(dnsCreateResult.error || 'Failed to create DNS records');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the step to show success
|
||||||
|
const dnsCreateStep = document.querySelectorAll('.step-item')[1];
|
||||||
|
dnsCreateStep.classList.remove('active');
|
||||||
|
dnsCreateStep.classList.add('completed');
|
||||||
|
dnsCreateStep.querySelector('.step-status').textContent = 'DNS records created successfully';
|
||||||
|
|
||||||
|
// Add DNS creation details
|
||||||
|
const dnsCreateDetails = document.createElement('div');
|
||||||
|
dnsCreateDetails.className = 'mt-3';
|
||||||
|
dnsCreateDetails.innerHTML = `
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title mb-3">DNS Record Creation Results</h6>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Domain</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Message</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${Object.entries(dnsCreateResult.results).map(([domain, result]) => `
|
||||||
|
<tr>
|
||||||
|
<td>${domain}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-${result.status === 'created' || result.status === 'updated' ? 'success' : 'danger'}">
|
||||||
|
${result.status}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>${result.message}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
dnsCreateStep.querySelector('.step-content').appendChild(dnsCreateDetails);
|
||||||
|
|
||||||
|
// Step 3: Check DNS records
|
||||||
|
await updateStep(3, 'Checking DNS Records', 'Verifying domain configurations...');
|
||||||
const dnsResult = await checkDNSRecords(data.webAddresses);
|
const dnsResult = await checkDNSRecords(data.webAddresses);
|
||||||
|
|
||||||
// Check if any domains failed to resolve
|
// Check if any domains failed to resolve
|
||||||
@@ -213,7 +301,7 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the step to show success
|
// Update the step to show success
|
||||||
const dnsStep = document.querySelectorAll('.step-item')[0];
|
const dnsStep = document.querySelectorAll('.step-item')[2];
|
||||||
dnsStep.classList.remove('active');
|
dnsStep.classList.remove('active');
|
||||||
dnsStep.classList.add('completed');
|
dnsStep.classList.add('completed');
|
||||||
|
|
||||||
@@ -259,8 +347,8 @@ async function startLaunch(data) {
|
|||||||
statusText.textContent = 'DNS records verified successfully';
|
statusText.textContent = 'DNS records verified successfully';
|
||||||
statusText.after(detailsSection);
|
statusText.after(detailsSection);
|
||||||
|
|
||||||
// Step 2: Check NGINX connection
|
// Step 4: Check NGINX connection
|
||||||
await updateStep(2, 'Checking NGINX Connection', 'Verifying connection to NGINX Proxy Manager...');
|
await updateStep(4, 'Checking NGINX Connection', 'Verifying connection to NGINX Proxy Manager...');
|
||||||
const nginxResult = await checkNginxConnection();
|
const nginxResult = await checkNginxConnection();
|
||||||
|
|
||||||
if (!nginxResult.success) {
|
if (!nginxResult.success) {
|
||||||
@@ -268,29 +356,29 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the step to show success
|
// Update the step to show success
|
||||||
const nginxStep = document.querySelectorAll('.step-item')[1];
|
const nginxStep = document.querySelectorAll('.step-item')[3];
|
||||||
nginxStep.classList.remove('active');
|
nginxStep.classList.remove('active');
|
||||||
nginxStep.classList.add('completed');
|
nginxStep.classList.add('completed');
|
||||||
nginxStep.querySelector('.step-status').textContent = 'Successfully connected to NGINX Proxy Manager';
|
nginxStep.querySelector('.step-status').textContent = 'Successfully connected to NGINX Proxy Manager';
|
||||||
|
|
||||||
// Step 3: Generate SSL Certificate
|
// Step 5: Generate SSL Certificate
|
||||||
await updateStep(3, 'Generating SSL Certificate', 'Setting up secure HTTPS connection...');
|
await updateStep(5, 'Generating SSL Certificate', 'Setting up secure HTTPS connection...');
|
||||||
const sslResult = await generateSSLCertificate(data.webAddresses);
|
const sslResult = await generateSSLCertificate(data.webAddresses);
|
||||||
|
|
||||||
if (!sslResult.success) {
|
if (!sslResult.success) {
|
||||||
throw new Error(sslResult.error || 'Failed to generate SSL certificate');
|
throw new Error(sslResult.error || 'Failed to generate SSL certificate');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Create Proxy Host
|
// Step 6: Create Proxy Host
|
||||||
await updateStep(4, 'Creating Proxy Host', 'Setting up NGINX proxy host configuration...');
|
await updateStep(6, 'Creating Proxy Host', 'Setting up NGINX proxy host configuration...');
|
||||||
const proxyResult = await createProxyHost(data.webAddresses, data.port, sslResult.data.certificate.id);
|
const proxyResult = await createProxyHost(data.webAddresses, data.port, sslResult.data.certificate.id);
|
||||||
|
|
||||||
if (!proxyResult.success) {
|
if (!proxyResult.success) {
|
||||||
throw new Error(proxyResult.error || 'Failed to create proxy host');
|
throw new Error(proxyResult.error || 'Failed to create proxy host');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Check Portainer connection
|
// Step 7: Check Portainer connection
|
||||||
await updateStep(5, 'Checking Portainer Connection', 'Verifying connection to Portainer...');
|
await updateStep(7, 'Checking Portainer Connection', 'Verifying connection to Portainer...');
|
||||||
const portainerResult = await checkPortainerConnection();
|
const portainerResult = await checkPortainerConnection();
|
||||||
|
|
||||||
if (!portainerResult.success) {
|
if (!portainerResult.success) {
|
||||||
@@ -298,13 +386,13 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the step to show success
|
// Update the step to show success
|
||||||
const portainerStep = document.querySelectorAll('.step-item')[4];
|
const portainerStep = document.querySelectorAll('.step-item')[6];
|
||||||
portainerStep.classList.remove('active');
|
portainerStep.classList.remove('active');
|
||||||
portainerStep.classList.add('completed');
|
portainerStep.classList.add('completed');
|
||||||
portainerStep.querySelector('.step-status').textContent = portainerResult.message;
|
portainerStep.querySelector('.step-status').textContent = portainerResult.message;
|
||||||
|
|
||||||
// Step 6: Download Docker Compose
|
// Step 8: Download Docker Compose
|
||||||
await updateStep(6, 'Downloading Docker Compose', 'Fetching docker-compose.yml from repository...');
|
await updateStep(8, 'Downloading Docker Compose', 'Fetching docker-compose.yml from repository...');
|
||||||
const dockerComposeResult = await downloadDockerCompose(data.repository, data.branch);
|
const dockerComposeResult = await downloadDockerCompose(data.repository, data.branch);
|
||||||
|
|
||||||
if (!dockerComposeResult.success) {
|
if (!dockerComposeResult.success) {
|
||||||
@@ -312,7 +400,7 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the step to show success
|
// Update the step to show success
|
||||||
const dockerComposeStep = document.querySelectorAll('.step-item')[5];
|
const dockerComposeStep = document.querySelectorAll('.step-item')[7];
|
||||||
dockerComposeStep.classList.remove('active');
|
dockerComposeStep.classList.remove('active');
|
||||||
dockerComposeStep.classList.add('completed');
|
dockerComposeStep.classList.add('completed');
|
||||||
dockerComposeStep.querySelector('.step-status').textContent = 'Successfully downloaded docker-compose.yml';
|
dockerComposeStep.querySelector('.step-status').textContent = 'Successfully downloaded docker-compose.yml';
|
||||||
@@ -334,8 +422,8 @@ async function startLaunch(data) {
|
|||||||
};
|
};
|
||||||
dockerComposeStep.querySelector('.step-content').appendChild(downloadButton);
|
dockerComposeStep.querySelector('.step-content').appendChild(downloadButton);
|
||||||
|
|
||||||
// Step 7: Deploy Stack
|
// Step 9: Deploy Stack
|
||||||
await updateStep(7, 'Deploying Stack', 'Launching your application stack...');
|
await updateStep(9, 'Deploying Stack', 'Launching your application stack...');
|
||||||
const stackResult = await deployStack(dockerComposeResult.content, data.instanceName, data.port);
|
const stackResult = await deployStack(dockerComposeResult.content, data.instanceName, data.port);
|
||||||
|
|
||||||
if (!stackResult.success) {
|
if (!stackResult.success) {
|
||||||
@@ -343,7 +431,7 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the step to show success
|
// Update the step to show success
|
||||||
const stackDeployStep = document.querySelectorAll('.step-item')[6];
|
const stackDeployStep = document.querySelectorAll('.step-item')[8];
|
||||||
stackDeployStep.classList.remove('active');
|
stackDeployStep.classList.remove('active');
|
||||||
stackDeployStep.classList.add('completed');
|
stackDeployStep.classList.add('completed');
|
||||||
stackDeployStep.querySelector('.step-status').textContent =
|
stackDeployStep.querySelector('.step-status').textContent =
|
||||||
@@ -392,7 +480,7 @@ async function startLaunch(data) {
|
|||||||
stackDeployStep.querySelector('.step-content').appendChild(stackDetails);
|
stackDeployStep.querySelector('.step-content').appendChild(stackDetails);
|
||||||
|
|
||||||
// Save instance data
|
// Save instance data
|
||||||
await updateStep(8, 'Saving Instance Data', 'Storing instance information...');
|
await updateStep(10, 'Saving Instance Data', 'Storing instance information...');
|
||||||
try {
|
try {
|
||||||
const instanceData = {
|
const instanceData = {
|
||||||
name: data.instanceName,
|
name: data.instanceName,
|
||||||
@@ -407,15 +495,15 @@ async function startLaunch(data) {
|
|||||||
console.log('Saving instance data:', instanceData);
|
console.log('Saving instance data:', instanceData);
|
||||||
const saveResult = await saveInstanceData(instanceData);
|
const saveResult = await saveInstanceData(instanceData);
|
||||||
console.log('Save result:', saveResult);
|
console.log('Save result:', saveResult);
|
||||||
await updateStep(8, 'Saving Instance Data', 'Instance data saved successfully');
|
await updateStep(10, 'Saving Instance Data', 'Instance data saved successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving instance data:', error);
|
console.error('Error saving instance data:', error);
|
||||||
await updateStep(8, 'Saving Instance Data', `Error: ${error.message}`);
|
await updateStep(10, 'Saving Instance Data', `Error: ${error.message}`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the step to show success
|
// Update the step to show success
|
||||||
const saveDataStep = document.querySelectorAll('.step-item')[7];
|
const saveDataStep = document.querySelectorAll('.step-item')[9];
|
||||||
saveDataStep.classList.remove('active');
|
saveDataStep.classList.remove('active');
|
||||||
saveDataStep.classList.add('completed');
|
saveDataStep.classList.add('completed');
|
||||||
saveDataStep.querySelector('.step-status').textContent = 'Successfully saved instance data';
|
saveDataStep.querySelector('.step-status').textContent = 'Successfully saved instance data';
|
||||||
@@ -453,7 +541,7 @@ async function startLaunch(data) {
|
|||||||
saveDataStep.querySelector('.step-content').appendChild(instanceDetails);
|
saveDataStep.querySelector('.step-content').appendChild(instanceDetails);
|
||||||
|
|
||||||
// After saving instance data, add the health check step
|
// After saving instance data, add the health check step
|
||||||
await updateStep(9, 'Health Check', 'Verifying instance health...');
|
await updateStep(11, 'Health Check', 'Verifying instance health...');
|
||||||
const healthResult = await checkInstanceHealth(`https://${data.webAddresses[0]}`);
|
const healthResult = await checkInstanceHealth(`https://${data.webAddresses[0]}`);
|
||||||
|
|
||||||
if (!healthResult.success) {
|
if (!healthResult.success) {
|
||||||
@@ -461,7 +549,7 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add a retry button if health check fails
|
// Add a retry button if health check fails
|
||||||
const healthStep = document.querySelectorAll('.step-item')[8];
|
const healthStep = document.querySelectorAll('.step-item')[10];
|
||||||
if (!healthResult.success) {
|
if (!healthResult.success) {
|
||||||
const retryButton = document.createElement('button');
|
const retryButton = document.createElement('button');
|
||||||
retryButton.className = 'btn btn-sm btn-warning mt-2';
|
retryButton.className = 'btn btn-sm btn-warning mt-2';
|
||||||
@@ -483,7 +571,7 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// After health check, add authentication step
|
// After health check, add authentication step
|
||||||
await updateStep(10, 'Instance Authentication', 'Setting up instance authentication...');
|
await updateStep(12, 'Instance Authentication', 'Setting up instance authentication...');
|
||||||
const authResult = await authenticateInstance(`https://${data.webAddresses[0]}`, data.instanceId);
|
const authResult = await authenticateInstance(`https://${data.webAddresses[0]}`, data.instanceId);
|
||||||
|
|
||||||
if (!authResult.success) {
|
if (!authResult.success) {
|
||||||
@@ -491,7 +579,7 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the auth step to show success
|
// Update the auth step to show success
|
||||||
const authStep = document.querySelectorAll('.step-item')[9];
|
const authStep = document.querySelectorAll('.step-item')[11];
|
||||||
authStep.classList.remove('active');
|
authStep.classList.remove('active');
|
||||||
authStep.classList.add('completed');
|
authStep.classList.add('completed');
|
||||||
authStep.querySelector('.step-status').textContent = authResult.alreadyAuthenticated ?
|
authStep.querySelector('.step-status').textContent = authResult.alreadyAuthenticated ?
|
||||||
@@ -538,8 +626,8 @@ async function startLaunch(data) {
|
|||||||
`;
|
`;
|
||||||
authStep.querySelector('.step-content').appendChild(authDetails);
|
authStep.querySelector('.step-content').appendChild(authDetails);
|
||||||
|
|
||||||
// Step 11: Apply Company Information
|
// Step 13: Apply Company Information
|
||||||
await updateStep(11, 'Apply Company Information', 'Configuring company details...');
|
await updateStep(13, 'Apply Company Information', 'Configuring company details...');
|
||||||
const companyResult = await applyCompanyInformation(`https://${data.webAddresses[0]}`, data.company);
|
const companyResult = await applyCompanyInformation(`https://${data.webAddresses[0]}`, data.company);
|
||||||
|
|
||||||
if (!companyResult.success) {
|
if (!companyResult.success) {
|
||||||
@@ -547,7 +635,7 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the company step to show success
|
// Update the company step to show success
|
||||||
const companyStep = document.querySelectorAll('.step-item')[10];
|
const companyStep = document.querySelectorAll('.step-item')[12];
|
||||||
companyStep.classList.remove('active');
|
companyStep.classList.remove('active');
|
||||||
companyStep.classList.add('completed');
|
companyStep.classList.add('completed');
|
||||||
companyStep.querySelector('.step-status').textContent = 'Successfully applied company information';
|
companyStep.querySelector('.step-status').textContent = 'Successfully applied company information';
|
||||||
@@ -596,8 +684,8 @@ async function startLaunch(data) {
|
|||||||
`;
|
`;
|
||||||
companyStep.querySelector('.step-content').appendChild(companyDetails);
|
companyStep.querySelector('.step-content').appendChild(companyDetails);
|
||||||
|
|
||||||
// Step 12: Apply Colors
|
// Step 14: Apply Colors
|
||||||
await updateStep(12, 'Apply Colors', 'Configuring color scheme...');
|
await updateStep(14, 'Apply Colors', 'Configuring color scheme...');
|
||||||
const colorsResult = await applyColors(`https://${data.webAddresses[0]}`, data.colors);
|
const colorsResult = await applyColors(`https://${data.webAddresses[0]}`, data.colors);
|
||||||
|
|
||||||
if (!colorsResult.success) {
|
if (!colorsResult.success) {
|
||||||
@@ -605,7 +693,7 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the colors step to show success
|
// Update the colors step to show success
|
||||||
const colorsStep = document.querySelectorAll('.step-item')[11];
|
const colorsStep = document.querySelectorAll('.step-item')[13];
|
||||||
colorsStep.classList.remove('active');
|
colorsStep.classList.remove('active');
|
||||||
colorsStep.classList.add('completed');
|
colorsStep.classList.add('completed');
|
||||||
colorsStep.querySelector('.step-status').textContent = 'Successfully applied color scheme';
|
colorsStep.querySelector('.step-status').textContent = 'Successfully applied color scheme';
|
||||||
@@ -645,8 +733,8 @@ async function startLaunch(data) {
|
|||||||
`;
|
`;
|
||||||
colorsStep.querySelector('.step-content').appendChild(colorsDetails);
|
colorsStep.querySelector('.step-content').appendChild(colorsDetails);
|
||||||
|
|
||||||
// Step 13: Update Admin Credentials
|
// Step 15: Update Admin Credentials
|
||||||
await updateStep(13, 'Update Admin Credentials', 'Setting up admin account...');
|
await updateStep(15, 'Update Admin Credentials', 'Setting up admin account...');
|
||||||
const credentialsResult = await updateAdminCredentials(`https://${data.webAddresses[0]}`, data.company.email);
|
const credentialsResult = await updateAdminCredentials(`https://${data.webAddresses[0]}`, data.company.email);
|
||||||
|
|
||||||
if (!credentialsResult.success) {
|
if (!credentialsResult.success) {
|
||||||
@@ -654,7 +742,7 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the credentials step to show success
|
// Update the credentials step to show success
|
||||||
const credentialsStep = document.querySelectorAll('.step-item')[12];
|
const credentialsStep = document.querySelectorAll('.step-item')[14];
|
||||||
credentialsStep.classList.remove('active');
|
credentialsStep.classList.remove('active');
|
||||||
credentialsStep.classList.add('completed');
|
credentialsStep.classList.add('completed');
|
||||||
|
|
||||||
@@ -719,8 +807,8 @@ async function startLaunch(data) {
|
|||||||
`;
|
`;
|
||||||
credentialsStep.querySelector('.step-content').appendChild(credentialsDetails);
|
credentialsStep.querySelector('.step-content').appendChild(credentialsDetails);
|
||||||
|
|
||||||
// Step 14: Copy SMTP Settings
|
// Step 16: Copy SMTP Settings
|
||||||
await updateStep(14, 'Copy SMTP Settings', 'Configuring email settings...');
|
await updateStep(16, 'Copy SMTP Settings', 'Configuring email settings...');
|
||||||
const smtpResult = await copySmtpSettings(`https://${data.webAddresses[0]}`);
|
const smtpResult = await copySmtpSettings(`https://${data.webAddresses[0]}`);
|
||||||
|
|
||||||
if (!smtpResult.success) {
|
if (!smtpResult.success) {
|
||||||
@@ -728,7 +816,7 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the SMTP step to show success
|
// Update the SMTP step to show success
|
||||||
const smtpStep = document.querySelectorAll('.step-item')[13];
|
const smtpStep = document.querySelectorAll('.step-item')[15];
|
||||||
smtpStep.classList.remove('active');
|
smtpStep.classList.remove('active');
|
||||||
smtpStep.classList.add('completed');
|
smtpStep.classList.add('completed');
|
||||||
smtpStep.querySelector('.step-status').textContent = 'Successfully copied SMTP settings';
|
smtpStep.querySelector('.step-status').textContent = 'Successfully copied SMTP settings';
|
||||||
@@ -789,8 +877,8 @@ async function startLaunch(data) {
|
|||||||
`;
|
`;
|
||||||
smtpStep.querySelector('.step-content').appendChild(smtpDetails);
|
smtpStep.querySelector('.step-content').appendChild(smtpDetails);
|
||||||
|
|
||||||
// Step 15: Send Completion Email
|
// Step 17: Send Completion Email
|
||||||
await updateStep(15, 'Send Completion Email', 'Sending notification to client...');
|
await updateStep(17, 'Send Completion Email', 'Sending notification to client...');
|
||||||
const emailResult = await sendCompletionEmail(`https://${data.webAddresses[0]}`, data.company, credentialsResult.data);
|
const emailResult = await sendCompletionEmail(`https://${data.webAddresses[0]}`, data.company, credentialsResult.data);
|
||||||
|
|
||||||
if (!emailResult.success) {
|
if (!emailResult.success) {
|
||||||
@@ -798,7 +886,7 @@ async function startLaunch(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the email step to show success
|
// Update the email step to show success
|
||||||
const emailStep = document.querySelectorAll('.step-item')[14];
|
const emailStep = document.querySelectorAll('.step-item')[16];
|
||||||
emailStep.classList.remove('active');
|
emailStep.classList.remove('active');
|
||||||
emailStep.classList.add('completed');
|
emailStep.classList.add('completed');
|
||||||
emailStep.querySelector('.step-status').textContent = 'Successfully sent completion email';
|
emailStep.querySelector('.step-status').textContent = 'Successfully sent completion email';
|
||||||
@@ -983,31 +1071,128 @@ Thank you for choosing DocuPulse!
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Launch failed:', error);
|
console.error('Launch failed:', error);
|
||||||
await updateStep(15, 'Send Completion Email', `Error: ${error.message}`);
|
await updateStep(17, 'Send Completion Email', `Error: ${error.message}`);
|
||||||
showError(error.message);
|
showError(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkDNSRecords(domains) {
|
async function checkDNSRecords(domains) {
|
||||||
|
const maxRetries = 30; // 30 retries * 10 seconds = 5 minutes
|
||||||
|
const baseDelay = 10000; // 10 seconds base delay
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/check-dns', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ domains: domains })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to check DNS records');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
// Check if all domains are resolved
|
||||||
|
const allResolved = Object.values(result.results).every(result => result.resolved);
|
||||||
|
|
||||||
|
if (allResolved) {
|
||||||
|
console.log(`DNS records resolved successfully on attempt ${attempt}`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not all domains are resolved and this isn't the last attempt, wait and retry
|
||||||
|
if (attempt < maxRetries) {
|
||||||
|
const delay = baseDelay * Math.pow(1.2, attempt - 1); // Exponential backoff
|
||||||
|
const failedDomains = Object.entries(result.results)
|
||||||
|
.filter(([_, result]) => !result.resolved)
|
||||||
|
.map(([domain]) => domain);
|
||||||
|
|
||||||
|
console.log(`Attempt ${attempt}/${maxRetries}: DNS not yet propagated for ${failedDomains.join(', ')}. Waiting ${Math.round(delay/1000)}s before retry...`);
|
||||||
|
|
||||||
|
// Update the step description to show retry progress
|
||||||
|
const currentStep = document.querySelector('.step-item.active');
|
||||||
|
if (currentStep) {
|
||||||
|
const statusElement = currentStep.querySelector('.step-status');
|
||||||
|
statusElement.textContent = `Waiting for DNS propagation... (Attempt ${attempt}/${maxRetries})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delay));
|
||||||
|
} else {
|
||||||
|
// Last attempt failed
|
||||||
|
console.log(`DNS records failed to resolve after ${maxRetries} attempts`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error checking DNS records (attempt ${attempt}):`, error);
|
||||||
|
|
||||||
|
if (attempt === maxRetries) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait before retrying on error
|
||||||
|
const delay = baseDelay * Math.pow(1.2, attempt - 1);
|
||||||
|
console.log(`DNS check failed, retrying in ${Math.round(delay/1000)}s...`);
|
||||||
|
|
||||||
|
// Update the step description to show retry progress
|
||||||
|
const currentStep = document.querySelector('.step-item.active');
|
||||||
|
if (currentStep) {
|
||||||
|
const statusElement = currentStep.querySelector('.step-status');
|
||||||
|
statusElement.textContent = `DNS check failed, retrying... (Attempt ${attempt}/${maxRetries})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkCloudflareConnection() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/check-dns', {
|
const response = await fetch('/api/check-cloudflare-connection', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
'X-CSRF-Token': window.csrfToken
|
||||||
},
|
}
|
||||||
body: JSON.stringify({ domains })
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to check DNS records');
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to check Cloudflare connection');
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
return await response.json();
|
||||||
console.log('DNS check result:', result);
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking DNS records:', error);
|
console.error('Error checking Cloudflare connection:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createDNSRecords(domains) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/create-dns-records', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': window.csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ domains: domains })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to create DNS records');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating DNS records:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1390,7 +1575,7 @@ async function createProxyHost(domains, port, sslCertificateId) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// Update the proxy step to show success and add the results
|
// Update the proxy step to show success and add the results
|
||||||
const proxyStep = document.querySelectorAll('.step-item')[3];
|
const proxyStep = document.querySelectorAll('.step-item')[5];
|
||||||
proxyStep.classList.remove('active');
|
proxyStep.classList.remove('active');
|
||||||
proxyStep.classList.add('completed');
|
proxyStep.classList.add('completed');
|
||||||
const statusText = proxyStep.querySelector('.step-status');
|
const statusText = proxyStep.querySelector('.step-status');
|
||||||
@@ -1509,7 +1694,7 @@ async function generateSSLCertificate(domains) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the SSL step to show success
|
// Update the SSL step to show success
|
||||||
const sslStep = document.querySelectorAll('.step-item')[2];
|
const sslStep = document.querySelectorAll('.step-item')[4];
|
||||||
sslStep.classList.remove('active');
|
sslStep.classList.remove('active');
|
||||||
sslStep.classList.add('completed');
|
sslStep.classList.add('completed');
|
||||||
const sslStatusText = sslStep.querySelector('.step-status');
|
const sslStatusText = sslStep.querySelector('.step-status');
|
||||||
@@ -1589,8 +1774,8 @@ function updateStep(stepNumber, title, description) {
|
|||||||
document.getElementById('currentStep').textContent = title;
|
document.getElementById('currentStep').textContent = title;
|
||||||
document.getElementById('stepDescription').textContent = description;
|
document.getElementById('stepDescription').textContent = description;
|
||||||
|
|
||||||
// Calculate progress based on total number of steps (14 steps total)
|
// Calculate progress based on total number of steps (17 steps total)
|
||||||
const totalSteps = 14;
|
const totalSteps = 17;
|
||||||
const progress = ((stepNumber - 1) / (totalSteps - 1)) * 100;
|
const progress = ((stepNumber - 1) / (totalSteps - 1)) * 100;
|
||||||
const progressBar = document.getElementById('launchProgress');
|
const progressBar = document.getElementById('launchProgress');
|
||||||
progressBar.style.width = `${progress}%`;
|
progressBar.style.width = `${progress}%`;
|
||||||
@@ -1674,7 +1859,11 @@ async function downloadDockerCompose(repo, branch) {
|
|||||||
|
|
||||||
// Add new function to deploy stack
|
// Add new function to deploy stack
|
||||||
async function deployStack(dockerComposeContent, stackName, port) {
|
async function deployStack(dockerComposeContent, stackName, port) {
|
||||||
|
const maxRetries = 30; // 30 retries * 10 seconds = 5 minutes
|
||||||
|
const baseDelay = 10000; // 10 seconds base delay
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// First, attempt to deploy the stack
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 10 * 60 * 1000); // 10 minutes timeout
|
const timeoutId = setTimeout(() => controller.abort(), 10 * 60 * 1000); // 10 minutes timeout
|
||||||
|
|
||||||
@@ -1709,10 +1898,135 @@ async function deployStack(dockerComposeContent, stackName, port) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
|
// If deployment was successful, wait for stack to come online
|
||||||
|
if (result.success || result.data) {
|
||||||
|
console.log('Stack deployment initiated, waiting for stack to come online...');
|
||||||
|
|
||||||
|
// Update status to show we're waiting for stack to come online
|
||||||
|
const currentStep = document.querySelector('.step-item.active');
|
||||||
|
if (currentStep) {
|
||||||
|
const statusElement = currentStep.querySelector('.step-status');
|
||||||
|
statusElement.textContent = 'Stack deployed, waiting for services to start...';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait and retry to check if stack is online
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
|
try {
|
||||||
|
// Check stack status via Portainer API
|
||||||
|
const stackCheckResponse = await fetch('/api/admin/check-stack-status', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
stack_name: `docupulse_${port}`
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (stackCheckResponse.ok) {
|
||||||
|
const stackStatus = await stackCheckResponse.json();
|
||||||
|
|
||||||
|
if (stackStatus.success && stackStatus.data.status === 'active') {
|
||||||
|
console.log(`Stack came online successfully on attempt ${attempt}`);
|
||||||
|
|
||||||
|
// Update status to show success
|
||||||
|
if (currentStep) {
|
||||||
|
const statusElement = currentStep.querySelector('.step-status');
|
||||||
|
statusElement.textContent = 'Stack deployed and online successfully';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
...result.data || result,
|
||||||
|
status: 'active',
|
||||||
|
attempt: attempt
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not online yet and this isn't the last attempt, wait and retry
|
||||||
|
if (attempt < maxRetries) {
|
||||||
|
const delay = baseDelay * Math.pow(1.2, attempt - 1); // Exponential backoff
|
||||||
|
|
||||||
|
console.log(`Attempt ${attempt}/${maxRetries}: Stack not yet online. Waiting ${Math.round(delay/1000)}s before retry...`);
|
||||||
|
|
||||||
|
// Update the step description to show retry progress
|
||||||
|
if (currentStep) {
|
||||||
|
const statusElement = currentStep.querySelector('.step-status');
|
||||||
|
statusElement.textContent = `Waiting for stack to come online... (Attempt ${attempt}/${maxRetries})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delay));
|
||||||
|
} else {
|
||||||
|
// Last attempt failed - stack might be online but API check failed
|
||||||
|
console.log(`Stack status check failed after ${maxRetries} attempts, but deployment was successful`);
|
||||||
|
|
||||||
|
// Update status to show partial success
|
||||||
|
if (currentStep) {
|
||||||
|
const statusElement = currentStep.querySelector('.step-status');
|
||||||
|
statusElement.textContent = 'Stack deployed (status check timeout)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
...result.data || result,
|
||||||
|
status: 'deployed',
|
||||||
|
note: 'Status check timeout - stack may be online'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Stack status check attempt ${attempt} failed:`, error);
|
||||||
|
|
||||||
|
if (attempt === maxRetries) {
|
||||||
|
// Last attempt failed, but deployment was successful
|
||||||
|
console.log('Stack status check failed after all attempts, but deployment was successful');
|
||||||
|
|
||||||
|
if (currentStep) {
|
||||||
|
const statusElement = currentStep.querySelector('.step-status');
|
||||||
|
statusElement.textContent = 'Stack deployed (status check failed)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
...result.data || result,
|
||||||
|
status: 'deployed',
|
||||||
|
note: 'Status check failed - stack may be online'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait before retrying on error
|
||||||
|
const delay = baseDelay * Math.pow(1.2, attempt - 1);
|
||||||
|
console.log(`Stack check failed, retrying in ${Math.round(delay/1000)}s...`);
|
||||||
|
|
||||||
|
if (currentStep) {
|
||||||
|
const statusElement = currentStep.querySelector('.step-status');
|
||||||
|
statusElement.textContent = `Stack check failed, retrying... (Attempt ${attempt}/${maxRetries})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, deployment was successful but we couldn't verify status
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: result
|
data: {
|
||||||
|
...result.data || result,
|
||||||
|
status: 'deployed',
|
||||||
|
note: 'Deployment successful, status unknown'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deploying stack:', error);
|
console.error('Error deploying stack:', error);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -435,6 +435,100 @@ async function saveGitConnection(event, provider) {
|
|||||||
saveModal.show();
|
saveModal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test Cloudflare Connection
|
||||||
|
async function testCloudflareConnection() {
|
||||||
|
const saveModal = new bootstrap.Modal(document.getElementById('saveConnectionModal'));
|
||||||
|
const messageElement = document.getElementById('saveConnectionMessage');
|
||||||
|
messageElement.textContent = 'Testing connection...';
|
||||||
|
messageElement.className = '';
|
||||||
|
saveModal.show();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const email = document.getElementById('cloudflareEmail').value;
|
||||||
|
const apiKey = document.getElementById('cloudflareApiKey').value;
|
||||||
|
const zoneId = document.getElementById('cloudflareZone').value;
|
||||||
|
const serverIp = document.getElementById('cloudflareServerIp').value;
|
||||||
|
|
||||||
|
if (!email || !apiKey || !zoneId || !serverIp) {
|
||||||
|
throw new Error('Please fill in all required fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
email: email,
|
||||||
|
api_key: apiKey,
|
||||||
|
zone_id: zoneId,
|
||||||
|
server_ip: serverIp
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch('/settings/test-cloudflare-connection', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': getCsrfToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Connection test failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
messageElement.textContent = 'Connection test successful!';
|
||||||
|
messageElement.className = 'text-success';
|
||||||
|
} catch (error) {
|
||||||
|
messageElement.textContent = error.message || 'Connection test failed';
|
||||||
|
messageElement.className = 'text-danger';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save Cloudflare Connection
|
||||||
|
async function saveCloudflareConnection(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const saveModal = new bootstrap.Modal(document.getElementById('saveConnectionModal'));
|
||||||
|
const messageElement = document.getElementById('saveConnectionMessage');
|
||||||
|
messageElement.textContent = '';
|
||||||
|
messageElement.className = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const email = document.getElementById('cloudflareEmail').value;
|
||||||
|
const apiKey = document.getElementById('cloudflareApiKey').value;
|
||||||
|
const zoneId = document.getElementById('cloudflareZone').value;
|
||||||
|
const serverIp = document.getElementById('cloudflareServerIp').value;
|
||||||
|
|
||||||
|
if (!email || !apiKey || !zoneId || !serverIp) {
|
||||||
|
throw new Error('Please fill in all required fields');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/settings/save-cloudflare-connection', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-Token': getCsrfToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: email,
|
||||||
|
api_key: apiKey,
|
||||||
|
zone_id: zoneId,
|
||||||
|
server_ip: serverIp
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to save settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
messageElement.textContent = 'Settings saved successfully!';
|
||||||
|
messageElement.className = 'text-success';
|
||||||
|
} catch (error) {
|
||||||
|
messageElement.textContent = error.message || 'Failed to save settings';
|
||||||
|
messageElement.className = 'text-danger';
|
||||||
|
}
|
||||||
|
|
||||||
|
saveModal.show();
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize on page load
|
// Initialize on page load
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const gitSettings = JSON.parse(document.querySelector('meta[name="git-settings"]').getAttribute('content'));
|
const gitSettings = JSON.parse(document.querySelector('meta[name="git-settings"]').getAttribute('content'));
|
||||||
|
|||||||
@@ -61,7 +61,6 @@
|
|||||||
|
|
||||||
<div class="text-center mt-3">
|
<div class="text-center mt-3">
|
||||||
<p class="mb-1"><a href="{{ url_for('auth.forgot_password') }}" class="text-decoration-none">Forgot your password?</a></p>
|
<p class="mb-1"><a href="{{ url_for('auth.forgot_password') }}" class="text-decoration-none">Forgot your password?</a></p>
|
||||||
<p class="mb-0">Don't have an account? <a href="{{ url_for('auth.register') }}" class="text-decoration-none">Sign Up</a></p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -69,6 +69,13 @@
|
|||||||
api_key: '{{ portainer_settings.api_key if portainer_settings else "" }}'
|
api_key: '{{ portainer_settings.api_key if portainer_settings else "" }}'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.cloudflareSettings = {
|
||||||
|
email: '{{ cloudflare_settings.email if cloudflare_settings else "" }}',
|
||||||
|
api_key: '{{ cloudflare_settings.api_key if cloudflare_settings else "" }}',
|
||||||
|
zone_id: '{{ cloudflare_settings.zone_id if cloudflare_settings else "" }}',
|
||||||
|
server_ip: '{{ cloudflare_settings.server_ip if cloudflare_settings else "" }}'
|
||||||
|
};
|
||||||
|
|
||||||
// Pass CSRF token to JavaScript
|
// Pass CSRF token to JavaScript
|
||||||
window.csrfToken = '{{ csrf_token }}';
|
window.csrfToken = '{{ csrf_token }}';
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -134,7 +134,7 @@
|
|||||||
{% if is_master %}
|
{% if is_master %}
|
||||||
<!-- Connections Tab -->
|
<!-- Connections Tab -->
|
||||||
<div class="tab-pane fade {% if active_tab == 'connections' %}show active{% endif %}" id="connections" role="tabpanel" aria-labelledby="connections-tab">
|
<div class="tab-pane fade {% if active_tab == 'connections' %}show active{% endif %}" id="connections" role="tabpanel" aria-labelledby="connections-tab">
|
||||||
{{ connections_tab(portainer_settings, nginx_settings, site_settings, git_settings) }}
|
{{ connections_tab(portainer_settings, nginx_settings, site_settings, git_settings, cloudflare_settings) }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% from "settings/components/connection_modals.html" import connection_modals %}
|
{% from "settings/components/connection_modals.html" import connection_modals %}
|
||||||
|
|
||||||
{% macro connections_tab(portainer_settings, nginx_settings, site_settings, git_settings) %}
|
{% macro connections_tab(portainer_settings, nginx_settings, site_settings, git_settings, cloudflare_settings) %}
|
||||||
<!-- Meta tags for JavaScript -->
|
<!-- Meta tags for JavaScript -->
|
||||||
<meta name="management-api-key" content="{{ site_settings.management_api_key }}">
|
<meta name="management-api-key" content="{{ site_settings.management_api_key }}">
|
||||||
<meta name="git-settings" content="{{ git_settings|tojson|safe }}">
|
<meta name="git-settings" content="{{ git_settings|tojson|safe }}">
|
||||||
@@ -161,6 +161,57 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Cloudflare Connection Card -->
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-cloud me-2"></i>Cloudflare Connection
|
||||||
|
</h5>
|
||||||
|
<button class="btn btn-sm btn-outline-primary" onclick="testCloudflareConnection()">
|
||||||
|
<i class="fas fa-plug me-1"></i>Test Connection
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="cloudflareForm" onsubmit="saveCloudflareConnection(event)">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="cloudflareEmail" class="form-label">Email Address</label>
|
||||||
|
<input type="email" class="form-control" id="cloudflareEmail" name="cloudflareEmail"
|
||||||
|
placeholder="Enter your Cloudflare email" required
|
||||||
|
value="{{ cloudflare_settings.email if cloudflare_settings and cloudflare_settings.email else '' }}">
|
||||||
|
<div class="form-text">The email address associated with your Cloudflare account</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="cloudflareApiKey" class="form-label">API Key</label>
|
||||||
|
<input type="password" class="form-control" id="cloudflareApiKey" name="cloudflareApiKey"
|
||||||
|
placeholder="Enter your Cloudflare API key" required
|
||||||
|
value="{{ cloudflare_settings.api_key if cloudflare_settings and cloudflare_settings.api_key else '' }}">
|
||||||
|
<div class="form-text">You can generate this in your Cloudflare account settings</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="cloudflareZone" class="form-label">Zone ID</label>
|
||||||
|
<input type="text" class="form-control" id="cloudflareZone" name="cloudflareZone"
|
||||||
|
placeholder="Enter your Cloudflare zone ID" required
|
||||||
|
value="{{ cloudflare_settings.zone_id if cloudflare_settings and cloudflare_settings.zone_id else '' }}">
|
||||||
|
<div class="form-text">The zone ID for your domain in Cloudflare</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="cloudflareServerIp" class="form-label">Server IP Address</label>
|
||||||
|
<input type="text" class="form-control" id="cloudflareServerIp" name="cloudflareServerIp"
|
||||||
|
placeholder="Enter your server IP address (e.g., 192.168.1.100)" required
|
||||||
|
value="{{ cloudflare_settings.server_ip if cloudflare_settings and cloudflare_settings.server_ip else '' }}">
|
||||||
|
<div class="form-text">The IP address of this server for DNS management</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save me-1"></i>Save Cloudflare Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Save Connection Modal -->
|
<!-- Save Connection Modal -->
|
||||||
|
|||||||
Reference in New Issue
Block a user