diff --git a/PRD.md b/PRD.md index 54ba88a..81daa41 100644 --- a/PRD.md +++ b/PRD.md @@ -188,10 +188,15 @@ Dettagli di un modello specifico ``` #### `POST /api/v1/models/{model_name}/pull` -Scarica/carica un modello +Scarica/carica un modello (**disabilitato di default**) #### `DELETE /api/v1/models/{model_name}` -Elimina un modello +Elimina un modello (**disabilitato di default**) + +#### Policy endpoint R/W +- Gli endpoint `POST/DELETE` sono **non disponibili** per default. +- Si abilitano solo con variabile ambiente `ENABLE_MODEL_RW_API=true`. +- Se non abilitati, gli endpoint non sono esposti in Swagger e rispondono con `404`. --- @@ -244,7 +249,7 @@ Elimina un modello **Componenti:** - Dockerfile multi-stage ottimizzato -- docker-compose.yml con Ollama incluso +- docker-compose.yml per la sola dashboard (Ollama esterno/remoto) - Health checks configurati - Sempre acceso fino all'arresto manuale diff --git a/README.md b/README.md index dd4890c..0177569 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ OLLAMA_TIMEOUT=30 API_HOST=0.0.0.0 API_PORT=8000 API_WORKERS=4 +ENABLE_MODEL_RW_API=false # CORS Configuration CORS_ORIGINS=http://localhost:3000,http://localhost:5173 @@ -105,6 +106,7 @@ ENVIRONMENT=development | `API_HOST` | `0.0.0.0` | Host su cui esporre l'API | | `API_PORT` | `8000` | Porta dell'API | | `API_WORKERS` | `4` | Worker processes | +| `ENABLE_MODEL_RW_API` | `false` | Abilita endpoint `POST/DELETE` sui modelli | | `CORS_ORIGINS` | `http://localhost:3000` | Origini CORS consentite | | `LOG_LEVEL` | `INFO` | Livello di logging | | `ENVIRONMENT` | `development` | Ambiente (development/production) | @@ -151,6 +153,21 @@ GET /api/v1/models/{model_name} GET /api/v1/health ``` +#### Endpoint R/W modelli (opzionali) + +Per impostazione predefinita gli endpoint di scrittura sono **disabilitati** e non disponibili. + +```bash +POST /api/v1/models/{model_name}/pull +DELETE /api/v1/models/{model_name} +``` + +Per abilitarli, imposta nel file `.env`: + +```env +ENABLE_MODEL_RW_API=true +``` + **Risposta:** ```json @@ -215,16 +232,16 @@ docker compose restart llm-monitor ### Container sempre acceso -Il container Ollama rimarrĂ  in esecuzione fino al suo arresto manuale: +Il container `llm-monitor` rimarrĂ  in esecuzione fino al suo arresto manuale: ```bash # Fermare -docker compose stop ollama +docker compose stop llm-monitor # oppure docker stop llm-monitor # Riavviare -docker compose start ollama +docker compose start llm-monitor # oppure docker start llm-monitor ``` diff --git a/app/api/models.py b/app/api/models.py index 71e5bfa..9bf8250 100644 --- a/app/api/models.py +++ b/app/api/models.py @@ -13,6 +13,15 @@ from app.config import settings logger = logging.getLogger(__name__) router = APIRouter() + +def ensure_rw_api_enabled() -> None: + """Blocca le API di scrittura se non abilitate esplicitamente.""" + if not settings.ENABLE_MODEL_RW_API: + raise HTTPException( + status_code=404, + detail="Endpoint non disponibile" + ) + class ModelInfo(BaseModel): """Informazioni su un modello""" name: str @@ -165,7 +174,10 @@ async def get_model(model_name: str): detail="Errore nel recupero del modello" ) -@router.post("/models/{model_name}/pull") +@router.post( + "/models/{model_name}/pull", + include_in_schema=settings.ENABLE_MODEL_RW_API +) async def pull_model(model_name: str): """ Scarica/carica un modello in Ollama @@ -176,6 +188,7 @@ async def pull_model(model_name: str): Returns: dict: Status del download """ + ensure_rw_api_enabled() try: response = requests.post( f"{settings.OLLAMA_HOST}/api/pull", @@ -198,7 +211,10 @@ async def pull_model(model_name: str): detail="Errore nel pull del modello" ) -@router.delete("/models/{model_name}") +@router.delete( + "/models/{model_name}", + include_in_schema=settings.ENABLE_MODEL_RW_API +) async def delete_model(model_name: str): """ Elimina un modello da Ollama @@ -209,6 +225,7 @@ async def delete_model(model_name: str): Returns: dict: Confirmazione eliminazione """ + ensure_rw_api_enabled() try: response = requests.delete( f"{settings.OLLAMA_HOST}/api/delete", diff --git a/app/config.py b/app/config.py index a0e29be..8b538ad 100644 --- a/app/config.py +++ b/app/config.py @@ -16,6 +16,7 @@ class Settings(BaseSettings): API_HOST: str = "0.0.0.0" API_PORT: int = 8000 API_WORKERS: int = 4 + ENABLE_MODEL_RW_API: bool = False # CORS CORS_ORIGINS: str = "http://localhost:3000,http://localhost:5173,http://localhost:8000" diff --git a/app/web/static/favicon.ico b/app/web/static/favicon.ico new file mode 100644 index 0000000..0014651 Binary files /dev/null and b/app/web/static/favicon.ico differ diff --git a/app/web/templates/index.html b/app/web/templates/index.html index 6099f01..ffd636e 100644 --- a/app/web/templates/index.html +++ b/app/web/templates/index.html @@ -4,7 +4,7 @@ LLM Monitor - Dashboard Ollama - + diff --git a/env.example b/env.example index f0581fe..a143d12 100644 --- a/env.example +++ b/env.example @@ -26,6 +26,10 @@ API_PORT=8000 # Numero di worker processes per uVicorn API_WORKERS=4 +# Abilita API R/W modelli (POST /pull, DELETE /models/{name}) +# Default sicuro: false (endpoint non disponibili) +ENABLE_MODEL_RW_API=false + # =========================================== # CORS Configuration # =========================================== diff --git a/main.py b/main.py index 9f5e921..900f098 100644 --- a/main.py +++ b/main.py @@ -61,6 +61,12 @@ async def dashboard(): """Dashboard principale""" return FileResponse(templates_path / "index.html") + +@app.get("/favicon.ico", include_in_schema=False) +async def favicon(): + """Favicon dell'applicazione.""" + return FileResponse(static_path / "favicon.ico") + # Event hooks @app.on_event("startup") async def startup_event(): diff --git a/tests/test_api.py b/tests/test_api.py index 17d50d5..47e6f9f 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -92,3 +92,13 @@ def test_openapi_schema(client): assert "paths" in schema assert "/api/v1/health" in schema["paths"] assert "/api/v1/models" in schema["paths"] + assert "/api/v1/models/{model_name}/pull" not in schema["paths"] + + +def test_write_endpoints_disabled_by_default(client): + """POST/DELETE sui modelli devono essere non disponibili di default.""" + response_pull = client.post("/api/v1/models/llama2/pull") + assert response_pull.status_code == 404 + + response_delete = client.delete("/api/v1/models/llama2") + assert response_delete.status_code == 404