Conversation
- Implemented GetTorrentWebSeeds handler in the API to retrieve HTTP sources for torrents. - Added WebSeedsTable component to display web seeds in the UI with search functionality. - Updated TorrentDetailsPanel to include a new tab for web seeds. - Enhanced API client to support fetching web seeds from the server.
WalkthroughAdds a new backend API endpoint and SyncManager method to return a torrent's HTTP web seeds, wires the route, extends the OpenAPI spec and client API, and integrates a new "Web Seeds" tab and WebSeedsTable component in the torrent details UI. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser as Client (UI)
participant API as API Server (handler)
participant Sync as SyncManager
participant QBT as qBittorrent Client (pool)
Browser->>API: GET /instances/{id}/torrents/{hash}/webseeds
API->>API: validate instanceID & hash
API->>Sync: GetTorrentWebSeeds(ctx, instanceID, hash)
Sync->>QBT: acquire client from pool
Sync->>QBT: GetTorrentsWebSeedsCtx(ctx, hash)
QBT-->>Sync: returns []WebSeed or error
Sync-->>API: []WebSeed or error
API-->>Browser: 200 JSON []WebSeed (or 4xx/5xx on error)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
web/src/components/torrents/details/WebSeedsTable.tsx (1)
42-52: Optional: Consider URL-safe truncation for incognito masking.Line 47 slices the pathname at character 20, which could truncate in the middle of a URL-encoded sequence (e.g.,
%2in%20), potentially displaying broken encoding. While this is cosmetic and only affects the masked display, you might consider truncating at a safe boundary.Note: The Biome linter warning about "wrapping comments" is a false positive—the
//on line 47 is part of the URL protocol string, not a JSX comment.
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
internal/api/handlers/torrents.go(1 hunks)internal/api/server.go(1 hunks)internal/qbittorrent/sync_manager.go(1 hunks)web/src/components/torrents/TorrentDetailsPanel.tsx(5 hunks)web/src/components/torrents/details/WebSeedsTable.tsx(1 hunks)web/src/components/torrents/details/index.ts(1 hunks)web/src/lib/api.ts(2 hunks)web/src/types/index.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: finevan
Repo: autobrr/qui PR: 677
File: web/src/components/torrents/AddTorrentDialog.tsx:496-504
Timestamp: 2025-12-03T18:11:08.682Z
Learning: In the AddTorrentDialog component (web/src/components/torrents/AddTorrentDialog.tsx), temporary path settings (useDownloadPath/downloadPath) should be applied per-torrent rather than updating global instance preferences. The UI/UX is designed to suggest that these options apply to the individual torrent being added.
📚 Learning: 2025-11-06T11:59:21.390Z
Learnt from: Audionut
Repo: autobrr/qui PR: 553
File: web/src/components/torrents/TorrentTableOptimized.tsx:1510-1515
Timestamp: 2025-11-06T11:59:21.390Z
Learning: In the qui project, the API layer in web/src/lib/api.ts normalizes backend snake_case responses to camelCase for frontend consumption. For CrossSeed search results, the backend's download_url field is transformed to downloadUrl in the searchCrossSeedTorrent method, so frontend code should always use the camelCase variant (result.downloadUrl).
Applied to files:
web/src/lib/api.ts
📚 Learning: 2025-11-28T20:32:30.126Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go:209-212
Timestamp: 2025-11-28T20:32:30.126Z
Learning: Repo: autobrr/qui PR: 641
File: internal/services/crossseed/service.go
Learning: The cross-seed recheck-resume worker intentionally runs for the process lifetime and keys pending entries by hash only. This is acceptable under the current constraint that background seeded-search runs operate on a single instance at a time; graceful shutdown and instanceID|hash keying are deferred by design.
Applied to files:
internal/qbittorrent/sync_manager.go
📚 Learning: 2025-11-21T21:11:50.633Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 625
File: internal/qbittorrent/sync_manager.go:1112-1123
Timestamp: 2025-11-21T21:11:50.633Z
Learning: In internal/qbittorrent/sync_manager.go, the GetCachedFilesBatch interface documentation (around line 39-40) should specify "uppercase hex" instead of "lowercase hex" to match the actual normalization practice used throughout the codebase (e.g., normalizeHash in internal/services/crossseed/service.go uses strings.ToUpper, and hash filtering uses uppercase).
Applied to files:
internal/qbittorrent/sync_manager.go
📚 Learning: 2025-11-25T22:46:03.762Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 632
File: internal/backups/service.go:1401-1404
Timestamp: 2025-11-25T22:46:03.762Z
Learning: In qui's backup service (internal/backups/service.go), background torrent downloads initiated during manifest import intentionally use a fire-and-forget pattern with the shared service context (s.ctx). Per-run cancellation is not needed, as orphaned downloads completing after run deletion are considered harmless and acceptable. This design prioritizes simplicity over per-run lifecycle management for background downloads.
Applied to files:
internal/qbittorrent/sync_manager.go
📚 Learning: 2025-12-03T18:11:08.682Z
Learnt from: finevan
Repo: autobrr/qui PR: 677
File: web/src/components/torrents/AddTorrentDialog.tsx:496-504
Timestamp: 2025-12-03T18:11:08.682Z
Learning: In the AddTorrentDialog component (web/src/components/torrents/AddTorrentDialog.tsx), temporary path settings (useDownloadPath/downloadPath) should be applied per-torrent rather than updating global instance preferences. The UI/UX is designed to suggest that these options apply to the individual torrent being added.
Applied to files:
web/src/components/torrents/TorrentDetailsPanel.tsx
🧬 Code graph analysis (4)
web/src/lib/api.ts (1)
web/src/types/index.ts (1)
WebSeed(499-501)
internal/api/handlers/torrents.go (1)
internal/api/handlers/helpers.go (2)
RespondError(34-38)RespondJSON(22-31)
internal/qbittorrent/sync_manager.go (1)
web/src/types/index.ts (1)
WebSeed(499-501)
web/src/components/torrents/TorrentDetailsPanel.tsx (4)
web/src/components/torrents/details/WebSeedsTable.tsx (1)
WebSeedsTable(30-173)web/src/components/torrents/details/index.ts (1)
WebSeedsTable(12-12)web/src/lib/linkUtils.tsx (1)
renderTextWithLinks(17-53)web/src/lib/utils.ts (1)
copyTextToClipboard(186-205)
🪛 Biome (2.1.2)
web/src/components/torrents/details/WebSeedsTable.tsx
[error] 47-47: Wrap comments inside children within braces.
Unsafe fix: Wrap the comments with braces
(lint/suspicious/noCommentText)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (11)
internal/api/handlers/torrents.go (1)
1155-1180: LGTM! Handler implementation follows established patterns.The GetTorrentWebSeeds handler is well-structured and consistent with other torrent detail handlers in the file (e.g., GetTorrentTrackers, GetTorrentProperties). Proper input validation, error handling, and logging are all in place.
web/src/components/torrents/details/WebSeedsTable.tsx (2)
63-80: LGTM! Efficient filtering and clipboard logic.The filtered data memoization and copy handler are well-implemented with proper dependencies and incognito mode guards.
82-172: LGTM! Clean UI with proper loading and empty states.The component provides a good user experience with:
- Loading spinner while fetching
- Empty state messaging
- Live search with clear button
- Count indicators (filtered/total)
- Context menu for actions (properly disabled in incognito mode)
web/src/components/torrents/TorrentDetailsPanel.tsx (3)
37-37: LGTM! WebSeedsTable properly integrated.The import and tab definition follow established patterns and are positioned logically among other torrent detail tabs.
Also applies to: 50-50
346-354: LGTM! Web seeds query properly configured.The data fetching logic mirrors the trackers pattern with appropriate cache times. Unlike peers, web seeds are relatively static, so the absence of a refetch interval is correct.
714-719: LGTM! Webseeds tab UI is well-integrated.Both horizontal and vertical layouts are properly implemented:
- Horizontal layout delegates to the WebSeedsTable component
- Vertical layout follows the same pattern as peers and trackers tabs
- Loading, empty, and data states are handled consistently
- Incognito mode properly masks URLs and disables copy actions
- Context menu integration matches existing patterns
Also applies to: 1378-1434
internal/api/server.go (1)
387-401: Web seeds route wiring matches existing torrent detail routesAdding
GET /webseedsalongside/peers,/files, etc. is consistent with the existing nested routing structure and handler usage; no issues spotted.web/src/types/index.ts (1)
499-501: WebSeed type is minimal and appropriately scopedUsing a single
url: stringfield aligns with the backend payload and keeps the public type surface focused; looks good.web/src/components/torrents/details/index.ts (1)
6-12: Re-exporting WebSeedsTable keeps the details module cohesiveAdding
WebSeedsTableto this barrel export matches existing conventions and simplifies imports for consumers likeTorrentDetailsPanel.web/src/lib/api.ts (1)
50-50: Web seeds API surface is consistent with existing torrent detail methodsImporting
WebSeedand exposinggetTorrentWebSeeds(instanceId, hash)via the sharedrequesthelper fits the existing pattern used for properties/trackers/peers; the endpoint path and return type line up cleanly with the new backend route andWebSeedshape.Also applies to: 1154-1156
internal/qbittorrent/sync_manager.go (1)
1328-1341: GetTorrentWebSeeds implementation matches existing SyncManager patternsThe method’s structure (client lookup, wrapped error messages, simple passthrough to
GetTorrentsWebSeedsCtx) is in line with other read-only helpers likeGetTorrentPropertiesandGetTorrentTrackers; no functional issues seen.
The TestAllEndpointsDocumented test was failing because the new
GET /api/instances/{instanceID}/torrents/{hash}/webseeds endpoint
was not documented in the OpenAPI spec.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
internal/web/swagger/openapi.yaml (1)
3624-3630: Consider adding format and required field specifications.The WebSeed schema would benefit from:
- Adding
format: urito the url field to indicate it's a URI- Specifying
required: [url]to mark the url field as requiredApply this diff to enhance the schema:
WebSeed: type: object + required: + - url properties: url: type: string + format: uri description: HTTP source URL for the torrent
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/web/swagger/openapi.yaml(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (1)
internal/web/swagger/openapi.yaml (1)
1354-1376: LGTM! Endpoint follows established patterns.The new web seeds endpoint is well-structured and consistent with other torrent detail endpoints in the API. The response codes and parameter references are appropriate.
Conditionally render the HTTP Sources tab only when the selected torrent has web seeds. Fetches web seeds data upfront (instead of lazy loading on tab click) to determine tab visibility. Automatically redirects to general tab if switching to a torrent without web seeds while on the HTTP Sources tab. Also adds required constraint to WebSeed schema in OpenAPI spec.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
web/src/components/torrents/TorrentDetailsPanel.tsx (2)
1408-1415: Consider consistent incognito masking.The vertical layout completely masks URLs (
"***masked***"), while the WebSeedsTable (horizontal layout) shows partial information (protocol//***masked***pathname...). For consistency and better UX, consider applying the same masking logic in both layouts.Apply this diff to match the horizontal layout's masking approach:
- <p className="font-mono text-xs break-all"> - {incognitoMode ? "***masked***" : renderTextWithLinks(webseed.url)} - </p> + <p className="font-mono text-xs break-all"> + {incognitoMode ? (() => { + try { + const parsed = new URL(webseed.url) + return `${parsed.protocol}//***masked***${parsed.pathname.slice(0, 20)}...` + } catch { + return "***masked***" + } + })() : renderTextWithLinks(webseed.url)} + </p>
1418-1429: Consider using the existing error handling helper.The inline copy implementation doesn't handle errors, while the
copyToClipboardhelper (lines 112-119) provides consistent error handling used elsewhere in this file. Consider refactoring for consistency.Apply this diff:
<ContextMenuContent> <ContextMenuItem - onClick={() => { - if (!incognitoMode) { - copyTextToClipboard(webseed.url) - toast.success("URL copied to clipboard") - } - }} + onClick={() => !incognitoMode && copyToClipboard(webseed.url, "URL")} disabled={incognitoMode} >
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/web/swagger/openapi.yaml(2 hunks)web/src/components/torrents/TorrentDetailsPanel.tsx(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/web/swagger/openapi.yaml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-03T18:11:08.682Z
Learnt from: finevan
Repo: autobrr/qui PR: 677
File: web/src/components/torrents/AddTorrentDialog.tsx:496-504
Timestamp: 2025-12-03T18:11:08.682Z
Learning: In the AddTorrentDialog component (web/src/components/torrents/AddTorrentDialog.tsx), temporary path settings (useDownloadPath/downloadPath) should be applied per-torrent rather than updating global instance preferences. The UI/UX is designed to suggest that these options apply to the individual torrent being added.
Applied to files:
web/src/components/torrents/TorrentDetailsPanel.tsx
🧬 Code graph analysis (1)
web/src/components/torrents/TorrentDetailsPanel.tsx (5)
web/src/lib/api.ts (1)
api(1716-1716)web/src/components/ui/tabs.tsx (2)
TabsTrigger(69-69)TabsContent(69-69)web/src/components/torrents/details/index.ts (1)
WebSeedsTable(12-12)web/src/components/torrents/details/WebSeedsTable.tsx (1)
WebSeedsTable(30-173)web/src/lib/utils.ts (1)
copyTextToClipboard(186-205)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (5)
web/src/components/torrents/TorrentDetailsPanel.tsx (5)
37-37: LGTM!The import follows the established pattern for other detail components.
50-50: LGTM!The tab value is correctly added to the type-safe array.
346-354: LGTM!The query setup follows the same pattern as the trackers query, fetching data upfront to determine tab visibility. This is a reasonable trade-off for better UX.
356-361: LGTM!The redirection logic correctly handles the edge case where web seeds become unavailable while viewing the tab.
721-728: LGTM!The conditional rendering ensures the tab only appears when relevant data is available.
This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.10.0` -> `v1.11.0` | --- ### Release Notes <details> <summary>autobrr/qui (ghcr.io/autobrr/qui)</summary> ### [`v1.11.0`](https://github.com/autobrr/qui/releases/tag/v1.11.0) [Compare Source](autobrr/qui@v1.10.0...v1.11.0) #### Changelog ##### New Features - [`6e65de4`](autobrr/qui@6e65de4): feat(torrents): add "não registrado" to unregistered status ([#​794](autobrr/qui#794)) ([@​fabricionaweb](https://github.com/fabricionaweb)) - [`ac5f8f3`](autobrr/qui@ac5f8f3): feat(torrents): add web seeds table ([#​808](autobrr/qui#808)) ([@​s0up4200](https://github.com/s0up4200)) - [`24559c9`](autobrr/qui@24559c9): feat(web): add Size default sort option to Tracker Breakdown table settings ([#​786](autobrr/qui#786)) ([@​thesecretlifeofabunny](https://github.com/thesecretlifeofabunny)) ##### Bug Fixes - [`69ed1a3`](autobrr/qui@69ed1a3): fix(api): respect baseURL for path autocompletion cap ([#​798](autobrr/qui#798)) ([@​Ryu481](https://github.com/Ryu481)) - [`0a721d0`](autobrr/qui@0a721d0): fix(crossseed): add verification and retry for async file renames ([#​789](autobrr/qui#789)) ([@​s0up4200](https://github.com/s0up4200)) - [`e9fcbda`](autobrr/qui@e9fcbda): fix(crossseed): pass source filters through to FindCandidates ([#​802](autobrr/qui#802)) ([@​s0up4200](https://github.com/s0up4200)) - [`b4f1ffa`](autobrr/qui@b4f1ffa): fix(crossseed): require strict HDR and Collection matching ([#​799](autobrr/qui#799)) ([@​s0up4200](https://github.com/s0up4200)) - [`4f3365b`](autobrr/qui@4f3365b): fix(sync): edited trackers no longer appear under old domain in sidebar ([#​792](autobrr/qui#792)) ([@​s0up4200](https://github.com/s0up4200)) - [`fcb081e`](autobrr/qui@fcb081e): fix(web): move global stats to bottom of torrents page ([#​800](autobrr/qui#800)) ([@​s0up4200](https://github.com/s0up4200)) - [`13b40b5`](autobrr/qui@13b40b5): fix(web): prevent Edit Tracker Name dialog overflow ([#​797](autobrr/qui#797)) ([@​s0up4200](https://github.com/s0up4200)) - [`8e3b352`](autobrr/qui@8e3b352): fix(web): replace completion filter inputs with MultiSelect dropdowns ([#​791](autobrr/qui#791)) ([@​s0up4200](https://github.com/s0up4200)) - [`adfd5bb`](autobrr/qui@adfd5bb): fix(web): restore piece size display in torrent details panel ([#​790](autobrr/qui#790)) ([@​s0up4200](https://github.com/s0up4200)) - [`5d97b49`](autobrr/qui@5d97b49): fix(web): sort trackers by display name in filter sidebar ([#​810](autobrr/qui#810)) ([@​s0up4200](https://github.com/s0up4200)) - [`2fad4e8`](autobrr/qui@2fad4e8): fix(web): use total counts for Seeds/Peers column filtering ([#​796](autobrr/qui#796)) ([@​s0up4200](https://github.com/s0up4200)) ##### Other Changes - [`1bf1b6e`](autobrr/qui@1bf1b6e): chore(crossseed): add debug logging for RSS and webhook source filters ([#​795](autobrr/qui#795)) ([@​s0up4200](https://github.com/s0up4200)) - [`95e4bc0`](autobrr/qui@95e4bc0): chore(deps): bump github.com/expr-lang/expr from 1.17.6 to 1.17.7 ([#​809](autobrr/qui#809)) ([@​dependabot](https://github.com/dependabot)\[bot]) **Full Changelog**: <autobrr/qui@v1.10.0...v1.11.0> #### Docker images - `docker pull ghcr.io/autobrr/qui:v1.11.0` - `docker pull ghcr.io/autobrr/qui:latest` #### What to do next? - Join our [Discord server](https://discord.autobrr.com/qui) Thank you for using qui! </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4zOS4xIiwidXBkYXRlZEluVmVyIjoiNDIuMzkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW1hZ2UiXX0=--> Reviewed-on: https://gitea.alexlebens.dev/alexlebens/infrastructure/pulls/2734 Co-authored-by: Renovate Bot <renovate-bot@alexlebens.net> Co-committed-by: Renovate Bot <renovate-bot@alexlebens.net>
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.