Files
mission-control-v2/frontend/src/lib/api.ts
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

57 lines
2.0 KiB
TypeScript

// Dünner Zugriff auf das Backend. Bewusst ohne Anmelde- oder Geheimnis-Felder:
// Nichts, was der Browser speichert, reist hier mit (Lehre aus v3-Umbau P1).
// Seit Phase 2 gibt es zwei Instanzen; welche eine Anfrage beantwortet, entscheidet lib/instanz.ts.
import { eigeneRolle, zielPfad } from "./instanz"
export class ApiFehler extends Error {
constructor(
public status: number,
public detail: string,
) {
super(detail)
this.name = "ApiFehler"
}
}
/** FastAPI legt Fehlertexte in `detail` ab — manchmal als Text, manchmal als Liste. */
function detailAus(body: unknown, status: number): string {
if (body && typeof body === "object" && "detail" in body) {
const d = (body as { detail: unknown }).detail
if (typeof d === "string") return d
if (Array.isArray(d)) return d.map((x) => (x as { msg?: string }).msg ?? String(x)).join("; ")
}
return `Die Box antwortet mit Fehler ${status}.`
}
export async function api<T>(pfad: string, init: RequestInit = {}): Promise<T> {
const ziel = zielPfad(pfad, await eigeneRolle())
let antwort: Response
try {
antwort = await fetch(ziel, {
...init,
headers: { "Content-Type": "application/json", Accept: "application/json", ...init.headers },
})
} catch {
throw new ApiFehler(0, "MC2 ist nicht erreichbar. Läuft die Box?")
}
const text = await antwort.text()
const body = text ? safeJson(text) : null
if (!antwort.ok) throw new ApiFehler(antwort.status, detailAus(body, antwort.status))
// Kommt statt JSON eine HTML-Seite (alter Server, falscher Pfad), ist das ein Fehler —
// sonst rechnen die Ansichten mit einem Text, als wären es Daten.
if (typeof body === "string") throw new ApiFehler(antwort.status, "Die Box antwortet nicht mit Daten.")
return body as T
}
function safeJson(text: string): unknown {
try {
return JSON.parse(text)
} catch {
return text
}
}
export const post = <T>(pfad: string, daten?: unknown) =>
api<T>(pfad, { method: "POST", body: daten === undefined ? undefined : JSON.stringify(daten) })