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)}")
|
||||
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'])
|
||||
@csrf.exempt
|
||||
def save_instance():
|
||||
@@ -1559,18 +1678,26 @@ def copy_smtp_settings():
|
||||
if not jwt_token:
|
||||
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
|
||||
smtp_response = requests.post(
|
||||
f"{instance_url.rstrip('/')}/api/admin/key-value",
|
||||
smtp_response = requests.put(
|
||||
f"{instance_url.rstrip('/')}/api/admin/settings",
|
||||
headers={
|
||||
'Authorization': f'Bearer {jwt_token}',
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
json={
|
||||
'key': 'smtp_settings',
|
||||
'value': smtp_settings
|
||||
},
|
||||
json=api_smtp_data,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
@@ -1582,7 +1709,7 @@ def copy_smtp_settings():
|
||||
|
||||
return jsonify({
|
||||
'message': 'SMTP settings copied successfully',
|
||||
'data': smtp_settings
|
||||
'data': api_smtp_data
|
||||
})
|
||||
|
||||
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')
|
||||
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
||||
git_settings = KeyValueSettings.get_value('git_settings')
|
||||
cloudflare_settings = KeyValueSettings.get_value('cloudflare_settings')
|
||||
|
||||
return render_template('main/instances.html',
|
||||
instances=instances,
|
||||
portainer_settings=portainer_settings,
|
||||
nginx_settings=nginx_settings,
|
||||
git_settings=git_settings)
|
||||
git_settings=git_settings,
|
||||
cloudflare_settings=cloudflare_settings)
|
||||
|
||||
@main_bp.route('/instances/add', methods=['POST'])
|
||||
@login_required
|
||||
@@ -950,6 +952,7 @@ def init_routes(main_bp):
|
||||
portainer_settings = KeyValueSettings.get_value('portainer_settings')
|
||||
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
||||
git_settings = KeyValueSettings.get_value('git_settings')
|
||||
cloudflare_settings = KeyValueSettings.get_value('cloudflare_settings')
|
||||
|
||||
# Get management API key for the connections tab
|
||||
management_api_key = ManagementAPIKey.query.filter_by(is_active=True).first()
|
||||
@@ -1020,6 +1023,7 @@ def init_routes(main_bp):
|
||||
portainer_settings=portainer_settings,
|
||||
nginx_settings=nginx_settings,
|
||||
git_settings=git_settings,
|
||||
cloudflare_settings=cloudflare_settings,
|
||||
csrf_token=generate_csrf())
|
||||
|
||||
@main_bp.route('/settings/update-smtp', methods=['POST'])
|
||||
@@ -1678,6 +1682,77 @@ def init_routes(main_bp):
|
||||
except Exception as e:
|
||||
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')
|
||||
@login_required
|
||||
@require_password_change
|
||||
@@ -1690,10 +1765,13 @@ def init_routes(main_bp):
|
||||
nginx_settings = KeyValueSettings.get_value('nginx_settings')
|
||||
# Get 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',
|
||||
nginx_settings=nginx_settings,
|
||||
portainer_settings=portainer_settings)
|
||||
portainer_settings=portainer_settings,
|
||||
cloudflare_settings=cloudflare_settings)
|
||||
|
||||
@main_bp.route('/api/check-dns', methods=['POST'])
|
||||
@login_required
|
||||
@@ -1728,6 +1806,145 @@ def init_routes(main_bp):
|
||||
'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>')
|
||||
@login_required
|
||||
def get_mail_details(mail_id):
|
||||
|
||||
Reference in New Issue
Block a user