# app/blueprints/ui.py import os import json import asyncio from pathlib import Path from datetime import datetime from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, send_file, abort from app.utils.browser import get_browser from app.utils.enrichment import enrich_url from app.utils.settings import get_settings from app.utils.io_helpers import get_recent_results bp = Blueprint("main", __name__) settings = get_settings() app_name = settings.app.name app_version = f"v {settings.app.version_major}.{settings.app.version_minor}" # --- context processor --- @bp.context_processor def inject_app_info(): """Inject app name and version into all templates.""" return { "app_name": app_name, "app_version": app_version } @bp.route("/", methods=["GET"]) def index(): """ Render the landing page with optional 'recent_results' list. The number of recent runs is controlled via settings.cache.recent_runs_count (int). Falls back to 10 if not present or invalid. """ # Pull recent count from settings with a safe fallback try: # settings is already initialized at module import in your file recent_count = int(getattr(settings.cache, "recent_runs_count", 10)) if recent_count < 0: recent_count = 0 except Exception: recent_count = 10 # Resolve SANDBOX_STORAGE from app config storage = Path(current_app.config["SANDBOX_STORAGE"]).resolve() # Build the recent list (non-fatal if storage is empty or unreadable) recent_results = get_recent_results(storage, recent_count, current_app.logger) # Pass to template; your index.html will hide the card if list is empty return render_template("index.html", recent_results=recent_results) @bp.route("/analyze", methods=["POST"]) def analyze(): url = request.form.get("url", "").strip() current_app.logger.info(f"[*] Analyzing {url}") if not url: flash("Please enter a URL.", "error") return redirect(url_for("main.index")) storage = Path(current_app.config["SANDBOX_STORAGE"]).resolve() storage.mkdir(parents=True, exist_ok=True) try: browser = get_browser() result = asyncio.run(browser.fetch_page_artifacts(url)) current_app.logger.info(f"[+] Analysis done for {url}") except Exception as e: flash(f"Analysis failed: {e}", "error") current_app.logger.error(f"Analysis failed for {url}: {e}") return redirect(url_for("main.index")) # Add enrichment safely try: enrichment = enrich_url(url) result["enrichment"] = enrichment current_app.logger.info(f"[+] Enrichment added for {url}") except Exception as e: result["enrichment"] = {} current_app.logger.warning(f"[!] Enrichment failed for {url}: {e}") # Redirect to permalink page for this run return redirect(url_for("main.view_result", run_uuid=result["uuid"])) @bp.route("/results/", methods=["GET"]) def view_result(run_uuid: str): # Resolve SANDBOX_STORAGE from app config storage = Path(current_app.config["SANDBOX_STORAGE"]).resolve() run_dir = storage / run_uuid results_path = run_dir / "results.json" if not results_path.exists(): current_app.logger.error(f"Results not found for UUID: {run_uuid}") abort(404) with open(results_path, "r", encoding="utf-8") as f: result = json.load(f) # Pass the UUID to the template for artifact links result["uuid"] = run_uuid return render_template("result.html", **result) @bp.route("/artifacts//", methods=["GET"]) def artifacts(run_uuid: str, filename: str): # Resolve SANDBOX_STORAGE from app config storage = Path(current_app.config["SANDBOX_STORAGE"]).resolve() run_dir = storage / run_uuid full_path = run_dir / filename # Prevent directory traversal try: full_path.relative_to(run_dir.resolve()) except ValueError: current_app.logger.warning(f"Directory traversal attempt: {filename}") abort(404) if not full_path.exists(): current_app.logger.error(f"Artifact not found: {filename} for UUID {run_uuid}") abort(404) return send_file(full_path) @bp.get("/view/artifact//") def view_artifact(run_uuid, filename): # Build a safe raw URL that streams the file (you said you already have this route) raw_url = url_for('api.get_artifact_raw', run_uuid=run_uuid, filename=filename) # Optional: derive language server-side if you prefer language = None # e.g., 'javascript' return render_template('viewer.html', filename=filename, raw_url=raw_url, language=language)