Files
2026-04-25 15:40:20 +02:00

131 lines
3.7 KiB
Python

"""
LLM Monitor - Dashboard to monitor Ollama models.
FastAPI application entry point.
"""
import logging
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.openapi.docs import get_redoc_html
from pathlib import Path
import os
# Logging configuration
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Import API routes
from app.api.health import router as health_router
from app.api.models import router as models_router
from app.config import settings
# Create FastAPI app
app = FastAPI(
title="LLM Monitor API",
description="Dashboard and API for monitoring Ollama LLM models",
version="1.0.0",
docs_url="/docs",
redoc_url=None,
openapi_url="/openapi.json"
)
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS.split(","),
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Register API routes
app.include_router(health_router, prefix="/api/v1", tags=["health"])
app.include_router(models_router, prefix="/api/v1", tags=["models"])
# Serve static files
static_path = Path(__file__).parent / "app" / "web" / "static"
if static_path.exists():
app.mount("/static", StaticFiles(directory=static_path), name="static")
# Serve web pages
templates_path = Path(__file__).parent / "app" / "web" / "templates"
@app.get("/")
async def root():
"""Primary page: configured servers selector and control panel."""
return FileResponse(templates_path / "servers.html")
@app.get("/servers")
async def servers_page():
"""Configured Ollama servers page."""
return FileResponse(templates_path / "servers.html")
@app.get("/dashboard")
async def dashboard():
"""Legacy alias for configured servers page."""
return FileResponse(templates_path / "servers.html")
@app.get("/models-available")
async def models_available_page():
"""Page listing models available on disk."""
return FileResponse(templates_path / "index.html")
@app.get("/models-running")
async def models_running_page():
"""Page dedicated to models resident in memory (ollama ps)."""
return FileResponse(templates_path / "models_running.html")
@app.get("/manifest.webmanifest", include_in_schema=False)
async def web_manifest():
"""PWA web manifest."""
return FileResponse(static_path / "manifest.webmanifest", media_type="application/manifest+json")
@app.get("/service-worker.js", include_in_schema=False)
async def service_worker():
"""PWA service worker with root scope."""
return FileResponse(static_path / "js" / "service-worker.js", media_type="application/javascript")
@app.get("/redoc", include_in_schema=False)
async def redoc_html():
"""ReDoc documentation using a stable bundle."""
return get_redoc_html(
openapi_url=app.openapi_url,
title=f"{app.title} - ReDoc",
redoc_js_url="https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js",
with_google_fonts=False,
)
@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
"""Application favicon."""
return FileResponse(static_path / "favicon.ico")
# Event hooks
@app.on_event("startup")
async def startup_event():
logger.info("LLM Monitor started")
logger.info(f"Ollama host: {settings.OLLAMA_HOST}")
@app.on_event("shutdown")
async def shutdown_event():
logger.info("LLM Monitor stopped")
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"main:app",
host=settings.API_HOST,
port=settings.API_PORT,
reload=settings.ENVIRONMENT == "development",
log_level=settings.LOG_LEVEL.lower()
)