Files
haproxy-dashboard/app.py
Mateusz Gruszczyński addb21bc3e rewrite
2025-11-04 09:56:37 +01:00

263 lines
7.6 KiB
Python

"""
HAProxy Configurator - Main Application
SQLAlchemy + Flask-SQLAlchemy Integration
"""
import os
import sys
import ssl
import configparser
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 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'),
instance_path=INSTANCE_DIR
)
# ===== 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'
# ===== 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)
# ===== REGISTER BLUEPRINTS =====
print("[APP] Registering blueprints...", flush=True)
app.register_blueprint(main_bp)
app.register_blueprint(edit_bp)
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:
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)
except Exception as e:
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():
"""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('/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: ")
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.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__':
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
)