Skip to content

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:

unit3dprep-web

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

Media Library with TMDB posters, audio language badges and detail panel

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.
  • Language filter — dropdown listing the languages found in the current category, split between audio and subtitles with a per-language item count; shows only media carrying the selected language. Cleared with the × or by switching category.
  • 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 pymediainfo scan 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

Upload Wizard — TMDB step with title preview and metadata

Step-by-step flow. Alternative to the CLI with persistent history, graphical progress bar and live SSE logs.

Typical steps:

  1. Select source — file or folder under U3DP_MEDIA_ROOT.
  2. Audio check — if W_AUDIO_CHECK, scans the tracks.
  3. TMDB — search or ID entry. If W_AUTO_TMDB, auto-fetch from an existing ID.
  4. Name preview — editable; if W_CONFIRM_NAMES is OFF, skips the confirmation.
  5. 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.
  6. Hardlink — into U3DP_SEEDINGS_DIR/.unit3dprep/<jobid>/... (per-upload sandbox, see Unit3DWebUp integration). If W_HARDLINK_ONLY, stops here and records exit code 0.
  7. Upload — the HTTP bridge runs setenv → scan → maketorrent → upload → seed against 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%.
  8. History writeupdate_exit_code(seeding_path, code) persists into U3DP_DB_PATH (also from wizard_finish when W_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_path lands in uploaded_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

Upload Queue — qBittorrent queue with progress bars and seeding status

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).
    • manualW_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

Search Tracker — ITT results with type/resolution tags and seeder counts

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

Settings — Preferences panel with upload behaviour toggles and screenshot options

Full editor of the shared .env right in the browser. Each Save:

  1. atomically writes to $ENVPATH/.env (canonical naming TRACKER__* / TORRENT__* / PREFS__* on disk);
  2. propagates the changed keys to Unit3DWebUp via POST /setenv (no restart required).

Sections:

  • Trackers — URL, API key, PID for ITT / PTT / SIS; MULTI_TRACKER list.
  • 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 optionsANON, PERSONAL_RELEASE, NUMBER_OF_SCREENSHOTS, COMPRESS_SCSHOT, TAG_ORDER_*, etc.
  • Seeding FlowU3DP_* with effective values (env vs file) via env_runtime(). UNIT3DUP_CONFIG is read-only.
  • Radarr / Sonarr — URL and API key for both instances, with a connection test. See the dedicated section.
  • Version — see dedicated section.
  • App Auto-UpdateU3DP_SYSTEMD_UNIT, systemd user unit name used by the "Update app" button for the post-update restart. Default unit3dprep-web.service; override only if your unit has a different name.
  • Wizard Defaults — all W_*.
  • Interface — language selector (IT / EN); preference saved to localStorage and synced to U3DP_LANG via PUT /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.metadata or pyproject.toml in 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:

  1. UpdateProgressModal modal with pip / git output live-streamed via SSE (GET /api/version/update/{app|webup}/stream).
  2. Backend invalidates /api/version/info _cache on done, restarts systemd in a transient scope (systemd-run --user --on-active=3s).
  3. 5s countdown + browser reload; post-reload changelog popup (unit3dprep.pendingChangelog localStorage 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 to WEBUP_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 .env payload 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:

  • sourceapp, wizard, quickupload, webup (replaces legacy unit3dup), system.
  • kindinfo / 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 detailposition:fixed; inset:0 overlay instead of the 360px side panel.
  • Settings nav — horizontal scrollable row.
  • Tablesoverflow-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).


Radarr and Sonarr

Set the URL and API key under Settings → Radarr / Sonarr and use Test connection to check them. The test queries the saved credentials, so it stays disabled while there are unsaved changes. Radarr and Sonarr must see the same filesystem as unit3dprep: library items are matched to their records by path.

With the integration on, the library shows a monitored badge on whatever Radarr or Sonarr is still searching for. The removal button appears only there:

  • Movie — in the detail panel.
  • Series — in the detail panel; also switches off every season and episode.
  • Season — icon in the season header; also switches off its episodes.
  • Episode — icon on the episode row.

Monitoring can only be removed, never re-enabled.

The series card still shows as monitored after every season is cleared

Sonarr keeps the series.monitored flag independent of the individual season flags: if you remove monitoring season by season, the series can keep showing as monitored even with zero active seasons left. That's correct Sonarr behaviour, not a bug — use the series-level removal button to switch that flag off too.

To work in bulk: filter by Language, enter multi-select, pick the items and hit Stop monitoring. Items Radarr and Sonarr don't know about, or that are already unmonitored, are excluded automatically; movies go off in a single call, series run their cascade with several in parallel (bounded, so Radarr/Sonarr aren't hit with hundreds of requests at once), and a toast reports how many succeeded and how many failed.

If Radarr or Sonarr are unreachable the library keeps working as usual and a notice appears at the top: badges and buttons stay hidden until the service is back.