""" Main web routes for SneakyScanner. Provides dashboard and scan viewing pages. """ import logging from flask import Blueprint, current_app, redirect, render_template, url_for from web.auth.decorators import login_required logger = logging.getLogger(__name__) bp = Blueprint('main', __name__) @bp.route('/') def index(): """ Root route - redirect to dashboard. Returns: Redirect to dashboard """ return redirect(url_for('main.dashboard')) @bp.route('/dashboard') @login_required def dashboard(): """ Dashboard page - shows recent scans and statistics. Returns: Rendered dashboard template """ return render_template('dashboard.html') @bp.route('/scans') @login_required def scans(): """ Scans list page - shows all scans with pagination. Returns: Rendered scans list template """ return render_template('scans.html') @bp.route('/scans/') @login_required def scan_detail(scan_id): """ Scan detail page - shows full scan results. Args: scan_id: Scan ID to display Returns: Rendered scan detail template """ # TODO: Phase 5 - Implement scan detail page return render_template('scan_detail.html', scan_id=scan_id) @bp.route('/scans//compare/') @login_required def compare_scans(scan_id1, scan_id2): """ Scan comparison page - shows differences between two scans. Args: scan_id1: First (older) scan ID scan_id2: Second (newer) scan ID Returns: Rendered comparison template """ return render_template('scan_compare.html', scan_id1=scan_id1, scan_id2=scan_id2) @bp.route('/schedules') @login_required def schedules(): """ Schedules list page - shows all scheduled scans. Returns: Rendered schedules list template """ return render_template('schedules.html') @bp.route('/schedules/create') @login_required def create_schedule(): """ Create new schedule form page. Returns: Rendered schedule create template with available config files """ import os # Get list of available config files configs_dir = '/app/configs' config_files = [] try: if os.path.exists(configs_dir): config_files = [f for f in os.listdir(configs_dir) if f.endswith('.yaml')] config_files.sort() except Exception as e: logger.error(f"Error listing config files: {e}") return render_template('schedule_create.html', config_files=config_files) @bp.route('/schedules//edit') @login_required def edit_schedule(schedule_id): """ Edit existing schedule form page. Args: schedule_id: Schedule ID to edit Returns: Rendered schedule edit template """ from flask import flash # Note: Schedule data is loaded via AJAX in the template # This just renders the page with the schedule_id in the URL return render_template('schedule_edit.html', schedule_id=schedule_id) @bp.route('/sites') @login_required def sites(): """ Sites management page - manage reusable site definitions. Returns: Rendered sites template """ return render_template('sites.html') @bp.route('/configs') @login_required def configs(): """ Configuration files list page - shows all config files. Returns: Rendered configs list template """ return render_template('configs.html') @bp.route('/configs/upload') @login_required def upload_config(): """ Config upload page - allows CIDR/YAML upload. Returns: Rendered config upload template """ return render_template('config_upload.html') @bp.route('/configs/edit/') @login_required def edit_config(filename): """ Config edit page - allows editing YAML configuration. Args: filename: Config filename to edit Returns: Rendered config edit template """ from web.services.config_service import ConfigService from flask import flash, redirect try: config_service = ConfigService() config_data = config_service.get_config(filename) return render_template( 'config_edit.html', filename=config_data['filename'], content=config_data['content'] ) except FileNotFoundError: flash(f"Config file '{filename}' not found", 'error') return redirect(url_for('main.configs')) except Exception as e: logger.error(f"Error loading config for edit: {e}") flash(f"Error loading config: {str(e)}", 'error') return redirect(url_for('main.configs')) @bp.route('/alerts') @login_required def alerts(): """ Alerts history page - shows all alerts. Returns: Rendered alerts template """ from flask import request, current_app from web.models import Alert, AlertRule, Scan from web.utils.pagination import paginate # Get query parameters for filtering page = request.args.get('page', 1, type=int) per_page = 20 severity = request.args.get('severity') alert_type = request.args.get('alert_type') acknowledged = request.args.get('acknowledged') # Build query query = current_app.db_session.query(Alert).join(Scan, isouter=True) # Apply filters if severity: query = query.filter(Alert.severity == severity) if alert_type: query = query.filter(Alert.alert_type == alert_type) if acknowledged is not None: ack_bool = acknowledged == 'true' query = query.filter(Alert.acknowledged == ack_bool) # Order by severity and date query = query.order_by(Alert.severity.desc(), Alert.created_at.desc()) # Paginate using utility function pagination = paginate(query, page=page, per_page=per_page) alerts = pagination.items # Get unique alert types for filter dropdown try: alert_types = current_app.db_session.query(Alert.alert_type).distinct().all() alert_types = [at[0] for at in alert_types] if alert_types else [] except Exception: alert_types = [] return render_template( 'alerts.html', alerts=alerts, pagination=pagination, current_severity=severity, current_alert_type=alert_type, current_acknowledged=acknowledged, alert_types=alert_types ) @bp.route('/alerts/rules') @login_required def alert_rules(): """ Alert rules management page. Returns: Rendered alert rules template """ from flask import current_app from web.models import AlertRule # Get all alert rules with error handling try: rules = current_app.db_session.query(AlertRule).order_by( AlertRule.name.nullslast(), AlertRule.rule_type ).all() except Exception as e: logger.error(f"Error fetching alert rules: {e}") rules = [] # Ensure rules is always a list if rules is None: rules = [] return render_template( 'alert_rules.html', rules=rules )