Speed display was wildly inaccurate during parallel multipart uploads because:
1. Parallel chunk progress events caused non-monotonic byte counts
2. The 5-second smoothing window was too short for bursty uploads
3. Speed dropped to near-zero between chunk boundaries
Fixes:
- Enforce monotonic byte tracking via _peak guard
- Extend sliding window from 5s to 15s
- Cache last good speed value for display continuity
- Require >= 1s of data before calculating (was 0.5s)
Shows "XX% · Y.Y MB/s · ~Zm Zs" during uploads. Speed is smoothed
over a 5-second rolling window. Works for both small (single PUT)
and large (multipart chunked) uploads.
A 46MB file now splits into 6 chunks (all 6 streams active) instead
of 2 chunks (4 streams idle). Better saturation of available bandwidth
on high-latency or constrained links.
Large files (>32MB) now use presigned S3 multipart URLs via
/api/desktop/multipart/init, letting the browser PUT 32MB chunks
directly to S3 in 6 parallel streams. No data passes through Node.
Small files still use single presigned PUT. Updated HTTP mode
description to "HTTP multi-part upload".
Reverts accidental landing page overwrite. Files >32MB now use S3
multipart upload with 32MB chunks and 6 parallel streams per file.
Files <=32MB still use fast direct presigned PUT.
Files >32MB now use S3 multipart upload with 32MB chunks and 6 parallel
streams per file. Files <=32MB still use fast direct presigned PUT.
Fixes slow upload speeds for large files.
Files >32MB now use S3 multipart upload with 32MB chunks and 6 parallel
streams per file. Files <=32MB still use fast direct presigned PUT.
Fixes slow upload speeds for large files.
Addresses feedback from Gavin (VPM):
- Sort folders alphabetically in both upload tree and admin tree views
- Auto-select VPM as default folder on login instead of Root
- Fix S3 key construction for nested folders: convert "/" to "--" so FLX
correctly maps subfolders (e.g. Content/TEST - AMPP Demo now produces
Content--TEST - AMPP Demo--file.ext instead of Content/TEST - AMPP Demo--file.ext)
- Clarify HTTP mode note: "Files are processed 6 at a time" instead of
"up to 6 concurrent files" which implied a total file limit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Logs the first job's keys and data (truncated to 1000 chars) on each
/api/ampp/jobs request to help identify which field contains the asset name.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Browser now uploads files directly to RustFS/S3 via presigned PUT URLs.
The Node server only generates signed URLs and tracks quota — file data
never touches the server. 6 concurrent file uploads.
Falls back to server-proxied PutObjectCommand upload if presigned fails.
Server changes:
- /api/presigned now checks folder permissions, quota, and blocked files
- /api/presigned/complete endpoint for post-upload quota tracking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add search/filter input to destination folder browser with 320px
scrollable container
- Add User Audit button showing each user's visible pages, role,
quota, and folder access permissions
- Fix admin Folders tab delete buttons (were broken due to
JSON.stringify quote conflicts in innerHTML onclick handlers)
- Add more AMPP job name field fallbacks and debug logging to
diagnose asset name display issue
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The @aws-sdk/lib-storage Upload class internally uses
CreateMultipartUploadCommand for files over the part size threshold,
which returns non-standard XML from RustFS causing UnknownError.
PutObjectCommand does a simple single PUT request that RustFS handles
correctly. Fixed in both /api/upload and /api/link/:id/upload endpoints.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The manual CreateMultipartUploadCommand/UploadPartCommand/CompleteMultipartUploadCommand
flow fails with RustFS due to non-standard XML responses (same issue as HeadBucketCommand).
Switch front-end to use /api/upload endpoint which uses @aws-sdk/lib-storage Upload class —
the same method that worked reliably in VPM-Uploader with RustFS.
Uses XMLHttpRequest for upload progress tracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AMPP API returns keys like 'state:jobState', 'name:text', 'type:jobType',
'creator:id', 'created:dateTime', 'job:id' — not plain 'status', 'name',
etc. Frontend was falling back to 'Job' / 'Unknown' for every entry.
Updated field lookups to read colon-namespaced keys first, with fallbacks
for compatibility. Also added 'aborted' to the failed status detection.
Split files into 32 MB chunks, POST 6 concurrently to /api/upload/chunk,
server proxies each chunk as an S3 multipart part. Up to 4 files upload
in parallel simultaneously. Achieves Aspera-class throughput over plain
HTTP with no UDP port forwarding or custom protocols required.
Same approach used by MASV under the hood.
- server.js: Add /api/upload/initiate, /chunk, /complete, /abort endpoints
- public/index.html: Replace single-PUT uploadHTTP() with parallel chunked version
Root cause: three critical bugs in the UDP upload flow:
1. Main server never registered sessions on the relay — it stored them
in its own memory but never called POST /session on the relay, so
the relay had no idea about any upload sessions.
2. Relay had no HTTP chunk endpoint — the Chrome extension sends chunks
via HTTP POST to /session/:id/chunk/:index, but the relay only had
a binary UDP listener. Added the HTTP fallback endpoint.
3. Relay had no CORS headers — browser requests from chrome-extension://
origins were blocked. Added CORS middleware.
The flow now works:
Browser → POST /api/udp/session (main server)
Main server → POST /session (relay, with s3Config)
Browser → POST /session/:id/chunk/:n (relay, via public URL)
Relay → S3 multipart upload
Browser → POST /api/udp/session/:id/complete (main server)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The relay container is reached internally via docker service name
(http://dragon-wind-relay:3001) but browsers need the public address.
Previously the internal URL was sent to clients causing UDP uploads to fail.
- Add publicRelayUrl field to relay config (server + admin UI)
- /api/udp/session now returns publicRelayUrl to browser clients
- Internal relayUrl still used for server-side health checks
- Falls back to internal URL if public not set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The extension was saving and restoring UDP mode from storage. If UDP
was previously selected and the relay isn't configured, every upload
immediately fails with "UDP relay not configured".
- Default to HTTP mode on fresh installs (no saved mode)
- Before UDP upload, check /api/health relayConfigured flag and
auto-fall-back to HTTP with a warning if relay isn't set up
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same fix as the Chrome extension — the web GUI's uploadHTTP() was using
item.file.type (browser-determined MIME) instead of presigned.contentType
(server-signed MIME). For broadcast formats like MXF, R3D, BRAW where
the browser returns empty or generic types, this causes an S3 signature
mismatch and the PUT fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The presigned URL is signed with a specific Content-Type (determined by
the server's MIME map). If the browser's file.type doesn't match (common
for broadcast formats like MXF, R3D, BRAW), S3 rejects the PUT with a
signature mismatch. Now the extension uses presigned.contentType from the
server response instead of item.file.type.
Also added console logging for upload requests and detailed error
messages from S3 responses on failure.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- apiFetch() now logs method+URL on start and status code on response
- Added 10s AbortController timeout to prevent infinite hangs
- Added try/catch wrapper in saveSettings() around login() call
- This helps diagnose the "stuck on connecting" issue
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Login failures (wrong password) were caught by the 401 handler in
apiFetch() which threw "Session expired" instead of returning the
actual error message. Now /api/login 401 responses pass through
so the real "Invalid credentials" error is shown.
- Settings panel now shows error messages from login() failures
instead of staying stuck on "Saved — connecting…" forever.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add "alarms" permission to manifest — chrome.alarms.create() was
throwing "Cannot read properties of undefined" because the permission
was missing; also removed invalid "sockets" permission (not a valid MV3 perm)
- Remove all inline event handlers from popup.html (onclick, ondragover,
ondragleave, ondrop, onchange) — MV3 CSP blocks inline JS entirely;
all handlers moved to popup.js addEventListener() calls
- Replace inline onclick="removeFile(i)" on dynamically generated remove
buttons with data-idx attribute + delegated click listener on the list
container — same CSP fix for runtime-generated elements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>