324 lines
7.9 KiB
Python
324 lines
7.9 KiB
Python
"""
|
|
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
|
|
"""
|
|
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', '.yml'))]
|
|
config_files.sort()
|
|
except Exception as e:
|
|
logger.error(f"Error listing config files: {e}")
|
|
|
|
return render_template('dashboard.html', config_files=config_files)
|
|
|
|
|
|
@bp.route('/scans')
|
|
@login_required
|
|
def scans():
|
|
"""
|
|
Scans list page - shows all scans with pagination.
|
|
|
|
Returns:
|
|
Rendered scans list template
|
|
"""
|
|
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', '.yml'))]
|
|
config_files.sort()
|
|
except Exception as e:
|
|
logger.error(f"Error listing config files: {e}")
|
|
|
|
return render_template('scans.html', config_files=config_files)
|
|
|
|
|
|
@bp.route('/scans/<int:scan_id>')
|
|
@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/<int:scan_id1>/compare/<int:scan_id2>')
|
|
@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/<int:schedule_id>/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('/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/<filename>')
|
|
@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
|
|
"""
|
|
import os
|
|
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 = []
|
|
|
|
# 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', '.yml'))]
|
|
config_files.sort()
|
|
except Exception as e:
|
|
logger.error(f"Error listing config files: {e}")
|
|
|
|
return render_template(
|
|
'alert_rules.html',
|
|
rules=rules,
|
|
config_files=config_files
|
|
)
|