56c07c3581
Ein neuer Widget-Typ war an sieben Stellen einzutragen; vergass man eine, fehlte die Kachel im Katalog oder die API lehnte sie mit 400 ab. - WIDGET_TYPES/WidgetType/WIDGET_MODULE_SLUGS stehen jetzt einmal in packages/shared; Registry, Katalog und die @IsIn-Whitelist der API leiten davon ab - neun wireXWidget()-Funktionen durch ein generisches registerWidget() ersetzt (idempotent, unbekannter Typ wirft in der Entwicklung) - der Katalog fuehrt keine zweite Typliste mehr, sondern leitet sie aus der Registry ab und filtert nach Modulzugriff (fail-closed, wenn die Modulliste unbekannt ist); der Abruf von /modules/active liegt auf der Dashboard-Seite, nicht im Dialog - widget-module-map.ts liest die geteilte Tabelle statt einer Kopie, die oeffentliche Funktion bleibt unveraendert Der Katalogfilter ist Komfort (T-M1H-01) — verbindlich bleibt der serverseitige Filter in DashboardService.getWidgets. Abweichung vom Plan: apps/web hing entgegen der Planannahme noch nicht von @tessera/shared ab; die Abhaengigkeit wurde ergaenzt (Lockfile). Die Dockerfiles kopieren packages/shared bereits, der Produktionsbau von Next.js und der nest build laufen unveraendert. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
69 lines
2.6 KiB
TypeScript
69 lines
2.6 KiB
TypeScript
/**
|
|
* Desktop-Paket-Info fuer die Web-Oberflaeche (Phase 18, D-10/D-12).
|
|
*
|
|
* Quelle fuer den unauffaelligen Download-Link auf der Anmeldeseite
|
|
* (`DesktopDownloadLinks`) UND fuer die Einstellungsseite
|
|
* "Desktop-App" (`DesktopAppSettings`): beide lesen `GET /desktop/latest`
|
|
* (oeffentlich, `@Public()` in `apps/api/src/desktop/desktop.controller.ts`)
|
|
* und blenden sich aus, wenn der Server keine Pakete traegt (404).
|
|
*
|
|
* Wichtig: Next.js ersetzt `process.env.NEXT_PUBLIC_*` nur dann zur Bauzeit
|
|
* im Browser-Bundle, wenn der Ausdruck woertlich mit vollem Namen im Code
|
|
* steht — kein Destructuring, kein `process.env[name]` (siehe `app-version.ts`).
|
|
* Im Betrieb laeuft die Anfrage ueber den Rewrite `/api-proxy` auf die API.
|
|
*/
|
|
|
|
export type DesktopPlatform = 'windows' | 'linux';
|
|
|
|
/**
|
|
* Spiegel von `DesktopLatestFile`/`DesktopLatestResponse` aus
|
|
* `packages/shared` (gleicher Stand wie in `app-version.ts`: seit
|
|
* quick-260922-m1h waere ein Import moeglich, der Spiegel wurde aber bewusst
|
|
* nicht mit aufgeloest). Die API-Wahrheit bleibt in
|
|
* `apps/api/src/desktop/desktop.service.ts`.
|
|
*/
|
|
export interface DesktopFileInfo {
|
|
name: string;
|
|
size: number;
|
|
sha256: string;
|
|
url: string;
|
|
}
|
|
|
|
export interface DesktopLatestInfo {
|
|
version: string;
|
|
channel: string;
|
|
commit: string;
|
|
buildTime: string;
|
|
files: Partial<Record<DesktopPlatform, DesktopFileInfo>>;
|
|
}
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001';
|
|
|
|
let desktopLatestPromise: Promise<DesktopLatestInfo | null> | null = null;
|
|
|
|
/**
|
|
* Laedt `GET /desktop/latest` genau einmal je Modulinstanz (memoisiert);
|
|
* jeder Fehler (Netz, Nicht-2xx — insbesondere 404 ohne Manifest) ist still
|
|
* und liefert `null`. Kein `credentials: 'include'`: die Anmeldeseite zeigt
|
|
* den Link, bevor ein Cookie existiert, und der Endpunkt ist oeffentlich.
|
|
*/
|
|
export function loadDesktopLatest(): Promise<DesktopLatestInfo | null> {
|
|
if (!desktopLatestPromise) {
|
|
desktopLatestPromise = fetch(`${API_URL}/desktop/latest`)
|
|
.then((res) => (res.ok ? (res.json() as Promise<DesktopLatestInfo>) : null))
|
|
.catch(() => null);
|
|
}
|
|
return desktopLatestPromise;
|
|
}
|
|
|
|
/** Baut die absolute Download-Adresse aus `API_URL` plus dem relativen `url`-Feld (T-18-07). */
|
|
export function desktopDownloadUrl(file: DesktopFileInfo): string {
|
|
return `${API_URL}${file.url}`;
|
|
}
|
|
|
|
/** Formatiert Dateigroessen in MB, lokalisiert (z. B. `5,8 MB` / `5.8 MB`). */
|
|
export function formatFileSize(bytes: number, locale: string): string {
|
|
const mb = bytes / 1048576;
|
|
return `${new Intl.NumberFormat(locale, { maximumFractionDigits: 1 }).format(mb)} MB`;
|
|
}
|