Compare commits

...

3 Commits

Author SHA1 Message Date
Chocobo1
feacfb0627 WebUI: enforce using prefix operators via ESLint
NPM has a bug that cannot fetch the plugin via git protocol:
https://github.com/npm/cli/issues/2610, so fetch a tarball instead.

PR #23110.
2025-08-18 03:05:25 +08:00
xavier2k6
119a5a6e85 GHA CI: Bump some pre-commit hook revisions
* Bumped `pre-commit-hooks` -> v6.0.0
* Bumped `typos` -> v1.35.3

PR #23097.
2025-08-18 02:58:38 +08:00
Thomas Piccirello
6ef9db89f9 WebUI: Support editing tracker tier
This PR adds the ability to direct modify a tracker's tier from the WebUI. This process is notably different than the GUI, which provides arrows for increasing/decreasing a tracker's tier.

Closes #12233.
PR #22963.
2025-08-18 02:46:10 +08:00
7 changed files with 73 additions and 22 deletions

View File

@@ -19,7 +19,7 @@ repos:
- ts
- repo: https://github.com/pre-commit/pre-commit-hooks.git
rev: v5.0.0
rev: v6.0.0
hooks:
- id: check-json
name: Check JSON files
@@ -88,7 +88,7 @@ repos:
- ts
- repo: https://github.com/crate-ci/typos.git
rev: v1.32.0
rev: v1.35.3
hooks:
- id: typos
name: Check spelling (typos)

View File

@@ -6,6 +6,10 @@
* `endpoints` is an array of tracker endpoints, each with `name`, `updating`, `status`, `msg`, `bt_version`, `num_peers`, `num_peers`, `num_leeches`, `num_downloaded`, `next_announce` and `min_announce` fields
* `torrents/trackers` now returns `5` and `6` in `status` field as possible values
* `5` for `Tracker error` and `6` for `Unreachable`
* [#22963](https://github.com/qbittorrent/qBittorrent/pull/22963)
* `torrents/editTracker` endpoint now supports setting a tracker's tier via `tier` parameter
* `torrents/editTracker` endpoint always responds with a 204 when successful
* `torrents/editTracker` endpoint `origUrl` parameter renamed to `url`
## 2.12.1
* [#23031](https://github.com/qbittorrent/qBittorrent/pull/23031)

View File

@@ -1160,22 +1160,44 @@ void TorrentsController::addTrackersAction()
void TorrentsController::editTrackerAction()
{
requireParams({u"hash"_s, u"origUrl"_s, u"newUrl"_s});
requireParams({u"hash"_s, u"url"_s});
const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]);
const QString origUrl = params()[u"origUrl"_s];
const QString newUrl = params()[u"newUrl"_s];
const QString origUrl = params()[u"url"_s];
const std::optional<QString> newUrlParam = getOptionalString(params(), u"newUrl"_s);
const std::optional<QString> newTierParam = getOptionalString(params(), u"tier"_s);
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id);
if (!torrent)
throw APIError(APIErrorType::NotFound);
if (!newUrlParam && !newTierParam)
throw APIError(APIErrorType::BadParams, tr("Must specify at least one of [newUrl, tier]"));
std::optional<int> newTier;
if (newTierParam)
{
bool ok = false;
const auto number = newTierParam.value().toInt(&ok);
if (!ok)
throw APIError(APIErrorType::BadParams, tr("tier must be an integer"));
if ((number < 0) || (number > 255))
throw APIError(APIErrorType::BadParams, tr("tier must be between 0 and 255"));
newTier = number;
}
const QUrl origTrackerUrl {origUrl};
const QUrl newTrackerUrl {newUrl};
if (origTrackerUrl == newTrackerUrl)
std::optional<QUrl> newTrackerUrl;
if (newUrlParam)
{
const QUrl url = newUrlParam.value();
if (!url.isValid())
throw APIError(APIErrorType::BadParams, tr("New tracker URL is invalid"));
if (url != origTrackerUrl)
newTrackerUrl = url;
}
if (!newTrackerUrl && !newTier)
return;
if (!newTrackerUrl.isValid())
throw APIError(APIErrorType::BadParams, u"New tracker URL is invalid"_s);
const QList<BitTorrent::TrackerEntryStatus> currentTrackers = torrent->trackers();
QList<BitTorrent::TrackerEntry> entries;
@@ -1186,8 +1208,8 @@ void TorrentsController::editTrackerAction()
{
const QUrl trackerUrl {tracker.url};
if (trackerUrl == newTrackerUrl)
throw APIError(APIErrorType::Conflict, u"New tracker URL already exists"_s);
if (newTrackerUrl && (trackerUrl == newTrackerUrl))
throw APIError(APIErrorType::Conflict, tr("New tracker URL already exists"));
BitTorrent::TrackerEntry entry
{
@@ -1197,18 +1219,23 @@ void TorrentsController::editTrackerAction()
if (trackerUrl == origTrackerUrl)
{
const bool isTrackerTierChanged = newTier && (tracker.tier != newTier);
if (!newTrackerUrl && !isTrackerTierChanged)
return;
match = true;
entry.url = newTrackerUrl.toString();
if (newTrackerUrl)
entry.url = newTrackerUrl.value().toString();
if (newTier)
entry.tier = newTier.value();
}
entries.append(entry);
}
if (!match)
throw APIError(APIErrorType::Conflict, u"Tracker not found"_s);
throw APIError(APIErrorType::Conflict, tr("Tracker not found"));
torrent->replaceTrackers(entries);
torrent->forceReannounce();
setResult(QString());
}
void TorrentsController::removeTrackersAction()
@@ -1246,7 +1273,7 @@ void TorrentsController::addPeersAction()
}
if (peerList.isEmpty())
throw APIError(APIErrorType::BadParams, u"No valid peers were specified"_s);
throw APIError(APIErrorType::BadParams, tr("No valid peers were specified"));
QJsonObject results;

View File

@@ -1,6 +1,7 @@
import Globals from "globals";
import Html from "eslint-plugin-html";
import Js from "@eslint/js";
import PluginQbtWebUI from "eslint-plugin-qbt-webui";
import PreferArrowFunctions from "eslint-plugin-prefer-arrow-functions";
import Stylistic from "@stylistic/eslint-plugin";
import * as RegexpPlugin from "eslint-plugin-regexp";
@@ -23,6 +24,7 @@ export default [
},
plugins: {
Html,
PluginQbtWebUI,
PreferArrowFunctions,
RegexpPlugin,
Stylistic
@@ -41,6 +43,7 @@ export default [
"prefer-template": "error",
"radix": "error",
"require-await": "error",
"PluginQbtWebUI/prefix-inc-dec-operators": "error",
"PreferArrowFunctions/prefer-arrow-functions": "error",
"Stylistic/no-extra-semi": "error",
"Stylistic/no-mixed-operators": [

View File

@@ -15,6 +15,7 @@
"eslint": "*",
"eslint-plugin-html": "*",
"eslint-plugin-prefer-arrow-functions": "*",
"eslint-plugin-qbt-webui": "https://github.com/Chocobo1/eslint-plugin-qbt-webui/tarball/v1",
"eslint-plugin-regexp": "*",
"happy-dom": "*",
"html-validate": "*",

View File

@@ -26,9 +26,11 @@
const searchParams = new URLSearchParams(window.location.search);
const currentUrl = searchParams.get("url");
if (currentUrl === null)
const currentTier = searchParams.get("tier");
if ((currentUrl === null) || (currentTier === null))
return;
document.getElementById("trackerTier").value = currentTier;
document.getElementById("trackerUrl").value = currentUrl;
document.getElementById("trackerUrl").focus();
@@ -40,8 +42,9 @@
method: "POST",
body: new URLSearchParams({
hash: searchParams.get("hash"),
origUrl: currentUrl,
newUrl: document.getElementById("trackerUrl").value
url: currentUrl,
newUrl: document.getElementById("trackerUrl").value,
tier: document.getElementById("trackerTier").value
})
})
.then((response) => {
@@ -63,6 +66,11 @@
<input type="text" id="trackerUrl" style="width: 90%;">
</div>
<br>
<label for="trackerTier">QBT_TR(Tier:)QBT_TR[CONTEXT=TrackerListWidget]</label>
<div style="text-align: center; padding-top: 10px;">
<input type="number" id="trackerTier" style="width: 90%; max-width: 100px;" min="0" max="255">
</div>
<br>
<input type="button" value="QBT_TR(Edit)QBT_TR[CONTEXT=HttpServer]" id="editTrackerButton">
</div>
</body>

View File

@@ -255,13 +255,21 @@ window.qBittorrent.PropTrackers ??= (() => {
if (current_hash.length === 0)
return;
const trackerUrl = encodeURIComponent(torrentTrackersTable.selectedRowsIds()[0]);
const tracker = torrentTrackersTable.getRow(torrentTrackersTable.getSelectedRowId());
const contentURL = new URL("edittracker.html", window.location);
contentURL.search = new URLSearchParams({
v: "${CACHEID}",
hash: current_hash,
url: tracker.full_data.url,
tier: tracker.full_data.tier
});
new MochaUI.Window({
id: "trackersPage",
icon: "images/qbittorrent-tray.svg",
title: "QBT_TR(Tracker editing)QBT_TR[CONTEXT=TrackerListWidget]",
loadMethod: "iframe",
contentURL: `edittracker.html?v=${CACHEID}&hash=${current_hash}&url=${trackerUrl}`,
contentURL: contentURL.toString(),
scrollbars: true,
resizable: false,
maximizable: false,
@@ -269,7 +277,7 @@ window.qBittorrent.PropTrackers ??= (() => {
paddingVertical: 0,
paddingHorizontal: 0,
width: window.qBittorrent.Dialog.limitWidthToViewport(500),
height: 150,
height: 200,
onCloseComplete: () => {
updateData();
}