""" Flask application factory and initialization """ from flask import Flask, request, jsonify, render_template import os import logging from logging.handlers import RotatingFileHandler from config.config import get_config def create_app(config_name=None): """Application factory pattern""" # Get the project root directory (parent of app directory) project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) template_dir = os.path.join(project_root, 'templates') static_dir = os.path.join(project_root, 'static') app = Flask(__name__, template_folder=template_dir, static_folder=static_dir) # Load configuration config_class = get_config(config_name) app.config.from_object(config_class) # Ensure required directories exist _ensure_directories() # Setup logging _setup_logging(app) # Register blueprints _register_blueprints(app) # Register template filters _register_template_filters(app) # Register error handlers _register_error_handlers(app) # Add context processors _register_context_processors(app) return app def _ensure_directories(): """Ensure required directories exist""" directories = [ 'data', 'data/uploads', 'data/backups', 'logs', 'ansible/inventory', 'ansible/playbooks', 'ansible/roles' ] for directory in directories: os.makedirs(directory, exist_ok=True) def _setup_logging(app): """Setup application logging""" if not app.debug and not app.testing: # File logging for production if app.config.get('LOG_FILE'): if not os.path.exists('logs'): os.mkdir('logs') file_handler = RotatingFileHandler( app.config['LOG_FILE'], maxBytes=10240000, # 10MB backupCount=10 ) file_handler.setFormatter(logging.Formatter( '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]' )) file_handler.setLevel(logging.INFO) app.logger.addHandler(file_handler) app.logger.setLevel(logging.INFO) app.logger.info('Enhanced monitoring server startup') def _register_blueprints(app): """Register all blueprints""" # Import blueprints here to avoid circular imports from app.api.logs import logs_bp from app.api.ansible import ansible_bp from app.api.wmt import wmt_api_bp from app.web.main import main_bp from app.web.ansible import ansible_web_bp from app.web.wmt import wmt_web_bp app.register_blueprint(logs_bp) app.register_blueprint(ansible_bp) app.register_blueprint(wmt_api_bp) app.register_blueprint(main_bp) app.register_blueprint(ansible_web_bp) app.register_blueprint(wmt_web_bp) # Add compatibility routes for old clients @app.route('/logs', methods=['POST']) def compatibility_logs(): """Compatibility endpoint for old prezenta clients""" from flask import request, redirect, url_for import re # Forward the request to the new API endpoint # Import inside function to avoid circular imports from app.api.logs import submit_log return submit_log() def _register_template_filters(app): """Register custom Jinja2 template filters.""" import calendar from datetime import datetime as _dt @app.template_filter('local_dt') def local_dt_filter(value, fmt='%Y-%m-%d %H:%M'): """Convert a naive UTC datetime to server local time and format it. Usage: {{ some_utc_datetime | local_dt }} {{ some_utc_datetime | local_dt('%Y-%m-%d %H:%M:%S') }} """ if value is None: return None try: # calendar.timegm treats the timetuple as UTC → returns POSIX timestamp # datetime.fromtimestamp then converts to local time using the system timezone ts = calendar.timegm(value.timetuple()) return _dt.fromtimestamp(ts).strftime(fmt) except Exception: return value.strftime(fmt) def _register_error_handlers(app): """Register error handlers""" @app.errorhandler(400) def bad_request(error): if request.is_json: return jsonify({ 'error': 'Bad request', 'message': 'The request could not be understood by the server' }), 400 return render_template('errors/400.html'), 400 @app.errorhandler(401) def unauthorized(error): if request.is_json: return jsonify({ 'error': 'Unauthorized', 'message': 'Authentication required' }), 401 return render_template('errors/401.html'), 401 @app.errorhandler(403) def forbidden(error): if request.is_json: return jsonify({ 'error': 'Forbidden', 'message': 'Insufficient permissions' }), 403 return render_template('errors/403.html'), 403 @app.errorhandler(404) def not_found(error): if request.is_json: return jsonify({ 'error': 'Not found', 'message': 'The requested resource was not found' }), 404 try: return render_template('errors/404.html'), 404 except Exception as e: # Fallback if template cannot be loaded app.logger.error(f'Error loading 404 template: {e}') return ''' 404 - Page Not Found

404 - Page Not Found

The requested resource was not found.

← Back to Dashboard ''', 404 @app.errorhandler(500) def internal_error(error): app.logger.error(f'Internal server error: {error}') if request.is_json: return jsonify({ 'error': 'Internal server error', 'message': 'An unexpected error occurred' }), 500 try: return render_template('errors/500.html'), 500 except Exception as e: app.logger.error(f'Error loading 500 template: {e}') return ''' 500 - Internal Server Error

500 - Internal Server Error

An unexpected error occurred.

← Back to Dashboard ''', 500 def _register_context_processors(app): """Register template context processors""" @app.context_processor def inject_config(): from app.models import WMTUpdateRequest from config.database_config import get_db try: with get_db().get_session() as session: pending_wmt_count = session.query(WMTUpdateRequest).filter_by(status='pending').count() except Exception: pending_wmt_count = 0 return { 'app_name': 'Enhanced Server Monitoring', 'app_version': '2.0.0', 'pending_wmt_count': pending_wmt_count, }