feat: add favicon.ico and gate model write APIs by env flag
- Generate and serve real /favicon.ico from static assets - Update HTML to use /favicon.ico - Add ENABLE_MODEL_RW_API setting (default: false) - Disable POST/DELETE model endpoints by default - Hide write endpoints from OpenAPI when disabled - Return 404 for write endpoints when disabled - Update env.example with ENABLE_MODEL_RW_API documentation - Update README and PRD with R/W API policy and remote compose notes - Add tests to verify write endpoints are disabled by default
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
+19
-2
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LLM Monitor - Dashboard Ollama</title>
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🦙</text></svg>">
|
||||
<link rel="icon" href="/favicon.ico" sizes="any">
|
||||
<!-- Tailwind CSS (compiled for production) -->
|
||||
<link rel="stylesheet" href="/static/css/output.css">
|
||||
<!-- Fallback CDN for development (if output.css not available) -->
|
||||
|
||||
@@ -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
|
||||
# ===========================================
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user