rewrite
This commit is contained in:
376
app.py
376
app.py
@@ -1,196 +1,262 @@
|
||||
"""
|
||||
HAProxy Configurator - Main Application
|
||||
SQLAlchemy + Flask-SQLAlchemy Integration
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import ssl
|
||||
import configparser
|
||||
from flask import Flask, render_template, render_template_string, request, jsonify
|
||||
from datetime import timedelta
|
||||
from flask import Flask, render_template, redirect, url_for, session
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
from config.settings import *
|
||||
from database import db, migrate, init_db
|
||||
from routes.main_routes import main_bp
|
||||
from routes.edit_routes import edit_bp
|
||||
from routes.auth_routes import auth_bp
|
||||
from routes.user_routes import user_bp
|
||||
from auth.auth_middleware import setup_auth, login_required
|
||||
from utils.stats_utils import fetch_haproxy_stats, parse_haproxy_stats
|
||||
from auth.auth_middleware import setup_auth
|
||||
from log_parser import parse_log_file
|
||||
from utils.haproxy_config import update_haproxy_config, count_frontends_and_backends
|
||||
from routes.vhost_routes import vhost_bp
|
||||
from routes.cert_routes import cert_bp
|
||||
|
||||
|
||||
# ===== BASE DIRECTORY =====
|
||||
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
# ===== CREATE REQUIRED DIRECTORIES =====
|
||||
INSTANCE_DIR = os.path.join(BASE_DIR, 'instance')
|
||||
os.makedirs(INSTANCE_DIR, exist_ok=True)
|
||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
||||
os.makedirs(HAPROXY_BACKUP_DIR, exist_ok=True)
|
||||
|
||||
print(f"[APP] Base directory: {BASE_DIR}", flush=True)
|
||||
print(f"[APP] Instance directory: {INSTANCE_DIR}", flush=True)
|
||||
print(f"[APP] Database: {SQLALCHEMY_DATABASE_URI}", flush=True)
|
||||
|
||||
|
||||
# ===== CREATE FLASK APP =====
|
||||
app = Flask(
|
||||
__name__,
|
||||
static_folder=os.path.join(BASE_DIR, 'static'),
|
||||
static_url_path='/static',
|
||||
template_folder=os.path.join(BASE_DIR, 'templates')
|
||||
template_folder=os.path.join(BASE_DIR, 'templates'),
|
||||
instance_path=INSTANCE_DIR
|
||||
)
|
||||
|
||||
CONFIG_DIR_DOCKER = '/etc/haproxy-configurator'
|
||||
CONFIG_DIR_LOCAL = './config'
|
||||
CONFIG_DIR_ENV = os.environ.get('CONFIG_DIR', None)
|
||||
|
||||
if CONFIG_DIR_ENV and os.path.exists(CONFIG_DIR_ENV):
|
||||
CONFIG_DIR = CONFIG_DIR_ENV
|
||||
elif os.path.exists(CONFIG_DIR_DOCKER):
|
||||
CONFIG_DIR = CONFIG_DIR_DOCKER
|
||||
elif os.path.exists(CONFIG_DIR_LOCAL):
|
||||
CONFIG_DIR = CONFIG_DIR_LOCAL
|
||||
else:
|
||||
CONFIG_DIR = CONFIG_DIR_DOCKER
|
||||
# ===== LOAD CONFIGURATION =====
|
||||
app.config.from_object('config.settings')
|
||||
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
|
||||
app.config['SESSION_COOKIE_SECURE'] = True
|
||||
app.config['SESSION_COOKIE_HTTPONLY'] = True
|
||||
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
|
||||
|
||||
AUTH_CFG = os.path.join(CONFIG_DIR, 'auth', 'auth.cfg')
|
||||
SSL_INI = os.path.join(CONFIG_DIR, 'ssl.ini')
|
||||
|
||||
os.makedirs(os.path.dirname(AUTH_CFG), exist_ok=True)
|
||||
os.makedirs(os.path.dirname(SSL_INI), exist_ok=True)
|
||||
# ===== INITIALIZE DATABASE =====
|
||||
print("[APP] Initializing database...", flush=True)
|
||||
db.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
init_db(app)
|
||||
print("[APP] Database initialized", flush=True)
|
||||
|
||||
BASIC_AUTH_USERNAME = "admin"
|
||||
BASIC_AUTH_PASSWORD = "admin"
|
||||
|
||||
try:
|
||||
auth_config = configparser.ConfigParser()
|
||||
auth_config.read(AUTH_CFG)
|
||||
if auth_config.has_section('auth'):
|
||||
BASIC_AUTH_USERNAME = auth_config.get('auth', 'username', fallback='admin')
|
||||
BASIC_AUTH_PASSWORD = auth_config.get('auth', 'password', fallback='admin')
|
||||
else:
|
||||
BASIC_AUTH_USERNAME = "admin"
|
||||
BASIC_AUTH_PASSWORD = "admin"
|
||||
except Exception as e:
|
||||
print(f"[APP] Auth config error: {e}, using defaults", flush=True)
|
||||
BASIC_AUTH_USERNAME = "admin"
|
||||
BASIC_AUTH_PASSWORD = "admin"
|
||||
|
||||
# ===== REGISTER BLUEPRINTS =====
|
||||
print("[APP] Registering blueprints...", flush=True)
|
||||
app.register_blueprint(main_bp)
|
||||
app.register_blueprint(edit_bp)
|
||||
setup_auth(app)
|
||||
app.register_blueprint(auth_bp)
|
||||
app.register_blueprint(user_bp)
|
||||
app.register_blueprint(vhost_bp)
|
||||
app.register_blueprint(cert_bp)
|
||||
|
||||
print("[APP] Blueprints registered", flush=True)
|
||||
|
||||
|
||||
# ===== SETUP AUTHENTICATION MIDDLEWARE =====
|
||||
print("[APP] Setting up auth middleware...", flush=True)
|
||||
setup_auth(app)
|
||||
print("[APP] Auth middleware setup complete", flush=True)
|
||||
|
||||
|
||||
# ===== SSL CONTEXT SETUP =====
|
||||
certificate_path = None
|
||||
private_key_path = None
|
||||
ssl_context = None
|
||||
|
||||
try:
|
||||
config2 = configparser.ConfigParser()
|
||||
config2.read(SSL_INI)
|
||||
if config2.has_section('ssl'):
|
||||
certificate_path = config2.get('ssl', 'certificate_path')
|
||||
private_key_path = config2.get('ssl', 'private_key_path')
|
||||
config_ssl = configparser.ConfigParser()
|
||||
config_ssl.read(SSL_INI)
|
||||
|
||||
if config_ssl.has_section('ssl'):
|
||||
certificate_path = config_ssl.get('ssl', 'certificate_path')
|
||||
private_key_path = config_ssl.get('ssl', 'private_key_path')
|
||||
|
||||
if os.path.exists(certificate_path) and os.path.exists(private_key_path):
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||
ssl_context.load_cert_chain(certfile=certificate_path, keyfile=private_key_path)
|
||||
print("[APP] SSL context loaded successfully", flush=True)
|
||||
else:
|
||||
print(f"[APP] SSL certificate files not found", flush=True)
|
||||
print(f" Certificate: {certificate_path}", flush=True)
|
||||
print(f" Private Key: {private_key_path}", flush=True)
|
||||
else:
|
||||
print(f"[APP] No [ssl] section in {SSL_INI}", flush=True)
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.exists(certificate_path):
|
||||
print(f"[APP] Certificate not found: {certificate_path}", flush=True)
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.exists(private_key_path):
|
||||
print(f"[APP] Private key not found: {private_key_path}", flush=True)
|
||||
sys.exit(1)
|
||||
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||
ssl_context.load_cert_chain(certfile=certificate_path, keyfile=private_key_path)
|
||||
print(f"[APP] SSL context loaded", flush=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[APP] SSL error: {e}", flush=True)
|
||||
sys.exit(1)
|
||||
print(f"[APP] SSL warning (non-critical): {e}", flush=True)
|
||||
|
||||
|
||||
# ===== ROUTES =====
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Index/Home - Redirect to login or dashboard"""
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
return redirect(url_for('main.index'))
|
||||
|
||||
|
||||
@app.route('/statistics')
|
||||
@login_required
|
||||
def display_haproxy_stats():
|
||||
haproxy_stats = fetch_haproxy_stats()
|
||||
parsed_stats = parse_haproxy_stats(haproxy_stats)
|
||||
return render_template('statistics.html', stats=parsed_stats)
|
||||
"""Display HAProxy statistics"""
|
||||
try:
|
||||
haproxy_stats = fetch_haproxy_stats()
|
||||
parsed_stats = parse_haproxy_stats(haproxy_stats)
|
||||
return render_template('statistics.html', stats=parsed_stats)
|
||||
except Exception as e:
|
||||
print(f"[STATS] Error: {e}", flush=True)
|
||||
return render_template('statistics.html', stats={}, error=str(e))
|
||||
|
||||
@app.route('/logs', endpoint='display_logs')
|
||||
def display_haproxy_logs():
|
||||
log_file_path = '/var/log/haproxy.log'
|
||||
if not os.path.exists(log_file_path):
|
||||
return render_template('logs.html',
|
||||
logs=[],
|
||||
total_logs=0,
|
||||
error_message=f"Log file not found: {log_file_path}")
|
||||
|
||||
@app.route('/dashboard')
|
||||
@login_required
|
||||
def dashboard():
|
||||
"""Dashboard - Overview of vhosts and status"""
|
||||
from database.models import VirtualHost
|
||||
try:
|
||||
vhosts = VirtualHost.query.all()
|
||||
return render_template('dashboard.html', vhosts=vhosts)
|
||||
except Exception as e:
|
||||
print(f"[DASHBOARD] Error: {e}", flush=True)
|
||||
return render_template('dashboard.html', vhosts=[], error=str(e))
|
||||
|
||||
|
||||
# ===== ERROR HANDLERS =====
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(error):
|
||||
"""404 error handler"""
|
||||
return render_template('404.html'), 404
|
||||
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_error(error):
|
||||
"""500 error handler"""
|
||||
db.session.rollback()
|
||||
return render_template('500.html'), 500
|
||||
|
||||
|
||||
@app.errorhandler(403)
|
||||
def forbidden(error):
|
||||
"""403 error handler"""
|
||||
return render_template('403.html'), 403
|
||||
|
||||
|
||||
# ===== SHELL CONTEXT =====
|
||||
|
||||
@app.shell_context_processor
|
||||
def make_shell_context():
|
||||
"""Add models to Flask shell context"""
|
||||
from database.models import User, Certificate, VirtualHost, BackendServer, ConfigHistory
|
||||
return {
|
||||
'db': db,
|
||||
'User': User,
|
||||
'Certificate': Certificate,
|
||||
'VirtualHost': VirtualHost,
|
||||
'BackendServer': BackendServer,
|
||||
'ConfigHistory': ConfigHistory
|
||||
}
|
||||
|
||||
|
||||
# ===== APPLICATION CONTEXT =====
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
"""Run before each request"""
|
||||
pass
|
||||
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
"""Run after each request"""
|
||||
return response
|
||||
|
||||
|
||||
# ===== CLI COMMANDS =====
|
||||
|
||||
@app.cli.command()
|
||||
def init_db_cli():
|
||||
"""Initialize database"""
|
||||
with app.app_context():
|
||||
from database import init_db
|
||||
init_db(app)
|
||||
print("[CLI] Database initialized successfully")
|
||||
|
||||
|
||||
@app.cli.command()
|
||||
def create_admin():
|
||||
"""Create admin user"""
|
||||
import getpass
|
||||
username = input("Username: ")
|
||||
password = getpass.getpass("Password: ")
|
||||
|
||||
try:
|
||||
logs = parse_log_file(log_file_path)
|
||||
total_logs = len(logs)
|
||||
# Załaduj ostatnie 200 logów
|
||||
initial_logs = logs[-200:] if len(logs) > 200 else logs
|
||||
|
||||
return render_template('logs.html',
|
||||
logs=initial_logs,
|
||||
total_logs=total_logs,
|
||||
loaded_count=len(initial_logs))
|
||||
except Exception as e:
|
||||
return render_template('logs.html',
|
||||
logs=[],
|
||||
total_logs=0,
|
||||
error_message=f"Error parsing logs: {str(e)}")
|
||||
|
||||
@app.route('/api/logs', methods=['POST'])
|
||||
def api_get_logs():
|
||||
"""API endpoint for paginated and filtered logs"""
|
||||
try:
|
||||
log_file_path = '/var/log/haproxy.log'
|
||||
|
||||
if not os.path.exists(log_file_path):
|
||||
return jsonify({'error': 'Log file not found', 'success': False}), 404
|
||||
|
||||
page = request.json.get('page', 1)
|
||||
per_page = request.json.get('per_page', 50)
|
||||
search_query = request.json.get('search', '').lower()
|
||||
exclude_phrases = request.json.get('exclude', [])
|
||||
|
||||
if page < 1:
|
||||
page = 1
|
||||
if per_page < 1 or per_page > 500:
|
||||
per_page = 50
|
||||
|
||||
print(f"[API] page={page}, per_page={per_page}, search={search_query}, exclude={len(exclude_phrases)}", flush=True)
|
||||
|
||||
# Parse all logs
|
||||
all_logs = parse_log_file(log_file_path)
|
||||
total_logs = len(all_logs)
|
||||
|
||||
# Reverse to show newest first
|
||||
all_logs = all_logs[::-1]
|
||||
|
||||
# Apply filters
|
||||
filtered_logs = all_logs
|
||||
|
||||
if search_query:
|
||||
filtered_logs = [log for log in filtered_logs if search_query in
|
||||
f"{log.get('timestamp', '')} {log.get('ip_address', '')} {log.get('http_method', '')} {log.get('requested_url', '')}".lower()]
|
||||
|
||||
if exclude_phrases:
|
||||
filtered_logs = [log for log in filtered_logs if not any(
|
||||
phrase in f"{log.get('message', '')}" for phrase in exclude_phrases
|
||||
)]
|
||||
|
||||
total_filtered = len(filtered_logs)
|
||||
|
||||
# Paginate
|
||||
offset = (page - 1) * per_page
|
||||
paginated_logs = filtered_logs[offset:offset + per_page]
|
||||
|
||||
print(f"[API] total={total_logs}, filtered={total_filtered}, returned={len(paginated_logs)}", flush=True)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'logs': paginated_logs,
|
||||
'page': page,
|
||||
'per_page': per_page,
|
||||
'total': total_logs,
|
||||
'total_filtered': total_filtered,
|
||||
'loaded_count': len(paginated_logs),
|
||||
'has_more': offset + per_page < total_filtered
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[API] Error: {e}", flush=True)
|
||||
return jsonify({'error': str(e), 'success': False}), 500
|
||||
from database.models import User
|
||||
|
||||
if User.query.filter_by(username=username).first():
|
||||
print(f"Error: User '{username}' already exists")
|
||||
return
|
||||
|
||||
user = User(username=username, is_admin=True)
|
||||
user.set_password(password)
|
||||
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
print(f"[CLI] Admin user '{username}' created successfully")
|
||||
|
||||
|
||||
@app.route('/home')
|
||||
def home():
|
||||
frontend_count, backend_count, acl_count, layer7_count, layer4_count = count_frontends_and_backends()
|
||||
return render_template('home.html',
|
||||
frontend_count=frontend_count,
|
||||
backend_count=backend_count,
|
||||
acl_count=acl_count,
|
||||
layer7_count=layer7_count,
|
||||
layer4_count=layer4_count)
|
||||
@app.cli.command()
|
||||
def import_config():
|
||||
"""Import existing haproxy.cfg to database"""
|
||||
from database.migration import parse_existing_haproxy_config
|
||||
|
||||
config_path = HAPROXY_CONFIG_PATH
|
||||
count = parse_existing_haproxy_config(config_path)
|
||||
print(f"[CLI] Successfully imported {count} vhosts from {config_path}")
|
||||
|
||||
|
||||
# ===== MAIN ENTRY POINT =====
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='::', port=5000, ssl_context=ssl_context, debug=True)
|
||||
print("""
|
||||
╔════════════════════════════════════════╗
|
||||
║ HAProxy Configurator & Manager ║
|
||||
║ Starting Application... ║
|
||||
╚════════════════════════════════════════╝
|
||||
""", flush=True)
|
||||
|
||||
print(f"[APP] Environment: {'Development' if DEBUG else 'Production'}", flush=True)
|
||||
print(f"[APP] Running on: https://[::]:5000 (IPv6)", flush=True)
|
||||
|
||||
app.run(
|
||||
host='::',
|
||||
port=5000,
|
||||
ssl_context=ssl_context,
|
||||
debug=DEBUG,
|
||||
use_reloader=DEBUG
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user