Files

170 lines
5.9 KiB
Python

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from jinja2 import Environment, FileSystemLoader
import httpx, os, json
from k8s import create_service, delete_service, list_user_services
from minio_client import ensure_bucket
app = FastAPI()
jinja_env = Environment(loader=FileSystemLoader("/app/code/templates"))
KEYCLOAK_URL = "https://keycloak.lab.leadwire.dev/auth/realms/datalab"
CLIENT_ID = "datalab-lite"
CLIENT_SECRET= os.getenv("CLIENT_SECRET", "")
REDIRECT_URI = "https://datalablite.lab.leadwire.dev/callback"
SERVICES = {
"jupyter": {
"name": "JupyterLab",
"image": "jupyter/scipy-notebook:latest",
"port": 8888,
"icon": "🔬",
"env": {"JUPYTER_ENABLE_LAB": "yes", "JUPYTER_TOKEN": "{token}"}
},
"rstudio": {
"name": "RStudio",
"image": "rocker/rstudio:latest",
"port": 8787,
"icon": "📊",
"env": {"PASSWORD": "{token}", "USER": "rstudio"}
},
"vscode": {
"name": "VSCode Server",
"image": "codercom/code-server:latest",
"port": 8080,
"icon": "💻",
"env": {"PASSWORD": "{token}"}
}
}
def render(template_name, **ctx):
t = jinja_env.get_template(template_name)
return HTMLResponse(t.render(**ctx))
@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
username = request.cookies.get("username")
if not username:
return RedirectResponse("/login")
services = list_user_services(username)
return render("index.html", username=username, services=services, catalog=SERVICES)
@app.get("/login")
async def login():
url = (
f"{KEYCLOAK_URL}/protocol/openid-connect/auth"
f"?client_id={CLIENT_ID}"
f"&redirect_uri={REDIRECT_URI}"
f"&response_type=code"
f"&scope=openid profile email"
)
return RedirectResponse(url)
@app.get("/callback")
async def callback(code: str, request: Request):
async with httpx.AsyncClient(verify=False) as client:
resp = await client.post(
f"{KEYCLOAK_URL}/protocol/openid-connect/token",
data={
"grant_type": "authorization_code",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"code": code,
"redirect_uri": REDIRECT_URI,
}
)
if resp.status_code != 200:
raise HTTPException(status_code=401, detail="Auth failed")
tokens = resp.json()
access_token = tokens["access_token"]
import base64
payload = access_token.split(".")[1]
payload += "=" * (4 - len(payload) % 4)
user_info = json.loads(base64.b64decode(payload))
username = user_info.get("preferred_username", "unknown")
ensure_bucket(username)
response = RedirectResponse("/", status_code=302)
response.set_cookie("token", access_token, httponly=True)
response.set_cookie("username", username, httponly=True)
return response
@app.get("/logout")
async def logout():
response = RedirectResponse("/login")
response.delete_cookie("token")
response.delete_cookie("username")
return response
@app.post("/api/launch/{service_type}")
async def launch(service_type: str, request: Request):
username = request.cookies.get("username")
if not username:
raise HTTPException(status_code=401)
if service_type not in SERVICES:
raise HTTPException(status_code=400, detail="Service inconnu")
svc = SERVICES[service_type]
url = create_service(username, service_type, svc)
return JSONResponse({"status": "ok", "url": url})
@app.delete("/api/delete/{service_type}")
async def delete(service_type: str, request: Request):
username = request.cookies.get("username")
if not username:
raise HTTPException(status_code=401)
delete_service(username, service_type)
return JSONResponse({"status": "deleted"})
@app.get("/api/services")
async def services(request: Request):
username = request.cookies.get("username")
if not username:
raise HTTPException(status_code=401)
return JSONResponse(list_user_services(username))
@app.get("/api/minio-credentials")
async def minio_credentials(request: Request):
username = request.cookies.get("username")
if not username:
raise HTTPException(status_code=401)
from minio_client import get_minio_credentials
return JSONResponse(get_minio_credentials(username))
@app.get("/api/minio-credentials")
async def minio_credentials(request: Request):
username = request.cookies.get("username")
if not username:
raise HTTPException(status_code=401)
from minio_client import get_minio_credentials
return JSONResponse(get_minio_credentials(username))
@app.post("/api/chat")
async def chat(request: Request):
username = request.cookies.get("username")
if not username:
raise HTTPException(status_code=401)
body = await request.json()
messages = body.get("messages", [])
async with httpx.AsyncClient(verify=False, timeout=60) as client:
resp = await client.post(
"http://10.233.9.253:4000/v1/chat/completions",
headers={"Authorization": "Bearer sk-local123test",
"Content-Type": "application/json"},
json={
"model": "llama3.2:3b",
"messages": [
{"role": "system", "content": f"Tu es un assistant expert Data Science pour {username}. Tu aides avec Python, pandas, SQL, machine learning, et l'utilisation de MinIO S3. Réponds en français de manière concise."}
] + messages,
"temperature": 0.7,
"max_tokens": 512,
"stream": False
}
)
if resp.status_code != 200:
raise HTTPException(status_code=502, detail="LiteLLM error")
result = resp.json()
return JSONResponse({"response": result["choices"][0]["message"]["content"]})