Usage › Web UI¶
The Web UI is a React SPA served by FastAPI. It covers the full Unit3D pre-flight workflow from the browser: library scan, guided upload, torrent queue, history, configuration, real-time logs, live integration with Unit3DWebUp. Available in Italian and English (TopBar switcher).
Start:
Open the resulting URL (http://<U3DP_HOST>:<U3DP_PORT><U3DP_ROOT_PATH>, default http://127.0.0.1:8765). Make sure Unit3DWebUp is listening at WEBUP_URL (default 127.0.0.1:8000).
Login¶
First screen: password field. Credentials are validated against U3DP_PASSWORD_HASH (bcrypt). The session is signed with U3DP_SECRET (itsdangerous) and lasts as long as the browser keeps the cookie.
Middleware order
Behind the scenes: SessionMiddleware must be added after the auth middleware (LIFO = last added is outermost). If you see AssertionError: SessionMiddleware must be installed, that's a bug — open an issue.
Media Library¶

Lists categories (subfolders of U3DP_MEDIA_ROOT) and items inside them.
Features:
- Category dropdown — auto-discovered (
GET /api/library/categories). Not hardcoded. - Item list — each row shows title, year (if from TMDB), total size, detected audio languages.
- Sorting — name, year, size.
- Search — live filter on name.
- Hide uploaded — toggle controlled by
W_HIDE_UPLOADED. - Hide non-Italian — toggle controlled by
W_HIDE_NO_ITALIAN. - Only to-check — toggle that shows only media flagged "to-check" (see below).
- Detail panel — clicking an item opens a side panel (mobile: full-screen overlay) with file list, TMDB match, actions.
- Rescan audio languages — button that streams the
pymediainfoscan via SSE, updating the cache. - Manual TMDB match — field to enter an ID, search button with result previews.
- Multi-select — checkbox on each item; action bar with "Select all", "Deselect", "Mark as uploaded" for bulk operations.
- Type filter — toggle to show only movies (
kind === 'movie'), hiding series and seasons. - Mark uploaded at every level — for series: full series, single season, single episode.
- To-check (⚑) — button to flag a media for later review (e.g. it lacks an Italian audio track). Persistent and removable with one click; available at every level (movie, episode, season, full series). Flagged media show a yellow "to check" badge and can be filtered with the Only to-check toggle. Endpoint:
POST /api/library/{category}/{item}/to-check.
Relevant endpoints: GET /api/library/categories, GET /api/library/{category}, GET /api/library/{category}/{item}, POST /api/library/{category}/{item}/langs, POST /api/tmdb/search, POST /api/tmdb/fetch.
Upload Wizard¶

Step-by-step flow. Alternative to the CLI with persistent history, graphical progress bar and live SSE logs.
Typical steps:
- Select source — file or folder under
U3DP_MEDIA_ROOT. - Audio check — if
W_AUDIO_CHECK, scans the tracks. - TMDB — search or ID entry. If
W_AUTO_TMDB, auto-fetch from an existing ID. - Name preview — editable; if
W_CONFIRM_NAMESis OFF, skips the confirmation. - Duplicate check — if
W_DUPLICATE_CHECK(default ON), query the ITT API before hardlinking. If a torrent with the same exact byte size already exists, a yellow panel pops up (see below). Skipped for season packs. - Hardlink — into
U3DP_SEEDINGS_DIR/.unit3dprep/<jobid>/...(per-upload sandbox, see Unit3DWebUp integration). IfW_HARDLINK_ONLY, stops here and records exit code0. - Upload — the HTTP bridge runs
setenv → scan → maketorrent → upload → seedagainst Unit3DWebUp and streams logs + progress to the frontend over SSE (GET /api/wizard/{tok}/upload). Phase weights shown in the bar: setenv 3% / scan 27% / maketorrent 45% / upload 15% / seed 10%. - History write —
update_exit_code(seeding_path, code)persists intoU3DP_DB_PATH(also fromwizard_finishwhenW_HARDLINK_ONLY=1, exit code 0).
Pre-upload duplicate check¶
Replicates the legacy unit3dup CLI behavior: before building the .torrent, the bridge calls GET <ITT_URL>/api/torrents/filter?tmdbId=<id>&api_token=<key> and compares data[].attributes.size byte-by-byte against the local file size. Exact match → yellow panel with:
- Name, size, type/resolution, uploader, seeders/leechers, created at;
- "Open on tracker" link to the details page;
- "Upload anyway" → proceeds with hardlink + upload (useful for legitimate re-releases or alternate sources);
- "Cancel" → writes a history entry with status
⏭ duplicate skipped, hides the item from the Media Library (source_pathlands inuploaded_paths), and ends the wizard without creating any hardlink.
Webup 0.0.25 does NOT implement duplicate detection (DUPLICATE_ON/SKIP_DUPLICATE are # Todo Not yet implemented upstream): the check is performed by the unit3dprep bridge and only runs for kind=movie and kind=episode. Season packs are skipped because the pack's total byte size doesn't correspond to any single torrent on the tracker.
Best-effort: if the ITT API is unreachable or returns an error, the check is skipped silently and the upload proceeds normally. Disabled globally from Settings → Wizard defaults → Tracker duplicate check.
Quick upload¶
POST /api/upload/quick skips most of the wizard for power users: you get a job ID and consume GET /api/upload/{job}/stream. Use it when you already have renamed files in ~/seedings/. The flow calls stream_webup directly, no unit3dprep pre-flight.
Dry-run¶
When U3DP_DRY_RUN_TRACKER=1, the wizard skips /upload but runs everything else. Useful in dev/WSL.
Upload Queue¶

Shows active torrents in the configured client (TORRENT_CLIENT in the .env: qbittorrent, transmission, rtorrent).
- Filter by name and state (downloading, seeding, paused, error).
- Auto-refresh.
- Links to local files.
Endpoint: GET /api/queue. Client credentials read from the QBIT_* / TRASM_* / RTORR_* keys of the shared .env.
Uploaded (history)¶
Table of completed uploads (GET /api/uploaded). Fields:
- Local path in
~/seedings/. - Record status:
✓ exit 0— upload completed normally.✗ exit N— failed with the given code.pending— exit code never written (see note below).manual—W_HARDLINK_ONLY=1(hardlink only, no upload).⏭ duplicate skipped— the user cancelled after the duplicate check found a torrent with the same exact byte size.duplicate_info(id, name, tracker link, etc.) is persisted in the DB for audit.
- Destination tracker.
- Timestamp.
- Size.
- Search and filter.
Stat cards on top: Total, Success, Failed, Hardlink only, Duplicate skipped.
On mobile the table uses overflow-x:auto with min-width:820px to stay readable on narrow screens.
Stuck pending records
A record stuck as pending after a successful upload means the endpoint never called update_exit_code. This is a known regression trigger on changes to quickupload.py or wizard.py — see Troubleshooting.
Search Tracker¶

Searches for a torrent on ITT (always) and on PTT/SIS (if configured with valid URL + API key in the .env).
- Tab per tracker.
- Shows link, size, seeders, freeleech, upload date.
- Handy for duplicate checks before uploading.
Endpoint: GET /api/trackers (status) + GET /api/search?q=.... Under the hood POST /api/webup/filter proxies Unit3DWebUp /filter.
Tracker status
A tracker shows as "Online" only if URL and API key are both set and the API key is not the "no_key" placeholder. All trackers appear in the sidebar even unconfigured ones (grey "Not set" badge).
Settings¶

Full editor of the shared .env right in the browser. Each Save:
- atomically writes to
$ENVPATH/.env(canonical namingTRACKER__* / TORRENT__* / PREFS__*on disk); - propagates the changed keys to Unit3DWebUp via
POST /setenv(no restart required).
Sections:
- Trackers — URL, API key, PID for ITT / PTT / SIS;
MULTI_TRACKERlist. - Metadata — TMDB, TVDB, IGDB, YouTube.
- Torrent client — type + credentials (qBit / Transmission / rTorrent).
- Image hosts — preference order + API keys for PTSCREENS, PASSIMA, IMGBB, IMGFI, etc. The list order is projected to
PREFS__<HOST>_PRIORITY(1, 2, …, 99 for hosts not in the list). - Upload options —
ANON,PERSONAL_RELEASE,NUMBER_OF_SCREENSHOTS,COMPRESS_SCSHOT,TAG_ORDER_*, etc. - Seeding Flow —
U3DP_*with effective values (env vs file) viaenv_runtime().UNIT3DUP_CONFIGis read-only. - Version — see dedicated section.
- App Auto-Update —
U3DP_SYSTEMD_UNIT, systemd user unit name used by the "Update app" button for the post-update restart. Defaultunit3dprep-web.service; override only if your unit has a different name. - Wizard Defaults — all
W_*. - Interface — language selector (IT / EN); preference saved to
localStorageand synced toU3DP_LANGviaPUT /api/settings.
Secrets masked as __SET__ — the field still appears populated. Editing other keys does not wipe secrets.
Endpoints: GET /api/settings, PUT /api/settings, GET /api/settings/fs-check.
Mobile: the left nav becomes a horizontally scrollable row; 2-col grids collapse to 1-col.
Version and auto-update¶
In Settings › Version you find two side-by-side cards:
- App — current version (
importlib.metadataorpyproject.tomlin git mode) vs latest GitHub release. - Unit3DWebUp — version installed in the webup venv vs latest on PyPI.
Each card exposes:
- Check updates button (force
POST /api/version/refresh— bypass the 10-min cache). - Install update button (visible only when
newer == true). - Changelog accordion with the GitHub release body (for app) or PyPI link (for webup).
Click Install:
UpdateProgressModalmodal withpip/gitoutput live-streamed via SSE (GET /api/version/update/{app|webup}/stream).- Backend invalidates
/api/version/info_cacheondone, restarts systemd in a transient scope (systemd-run --user --on-active=3s). - 5s countdown + browser reload; post-reload changelog popup (
unit3dprep.pendingChangeloglocalStorage key).
EventSource auto-reconnects
If an SSE endpoint closes the connection (e.g. after systemctl restart), the browser re-issues the request → endpoint re-execution. The modal calls closeSSE() on done/error to avoid the loop. If you modify the modal, keep this invariant.
Endpoints: GET /api/version/info, GET /api/version/changelog?v=X, GET /api/version/update/{app|webup}/stream (SSE), POST /api/version/refresh.
Unit3DWebUp health¶
In Settings › Trackers (or the Integrations section depending on rendering) the Unit3DWebUp card shows up:
- Status
online/offline(5s cache, ping toWEBUP_URL/setting). - Installed version.
- Ping latency in ms.
- WebSocket indicator (active connection to the bot's event channel).
- Push config button — runs
POST /api/webup/sync(push the whole.envpayload mapped to webup; useful after a backup restore or if you suspect drift). - Update button — quick link to the Version card.
Endpoints: GET /api/webup/health, POST /api/webup/sync, GET /api/webup/setting.
Logs¶
Real-time log stream via SSE. Anything uvicorn / the app writes to logbuf (unit3dprep/web/logbuf.py) shows up here, classified by:
- source —
app,wizard,quickupload,webup(replaces legacyunit3dup),system. - kind —
info/ok/warn/error/progress.
Filters persisted in localStorage (unit3dprep.logs.{hiddenSources,hiddenKinds,autoScroll} keys).
Useful for debugging without opening a shell on the VPS.
Mobile notes (≤768px)¶
The UI is responsive:
- Sidebar — closed via
translateX(-100%), scrim overlay when open. - Modal — full-bleed with 14px padding.
- Library detail —
position:fixed; inset:0overlay instead of the 360px side panel. - Settings nav — horizontal scrollable row.
- Tables —
overflow-x:auto.
Breakpoint handled by isMobile (App.tsx → Sidebar / TopBar / Library / Settings via props).
Programmatic access?¶
Every UI view consumes the JSON API under {U3DP_ROOT_PATH}/api/*. You can call it directly with a valid session cookie. See unit3dprep/web/api/*.py for the full list of routers (settings, version, webup, library, queue, uploaded, search, tmdb, fs).