mirror of
https://github.com/msoedov/agentic_security.git
synced 2026-07-05 03:07:49 +02:00
186 lines
5.4 KiB
Python
186 lines
5.4 KiB
Python
from pathlib import Path
|
|
|
|
import requests
|
|
from fastapi import APIRouter, HTTPException, Request
|
|
from fastapi.responses import FileResponse, HTMLResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
from jinja2 import Environment, FileSystemLoader
|
|
from starlette.responses import Response
|
|
|
|
from ..primitives import Settings
|
|
|
|
router = APIRouter()
|
|
STATIC_DIR = Path(__file__).parent.parent / "static"
|
|
ICONS_DIR = STATIC_DIR / "icons"
|
|
|
|
# Configure templates with custom delimiters to avoid conflicts
|
|
templates = Jinja2Templates(directory=str(STATIC_DIR))
|
|
templates.env = Environment(
|
|
loader=FileSystemLoader(str(STATIC_DIR)),
|
|
autoescape=True,
|
|
block_start_string="[[%",
|
|
block_end_string="%]]",
|
|
variable_start_string="[[",
|
|
variable_end_string="]]",
|
|
)
|
|
|
|
# Content type mapping for static files
|
|
CONTENT_TYPES = {
|
|
".js": "application/javascript",
|
|
".ico": "image/x-icon",
|
|
".html": "text/html",
|
|
".css": "text/css",
|
|
".svg": "image/svg+xml",
|
|
".png": "image/png",
|
|
}
|
|
|
|
|
|
def get_static_file(filepath: Path, content_type: str | None = None) -> FileResponse:
|
|
"""
|
|
Helper function to serve static files with proper error handling and caching.
|
|
|
|
Args:
|
|
filepath: Path to the static file
|
|
content_type: Optional content type override
|
|
|
|
Returns:
|
|
FileResponse with appropriate headers
|
|
|
|
Raises:
|
|
HTTPException if file not found
|
|
"""
|
|
if not filepath.is_file():
|
|
raise HTTPException(status_code=404, detail="File not found")
|
|
|
|
headers = {
|
|
"Cache-Control": "public, max-age=3600",
|
|
"Content-Type": content_type
|
|
or CONTENT_TYPES.get(filepath.suffix, "application/octet-stream"),
|
|
}
|
|
|
|
return FileResponse(filepath, headers=headers)
|
|
|
|
|
|
@router.get("/", response_class=HTMLResponse)
|
|
async def root(request: Request) -> Response:
|
|
"""Serve the main index.html template."""
|
|
return templates.TemplateResponse("index.html", {"request": request})
|
|
|
|
|
|
@router.get("/main.js")
|
|
async def main_js() -> FileResponse:
|
|
"""Serve the main JavaScript file."""
|
|
return get_static_file(STATIC_DIR / "main.js")
|
|
|
|
|
|
@router.get("/base.js")
|
|
async def base_js() -> FileResponse:
|
|
"""Serve the base JavaScript file."""
|
|
return get_static_file(STATIC_DIR / "base.js")
|
|
|
|
|
|
@router.get("/telemetry.js")
|
|
async def telemetry_js() -> FileResponse:
|
|
"""
|
|
Serve either telemetry.js or telemetry_disabled.js based on settings.
|
|
"""
|
|
filename = "telemetry_disabled.js" if Settings.DISABLE_TELEMETRY else "telemetry.js"
|
|
return get_static_file(STATIC_DIR / filename)
|
|
|
|
|
|
@router.get("/favicon.ico")
|
|
async def favicon() -> FileResponse:
|
|
"""Serve the favicon."""
|
|
return get_static_file(STATIC_DIR / "favicon.ico")
|
|
|
|
|
|
@router.get("/icons/{icon_name}")
|
|
async def serve_icon(icon_name: str) -> FileResponse:
|
|
"""Serve an icon from the icons directory."""
|
|
icon_path = ICONS_DIR / icon_name
|
|
if not icon_path.exists():
|
|
# Fetch the icon from the external URL and cache it
|
|
url = f"https://registry.npmmirror.com/@lobehub/icons-static-png/latest/files/dark/{icon_name}"
|
|
response = requests.get(url)
|
|
if response.status_code == 200:
|
|
icon_path.write_bytes(response.content)
|
|
else:
|
|
raise HTTPException(status_code=404, detail="Icon not found")
|
|
|
|
return get_static_file(icon_path, content_type="image/png")
|
|
|
|
|
|
# New endpoints for proxying external resources
|
|
@router.get("/cdn/tailwindcss.js")
|
|
async def proxy_tailwindcss() -> FileResponse:
|
|
"""Proxy the Tailwind CSS script."""
|
|
return proxy_external_resource(
|
|
"https://cdn.tailwindcss.com",
|
|
STATIC_DIR / "tailwindcss.js",
|
|
"application/javascript",
|
|
)
|
|
|
|
|
|
@router.get("/cdn/vue.js")
|
|
async def proxy_vue() -> FileResponse:
|
|
"""Proxy the Vue.js script."""
|
|
return proxy_external_resource(
|
|
"https://unpkg.com/vue@2.6.12/dist/vue.js",
|
|
STATIC_DIR / "vue.js",
|
|
"application/javascript",
|
|
)
|
|
|
|
|
|
@router.get("/cdn/lucide.js")
|
|
async def proxy_lucide() -> FileResponse:
|
|
"""Proxy the Lucide.js script."""
|
|
return proxy_external_resource(
|
|
"https://unpkg.com/lucide@latest/dist/umd/lucide.js",
|
|
STATIC_DIR / "lucide.js",
|
|
"application/javascript",
|
|
)
|
|
|
|
|
|
@router.get("/cdn/technopollas.css")
|
|
async def proxy_technopollas() -> FileResponse:
|
|
"""Proxy the Technopollas font stylesheet."""
|
|
return proxy_external_resource(
|
|
"https://fonts.cdnfonts.com/css/technopollas",
|
|
STATIC_DIR / "technopollas.css",
|
|
"text/css",
|
|
)
|
|
|
|
|
|
@router.get("/cdn/inter.css")
|
|
async def proxy_inter() -> FileResponse:
|
|
"""Proxy the Inter font stylesheet."""
|
|
return proxy_external_resource(
|
|
"https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap",
|
|
STATIC_DIR / "inter.css",
|
|
"text/css",
|
|
)
|
|
|
|
|
|
def proxy_external_resource(
|
|
url: str, local_path: Path, content_type: str
|
|
) -> FileResponse:
|
|
"""
|
|
Fetch and cache an external resource, then serve it locally.
|
|
|
|
Args:
|
|
url: The URL of the external resource
|
|
local_path: The local path to cache the resource
|
|
content_type: The content type of the resource
|
|
|
|
Returns:
|
|
FileResponse with the cached resource
|
|
"""
|
|
if not local_path.exists():
|
|
response = requests.get(url)
|
|
if response.status_code == 200:
|
|
local_path.write_bytes(response.content)
|
|
else:
|
|
raise HTTPException(status_code=404, detail="Resource not found")
|
|
|
|
return get_static_file(local_path, content_type=content_type)
|