feat(web-ui): add a Download button for completed files - #1216
Conversation
|
Read the whole change. The reasoning here is right and I checked the load-bearing claims rather than taking them: The anchor-not- One gap: the navigation bypasses the session-death gateYou reasoned that a partfile must not be an anchor, because a navigation would replace the page with the endpoint's
All three answer JSON with no The window is normally small - the SSE stream and polls notice a dead session within a tick and unmount the panel. It is not small for a tab left open overnight past the 24h I am not sure this is worth fixing, and that is the point of raising it. So: either take Nit: |
19b083f to
164a17d
Compare
|
Both addressed in The
So the comment now says what actually happens on all four statuses instead of only the one I had reasoned about: // A navigation cannot be intercepted, so 401, 404 and 503 also render their
// JSON in place of the app; 401 additionally bypasses markSessionDead(), which
// only runs inside request(). Accepted rather than fixed: the window is one
// tick, since any poll or the SSE stream unmounts the panel on a dead session.
// target="_blank" would contain it, but Firefox can leave an empty tab on the
// success path — trading a common-path annoyance for a rare-path recovery.Your overnight-tab case is the one that survives that reasoning, and I have not solved it — a backgrounded, throttled tab past the 24h The nit. Fixed: Re-verified after the change: the button still resolves to Separately, @ngosang asked on #1215 for fewer comments in the code. I have not applied that here yet beyond tightening the two blocks above — the trimming pass is on that branch first, and I will bring the same standard back to this one before it lands. |
|
Correcting a figure I cited here, in the last paragraph of my previous comment, when I referred to @ngosang's request on #1215. I claimed upstream's Web UI sits at 9% comment lines. It does not. That was Measured across all 24 non-vendor JS files over 50 lines, upstream ranges 5% to 35%, median 17%. The trim on #1215 still stands on its own merits, and I will bring the same standard here before this lands — but the justification I gave for it was not one that survives checking, and I would rather say so than leave the number standing. Full correction is on #1215. |
|
Agreed on The overnight tab is not this button's problem: there is no Both fixes confirmed in |
164a17d to
e6e00c9
Compare
This is what the endpoint was for. The Shared Files panel could tell you a file was complete and then leave you to fetch it over SFTP; now there is a button, and the finished-download panel has the same one, since a completed download has been moved into Incoming and resolves under the same hash. A plain anchor, never a fetch(). The endpoint answers with Content-Disposition: attachment, so a navigation hands the response to the browser's own downloader: the page stays mounted and the bytes never enter JS. A fetch()+Blob would buffer the whole file in browser memory and throw away the constant-memory streaming the endpoint exists for -- a 600 MB transfer moves the daemon by kilobytes, and it would be absurd to undo that in the client. No token in the URL. The session cookie is HttpOnly and same-origin, so a navigation carries the credential by itself; a token in a query string would land in browser history, the Referer header and every proxy log in between. The URL is built through a helper that shares the subpath derivation with the fetch client, so a reverse-proxy prefix keeps working. Not admin-gated: the route is GUEST-accessible, like the listing it hangs off. A session allowed to see the file is allowed its bytes. A partfile renders as a disabled button with a title saying why, rather than an anchor -- an anchor cannot be disabled, and since this is a navigation rather than a fetch, clicking one would replace the page with the endpoint's 409 JSON instead of raising a toast. That flag is also why there is no per-row action in the list: `incomplete` is detail-only, so a row-level control could not tell a servable file from a partfile.
e6e00c9 to
4278682
Compare
Closes #659
Summary
The Web UI half of #659, now that #1203 has landed: a Download button that pulls a finished file straight to the browser. That was the motivation stated in the issue — the Web UI could tell you a file was complete and then leave you to fetch it over SFTP.
Generated with AI, flagged up front per the terms in #1177. Web UI only, no C++.
It appears in the Shared Files detail panel, and in the detail panel of a finished download — a completed download has been moved into Incoming, which is shared, so the same hash resolves under
shared/{hash}/content.donegates it on the downloads side for exactly that reason; anything earlier is still a partfile and the endpoint has nothing servable for it.A plain anchor, deliberately not a
fetch()The endpoint answers with
Content-Disposition: attachment, so a navigation hands the response to the browser's own downloader: the page stays mounted and the bytes never enter JS. Afetch()+Blob would buffer the whole file in browser memory and undo the constant-memory streaming the endpoint exists for — a 600 MB transfer moves the daemon's RSS by kilobytes, and it would be absurd to spend that on the client instead.No token in the URL. The session cookie is HttpOnly and same-origin, so a navigation carries the credential by itself. A token in a query string would land in browser history, the
Refererheader and every proxy log in between. The URL is built through a helper that shares the subpath derivation with the fetch client, so a reverse-proxy prefix keeps working.Not admin-gated, because the route is
GUEST-accessible like the listing it hangs off: a session allowed to see the file is allowed its bytes.A partfile renders as a disabled button with a title saying why, rather than an anchor. An anchor cannot be disabled, and since this is a navigation rather than a fetch, clicking one would replace the page with the endpoint's
409JSON instead of raising a toast. That flag is also why there is no per-row action in the list:incompleteis detail-only, so a row-level control could not tell a servable file from a partfile.Screenshot
Test plan
Driven in a browser against a live daemon running merged master (
2e2a80e), so the endpoint under it is the shipped one, not a local branch.Clicking the button issues:
The filename has spaces, so the extended form exercises the percent-encoding rather than the trivial path.
686c163dd8f31d4e26b310fcf476e8d7received, identical to the file on disk.admin-onlyclass and noadmin-onlyancestor.check-i18n.mjsclean; 4 new keys in both catalogues.The headless browser reports
net::ERR_ABORTEDafter the headers because it has no download directory configured; the response is fully formed, which is what the byte check above confirms independently.