From a208f2eec7332af4a3d0ac409dc420ea570839b7 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Thu, 23 Apr 2026 15:44:50 +0200 Subject: [PATCH] feat(ui): useUploadJar uses XHR and exposes onProgress --- ui/src/api/queries/admin/apps.ts | 48 ++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/ui/src/api/queries/admin/apps.ts b/ui/src/api/queries/admin/apps.ts index 2d77e2d9..7e3efecd 100644 --- a/ui/src/api/queries/admin/apps.ts +++ b/ui/src/api/queries/admin/apps.ts @@ -140,21 +140,47 @@ export function useAppVersions(envSlug: string | undefined, appSlug: string | un export function useUploadJar() { const qc = useQueryClient(); return useMutation({ - mutationFn: async ({ envSlug, appSlug, file }: { envSlug: string; appSlug: string; file: File }) => { + mutationFn: ({ envSlug, appSlug, file, onProgress }: { + envSlug: string; + appSlug: string; + file: File; + onProgress?: (pct: number) => void; + }) => { const token = useAuthStore.getState().accessToken; const form = new FormData(); form.append('file', file); - const res = await fetch( - `${config.apiBaseUrl}${envBase(envSlug)}/${encodeURIComponent(appSlug)}/versions`, { - method: 'POST', - headers: { - ...(token ? { Authorization: `Bearer ${token}` } : {}), - 'X-Cameleer-Protocol-Version': '1', - }, - body: form, + + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open( + 'POST', + `${config.apiBaseUrl}${envBase(envSlug)}/${encodeURIComponent(appSlug)}/versions`, + ); + if (token) xhr.setRequestHeader('Authorization', `Bearer ${token}`); + xhr.setRequestHeader('X-Cameleer-Protocol-Version', '1'); + + xhr.upload.onprogress = (e) => { + if (!onProgress || !e.lengthComputable) return; + onProgress(Math.round((e.loaded / e.total) * 100)); + }; + + xhr.onload = () => { + if (xhr.status < 200 || xhr.status >= 300) { + reject(new Error(`Upload failed: ${xhr.status}`)); + return; + } + try { + resolve(JSON.parse(xhr.responseText) as AppVersion); + } catch (err) { + reject(err instanceof Error ? err : new Error('Invalid response')); + } + }; + + xhr.onerror = () => reject(new Error('Upload network error')); + xhr.onabort = () => reject(new Error('Upload aborted')); + + xhr.send(form); }); - if (!res.ok) throw new Error(`Upload failed: ${res.status}`); - return res.json() as Promise; }, onSuccess: (_data, { envSlug, appSlug }) => qc.invalidateQueries({ queryKey: ['apps', envSlug, appSlug, 'versions'] }),