diff --git a/backend/app/routers/branding.py b/backend/app/routers/branding.py index 2d12d52..a136184 100644 --- a/backend/app/routers/branding.py +++ b/backend/app/routers/branding.py @@ -40,7 +40,19 @@ def _check_kind(kind: str) -> str: return kind -@router.get("/{kind}") +@router.get("") +def branding_status(db: Session = Depends(get_db)) -> dict[str, bool]: + """Welche eigenen Bilder hinterlegt sind – ohne sie zu übertragen. + + Vorher fragte die Oberfläche das per HEAD auf das Bild ab. FastAPI + registriert für eine GET-Route aber kein HEAD (anders als Starlette), die + Anfrage lief also in ein 405 und jedes hinterlegte Logo galt als "nicht da". + """ + vorhanden = {asset.kind for asset in db.query(BrandingAsset).all()} + return {kind: kind in vorhanden for kind in KINDS} + + +@router.api_route("/{kind}", methods=["GET", "HEAD"]) def get_branding(kind: str, db: Session = Depends(get_db)) -> Response: """Liefert das hinterlegte Bild – oder 404, wenn keines gesetzt ist. diff --git a/web/src/branding.js b/web/src/branding.js index 4ab6e4c..ee421b2 100644 --- a/web/src/branding.js +++ b/web/src/branding.js @@ -8,14 +8,28 @@ export function brandingUrl(kind, version) { return `/api/branding/${kind}${version ? `?v=${version}` : ""}`; } +/** + * Fragt beim Server ab, welche eigenen Bilder hinterlegt sind. + * + * Frueher lief das als HEAD-Anfrage auf das Bild selbst. FastAPI beantwortet + * eine GET-Route aber nicht automatisch auch mit HEAD – die Anfrage endete in + * einem 405, und ein hochgeladenes Logo wurde nie angezeigt. Jetzt liefert der + * Server einen kleinen Status, ganz ohne Bilddaten. + */ +export async function brandingStatus() { + try { + const resp = await fetch("/api/branding"); + if (!resp.ok) return {}; + return await resp.json(); + } catch { + return {}; // Server nicht erreichbar: eingebautes Zeichen genügt. + } +} + /** Prüft, ob ein eigenes Bild hinterlegt ist (ohne es zu laden). */ export async function hasBranding(kind) { - try { - const resp = await fetch(brandingUrl(kind), { method: "HEAD" }); - return resp.ok; - } catch { - return false; // Server nicht erreichbar: eingebautes Zeichen genügt. - } + const status = await brandingStatus(); + return Boolean(status[kind]); } /** diff --git a/web/src/components/BrandingUpload.jsx b/web/src/components/BrandingUpload.jsx index 7cb9728..a9b95c4 100644 --- a/web/src/components/BrandingUpload.jsx +++ b/web/src/components/BrandingUpload.jsx @@ -7,11 +7,13 @@ import Icon from "./Icon"; * Ein Bild der Installation setzen oder wieder entfernen (Logo bzw. Favicon). * Ohne eigenes Bild bleibt das eingebaute Zeichen der Oberfläche stehen. */ -export default function BrandingUpload({ kind, title, hint, onChanged }) { +export default function BrandingUpload({ kind, title, hint, empfehlung, onChanged }) { const [vorhanden, setVorhanden] = useState(false); const [version, setVersion] = useState(0); const [busy, setBusy] = useState(false); const [fehler, setFehler] = useState(null); + // Tatsaechliche Bildgroesse - damit man sieht, ob sie zur Empfehlung passt. + const [groesse, setGroesse] = useState(null); const dateiFeld = useRef(null); useEffect(() => { @@ -55,13 +57,23 @@ export default function BrandingUpload({ kind, title, hint, onChanged }) { return (
+ {empfehlung && ( +

Empfohlen: {empfehlung}

+ )}
{vorhanden ? ( - {title} + {title} setGroesse({ + w: e.currentTarget.naturalWidth, + h: e.currentTarget.naturalHeight, + })} /> ) : ( Kein eigenes Bild – Standard wird verwendet )}
+ {vorhanden && groesse?.w > 0 && ( +

Hochgeladen: {groesse.w} × {groesse.h} px

+ )}

{hint}

{fehler &&
{fehler}
}
diff --git a/web/src/pages/Settings.jsx b/web/src/pages/Settings.jsx index 65e53ab..377e87a 100644 --- a/web/src/pages/Settings.jsx +++ b/web/src/pages/Settings.jsx @@ -86,13 +86,17 @@ export default function Settings() {