from flask import Flask, render_template, redirect, url_for, request, flash, jsonify from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user from werkzeug.security import check_password_hash import yaml import logging from apscheduler.schedulers.background import BackgroundScheduler from datetime import datetime import os from database import Database from monitor import OPNsenseMonitor from email_handler import EmailHandler # Load version with open('VERSION', 'r') as f: VERSION = f.read().strip() # Load configuration with open('config/config.yaml', 'r') as f: config = yaml.safe_load(f) # Setup logging from logging.handlers import RotatingFileHandler import os # Create logs directory if it doesn't exist logs_dir = os.path.dirname(config['logging']['file']) if logs_dir and not os.path.exists(logs_dir): os.makedirs(logs_dir, exist_ok=True) # Main log file with rotation main_handler = RotatingFileHandler( config['logging']['file'], maxBytes=config['logging'].get('max_bytes', 10 * 1024 * 1024), # 10MB default backupCount=config['logging'].get('backup_count', 5) ) main_handler.setLevel(getattr(logging, config['logging']['level'])) main_handler.setFormatter(logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )) # Error log file (separate file for errors only) error_handler = RotatingFileHandler( config['logging']['error_file'], maxBytes=config['logging'].get('max_bytes', 10 * 1024 * 1024), backupCount=config['logging'].get('backup_count', 5) ) error_handler.setLevel(logging.ERROR) error_handler.setFormatter(logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s - [%(pathname)s:%(lineno)d]' )) # Console handler console_handler = logging.StreamHandler() console_handler.setLevel(getattr(logging, config['logging']['level'])) console_handler.setFormatter(logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' )) # Configure root logger logging.basicConfig( level=logging.DEBUG, # Capture everything, handlers filter handlers=[main_handler, error_handler, console_handler] ) logger = logging.getLogger(__name__) # Initialize Flask app = Flask(__name__) app.config['SECRET_KEY'] = config['web']['secret_key'] # Initialize Flask-Login login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = 'login' # Initialize components db = Database(config['database']['path']) email_handler = EmailHandler(config['email']) if config['email']['enabled'] else None monitor = OPNsenseMonitor(config, db, email_handler) # Simple User class class User(UserMixin): def __init__(self, id): self.id = id @login_manager.user_loader def load_user(user_id): return User(user_id) # Routes @app.route('/') @login_required def index(): return redirect(url_for('dashboard')) @app.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('dashboard')) if request.method == 'POST': username = request.form.get('username') password = request.form.get('password') # Simple authentication (username: admin) if username == 'admin' and check_password_hash( config['web']['admin_password_hash'], password ): user = User('admin') login_user(user) logger.info(f"User {username} logged in successfully") return redirect(url_for('dashboard')) else: logger.warning(f"Failed login attempt for user: {username}") flash('Invalid username or password', 'danger') return render_template('login.html') @app.route('/logout') @login_required def logout(): logger.info(f"User {current_user.id} logged out") logout_user() return redirect(url_for('login')) @app.route('/dashboard') @login_required def dashboard(): # Get recent events events = db.get_recent_events(limit=100) stats = db.get_statistics() return render_template('dashboard.html', events=events, stats=stats, config=config) @app.route('/api/events') @login_required def api_events(): """API endpoint for real-time event updates""" limit = request.args.get('limit', 50, type=int) event_type = request.args.get('type', None) interface = request.args.get('interface', None) events = db.get_recent_events(limit=limit, event_type=event_type, interface=interface) return jsonify(events) @app.route('/api/stats') @login_required def api_stats(): """API endpoint for statistics""" return jsonify(db.get_statistics()) @app.route('/health') def health(): """Health check endpoint""" return jsonify({ 'status': 'healthy', 'timestamp': datetime.now().isoformat(), 'version': VERSION }) # Initialize scheduler def start_monitoring(): scheduler = BackgroundScheduler() interval = config['monitoring']['polling_interval'] scheduler.add_job( func=monitor.check_all, trigger="interval", seconds=interval, id='opnsense_monitor', name='OPNsense Monitor', replace_existing=True ) scheduler.start() logger.info(f"Monitoring started with {interval}s interval") # Send startup notification if email_handler and config['email']['send_on_startup']: email_handler.send_startup_notification() if __name__ == '__main__': logger.info(f"Starting Watchdog Docker v{VERSION}") # Initialize database db.initialize() # Start monitoring start_monitoring() # Run Flask app.run( host=config['web']['host'], port=config['web']['port'], debug=False )