Refactor code for improved readability and performance
This commit is contained in:
437
scripts/cloudflare-setup-web.py
Executable file
437
scripts/cloudflare-setup-web.py
Executable file
@@ -0,0 +1,437 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cloudflare Credentials Setup Web Interface
|
||||
Provides a web UI to configure Cloudflare API credentials
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import re
|
||||
from pathlib import Path
|
||||
from flask import Flask, render_template_string, request, jsonify, redirect, url_for
|
||||
|
||||
app = Flask(__name__)
|
||||
SCRIPT_DIR = Path(__file__).parent.parent
|
||||
ENV_FILE = SCRIPT_DIR / ".env"
|
||||
|
||||
# HTML Template
|
||||
HTML_TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cloudflare Credentials Setup</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
padding: 40px;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
font-size: 28px;
|
||||
}
|
||||
.subtitle {
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
input[type="text"], input[type="email"], input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
.help-text {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.btn {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 14px 28px;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
.btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.alert {
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.alert-success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
.alert-error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
.alert-info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
.status-section {
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.status-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
.status-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.status-label {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
.status-value {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
.status-value.valid {
|
||||
color: #28a745;
|
||||
}
|
||||
.status-value.invalid {
|
||||
color: #dc3545;
|
||||
}
|
||||
.links {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.links a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔐 Cloudflare Setup</h1>
|
||||
<p class="subtitle">Configure your Cloudflare API credentials</p>
|
||||
|
||||
{% if message %}
|
||||
<div class="alert alert-{{ message_type }}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="status-section">
|
||||
<h3 style="margin-bottom: 15px; font-size: 16px; color: #333;">Current Status</h3>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Email:</span>
|
||||
<span class="status-value {% if current_email %}valid{% else %}invalid{% endif %}">
|
||||
{{ current_email or 'Not set' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">API Key:</span>
|
||||
<span class="status-value {% if has_api_key %}valid{% else %}invalid{% endif %}">
|
||||
{{ 'Set' if has_api_key else 'Not set' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">API Token:</span>
|
||||
<span class="status-value {% if has_api_token %}valid{% else %}invalid{% endif %}">
|
||||
{{ 'Set' if has_api_token else 'Not set' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Account ID:</span>
|
||||
<span class="status-value {% if current_account_id %}valid{% else %}invalid{% endif %}">
|
||||
{{ current_account_id or 'Not set' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Zone ID:</span>
|
||||
<span class="status-value {% if current_zone_id %}valid{% else %}invalid{% endif %}">
|
||||
{{ current_zone_id or 'Not set' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Domain:</span>
|
||||
<span class="status-value {% if current_domain %}valid{% else %}invalid{% endif %}">
|
||||
{{ current_domain or 'Not set' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="/save">
|
||||
<div class="form-group">
|
||||
<label for="email">Cloudflare Email *</label>
|
||||
<input type="email" id="email" name="email" value="{{ current_email or '' }}" required>
|
||||
<div class="help-text">Your Cloudflare account email address</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="api_key">Global API Key</label>
|
||||
<input type="password" id="api_key" name="api_key" placeholder="Leave empty to keep current">
|
||||
<div class="help-text">
|
||||
<a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank">Get from Cloudflare Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="api_token">API Token (Alternative)</label>
|
||||
<input type="password" id="api_token" name="api_token" placeholder="Leave empty to keep current">
|
||||
<div class="help-text">
|
||||
<a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank">Create API Token</a> (Recommended - more secure)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="account_id">Account ID</label>
|
||||
<input type="text" id="account_id" name="account_id" value="{{ current_account_id or '' }}">
|
||||
<div class="help-text">Your Cloudflare account ID</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="zone_id">Zone ID</label>
|
||||
<input type="text" id="zone_id" name="zone_id" value="{{ current_zone_id or '' }}">
|
||||
<div class="help-text">DNS zone ID for your domain</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="domain">Domain</label>
|
||||
<input type="text" id="domain" name="domain" value="{{ current_domain or '' }}" placeholder="example.com">
|
||||
<div class="help-text">Your domain name (e.g., d-bis.org)</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="tunnel_token">Tunnel Token</label>
|
||||
<input type="password" id="tunnel_token" name="tunnel_token" placeholder="Leave empty to keep current">
|
||||
<div class="help-text">Cloudflare Tunnel service token</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn">💾 Save Credentials</button>
|
||||
</form>
|
||||
|
||||
<form method="POST" action="/test" style="margin-top: 10px;">
|
||||
<button type="submit" class="btn btn-secondary">🧪 Test API Connection</button>
|
||||
</form>
|
||||
|
||||
<div class="links">
|
||||
<a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank">Cloudflare API Tokens</a>
|
||||
<a href="https://dash.cloudflare.com/" target="_blank">Cloudflare Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
def load_env():
|
||||
"""Load current .env file values"""
|
||||
env_vars = {}
|
||||
if ENV_FILE.exists():
|
||||
with open(ENV_FILE, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#') and '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
key = key.strip()
|
||||
value = value.strip().strip('"').strip("'")
|
||||
env_vars[key] = value
|
||||
return env_vars
|
||||
|
||||
def save_env(env_vars):
|
||||
"""Save environment variables to .env file"""
|
||||
# Read existing file to preserve comments and structure
|
||||
lines = []
|
||||
if ENV_FILE.exists():
|
||||
with open(ENV_FILE, 'r') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# Update or add variables
|
||||
updated_keys = set()
|
||||
new_lines = []
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if stripped and not stripped.startswith('#') and '=' in stripped:
|
||||
key = stripped.split('=', 1)[0].strip()
|
||||
if key in env_vars:
|
||||
new_lines.append(f'{key}="{env_vars[key]}"\n')
|
||||
updated_keys.add(key)
|
||||
continue
|
||||
new_lines.append(line)
|
||||
|
||||
# Add new variables
|
||||
for key, value in env_vars.items():
|
||||
if key not in updated_keys:
|
||||
new_lines.append(f'{key}="{value}"\n')
|
||||
|
||||
with open(ENV_FILE, 'w') as f:
|
||||
f.writelines(new_lines)
|
||||
|
||||
def test_api_credentials(email, api_key, api_token):
|
||||
"""Test Cloudflare API credentials"""
|
||||
import requests
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
if api_token:
|
||||
headers["Authorization"] = f"Bearer {api_token}"
|
||||
url = "https://api.cloudflare.com/client/v4/user"
|
||||
elif api_key and email:
|
||||
headers["X-Auth-Email"] = email
|
||||
headers["X-Auth-Key"] = api_key
|
||||
url = "https://api.cloudflare.com/client/v4/user"
|
||||
else:
|
||||
return False, "No credentials provided"
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, timeout=10)
|
||||
data = response.json()
|
||||
|
||||
if data.get('success'):
|
||||
user_email = data.get('result', {}).get('email', '')
|
||||
return True, f"✓ Authentication successful! Logged in as: {user_email}"
|
||||
else:
|
||||
error = data.get('errors', [{}])[0].get('message', 'Unknown error')
|
||||
return False, f"✗ Authentication failed: {error}"
|
||||
except Exception as e:
|
||||
return False, f"✗ Connection error: {str(e)}"
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Display the setup form"""
|
||||
env = load_env()
|
||||
|
||||
return render_template_string(HTML_TEMPLATE,
|
||||
current_email=env.get('CLOUDFLARE_EMAIL', ''),
|
||||
has_api_key=bool(env.get('CLOUDFLARE_API_KEY', '')),
|
||||
has_api_token=bool(env.get('CLOUDFLARE_API_TOKEN', '')),
|
||||
current_account_id=env.get('CLOUDFLARE_ACCOUNT_ID', ''),
|
||||
current_zone_id=env.get('CLOUDFLARE_ZONE_ID', ''),
|
||||
current_domain=env.get('CLOUDFLARE_DOMAIN', ''),
|
||||
message=request.args.get('message', ''),
|
||||
message_type=request.args.get('type', 'info')
|
||||
)
|
||||
|
||||
@app.route('/save', methods=['POST'])
|
||||
def save():
|
||||
"""Save credentials to .env file"""
|
||||
env = load_env()
|
||||
|
||||
# Update only provided fields
|
||||
if request.form.get('email'):
|
||||
env['CLOUDFLARE_EMAIL'] = request.form.get('email')
|
||||
|
||||
if request.form.get('api_key'):
|
||||
env['CLOUDFLARE_API_KEY'] = request.form.get('api_key')
|
||||
|
||||
if request.form.get('api_token'):
|
||||
env['CLOUDFLARE_API_TOKEN'] = request.form.get('api_token')
|
||||
# Remove API key if token is provided
|
||||
if 'CLOUDFLARE_API_KEY' in env:
|
||||
del env['CLOUDFLARE_API_KEY']
|
||||
|
||||
if request.form.get('account_id'):
|
||||
env['CLOUDFLARE_ACCOUNT_ID'] = request.form.get('account_id')
|
||||
|
||||
if request.form.get('zone_id'):
|
||||
env['CLOUDFLARE_ZONE_ID'] = request.form.get('zone_id')
|
||||
|
||||
if request.form.get('domain'):
|
||||
env['CLOUDFLARE_DOMAIN'] = request.form.get('domain')
|
||||
|
||||
if request.form.get('tunnel_token'):
|
||||
env['CLOUDFLARE_TUNNEL_TOKEN'] = request.form.get('tunnel_token')
|
||||
|
||||
save_env(env)
|
||||
|
||||
return redirect(url_for('index', message='Credentials saved successfully!', type='success'))
|
||||
|
||||
@app.route('/test', methods=['POST'])
|
||||
def test():
|
||||
"""Test API credentials"""
|
||||
env = load_env()
|
||||
|
||||
email = request.form.get('email') or env.get('CLOUDFLARE_EMAIL', '')
|
||||
api_key = request.form.get('api_key') or env.get('CLOUDFLARE_API_KEY', '')
|
||||
api_token = request.form.get('api_token') or env.get('CLOUDFLARE_API_TOKEN', '')
|
||||
|
||||
if not email and not api_token:
|
||||
return redirect(url_for('index', message='Please provide email and API key, or API token', type='error'))
|
||||
|
||||
success, message = test_api_credentials(email, api_key, api_token)
|
||||
|
||||
return redirect(url_for('index', message=message, type='success' if success else 'error'))
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("\n" + "="*60)
|
||||
print("🌐 Cloudflare Credentials Setup Web Interface")
|
||||
print("="*60)
|
||||
print(f"\n📁 .env file location: {ENV_FILE}")
|
||||
print(f"\n🔗 Open in your browser:")
|
||||
print(f" http://localhost:5000")
|
||||
print(f" http://127.0.0.1:5000")
|
||||
print(f"\n⚠️ This server is only accessible from localhost")
|
||||
print(f" Press Ctrl+C to stop the server\n")
|
||||
print("="*60 + "\n")
|
||||
|
||||
app.run(host='127.0.0.1', port=5000, debug=False)
|
||||
|
||||
Reference in New Issue
Block a user