Files
mission-control-v2/backend/routers/partner.py
T
HitonabiandClaude Opus 5.5 22bb8c672b phase2c: Oberflaeche heisst Homelab Orchestrator, Bereich Homelab, Anfragen gehen an die richtige Instanz
- /api/instanz: Rolle der ausliefernden Instanz, ohne Netzabfragen
- lib/instanz.ts: /api/homelab/... an die Homelab-Instanz, alles andere an die Box,
  ueber /api/partner/..., wenn die andere Instanz die Seite ausliefert
- neue Seite Homelab: Stand der zweiten Instanz (noch nicht eingerichtet / verbunden /
  antwortet nicht) und was dazukommt
- Kopfzeile, Titel und Web-Manifest mit neuem Namen; fuenfter Menuepunkt, 320 px geprueft

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 17:59:23 +02:00

53 lines
2.4 KiB
Python

"""Partner-Instanz: Lebenszeichen und Durchreiche (zwei Instanzen, eine Oberfläche, 24.09.2026).
Die Oberfläche fragt den Bereich der anderen Instanz über /api/partner/<pfad> ab; hier wird
daraus <partner>/api/<pfad>. Die Herkunftsprüfung (services/herkunft.py) greift wie bei jeder
anderen schreibenden Anfrage schon hier, bevor etwas weitergereicht wird.
"""
import httpx
from fastapi import APIRouter, HTTPException, Request, Response
from kern import partner
from kern.einstellungen import einstellungen
router = APIRouter(prefix="/api", tags=["partner"])
# Nicht durchreichen: den Partner-Weg des Partners (sonst reichen sich zwei Instanzen eine Anfrage
# endlos zu) und den Live-Strom (SSE hält die Verbindung offen; der Partner-Bereich fragt ab).
_GESPERRT = ("partner", "stream")
_ZEITLIMIT = httpx.Timeout(30.0, connect=5.0)
@router.get("/instanz")
def instanz() -> dict:
"""Wer liefert die Seite aus? Ohne Netzabfragen, damit die Oberfläche sofort weiß, welche Anfragen
sie selbst beantwortet und welche über /api/partner/… an die andere Instanz gehen."""
e = einstellungen()
return {"rolle": e.rolle, "instanz": e.instanz,
"partner": {"eingerichtet": bool(e.partner_url), "name": e.partner_name}}
@router.get("/partner")
def partner_status() -> dict:
return partner.status()
@router.api_route("/partner/{pfad:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"], include_in_schema=False)
async def weiterreichen(pfad: str, request: Request) -> Response:
e = einstellungen()
if not e.partner_url:
raise HTTPException(status_code=404, detail="Es ist keine zweite Instanz eingerichtet.")
if pfad.split("/", 1)[0] in _GESPERRT:
raise HTTPException(status_code=404, detail="Diese Schnittstelle wird nicht weitergereicht.")
kopf = {"X-MC-Von": e.instanz}
if request.headers.get("content-type"):
kopf["Content-Type"] = request.headers["content-type"]
try:
async with httpx.AsyncClient(timeout=_ZEITLIMIT) as c:
r = await c.request(request.method, f"{e.partner_url}/api/{pfad}",
params=request.query_params, content=await request.body(), headers=kopf)
except httpx.HTTPError:
raise HTTPException(status_code=502, detail=f"{e.partner_name} ist gerade nicht erreichbar.") from None
return Response(content=r.content, status_code=r.status_code,
media_type=r.headers.get("content-type", "application/json"))