diff --git a/server/torrent-clients/index.ts b/server/torrent-clients/index.ts index 01cfa0e..18c5d8d 100644 --- a/server/torrent-clients/index.ts +++ b/server/torrent-clients/index.ts @@ -41,7 +41,10 @@ export interface TorrentInfo { progress : number size : number downloaded: number + uploaded : number + ratio : number speed : number + upspeed : number eta : number save_path : string category : string diff --git a/server/torrent-clients/qbittorrent.ts b/server/torrent-clients/qbittorrent.ts index 4052d35..2395d86 100644 --- a/server/torrent-clients/qbittorrent.ts +++ b/server/torrent-clients/qbittorrent.ts @@ -93,7 +93,10 @@ const QB: TorrentClientDriver = { progress : Math.round(t.progress * 100), size : t.size, downloaded: t.downloaded, + uploaded : t.uploaded ?? 0, + ratio : Math.round((t.ratio ?? 0) * 100) / 100, speed : t.dlspeed, + upspeed : t.upspeed ?? 0, eta : t.eta ?? -1, save_path : t.save_path, category : t.category ?? '', diff --git a/server/torrent-clients/rtorrent.ts b/server/torrent-clients/rtorrent.ts index 68b3aea..e7cba3d 100644 --- a/server/torrent-clients/rtorrent.ts +++ b/server/torrent-clients/rtorrent.ts @@ -178,11 +178,13 @@ const RT: TorrentClientDriver = { 'd.complete', 'd.size_bytes', 'd.bytes_done', + 'd.up.total', // total uploadé + 'd.ratio', // ratio x1000 'd.down.rate', + 'd.up.rate', 'd.left_bytes', - 'd.down.rate', 'd.directory', - 'd.custom1', // label rTorrent + 'd.custom1', ] const rows = await d_multicall(config, 'main', methods) @@ -190,10 +192,11 @@ const RT: TorrentClientDriver = { return rows .map(r => { const [hash, name, isOpen, isActive, isChecking, isComplete, - size, downloaded, speed, leftBytes, , directory, label] = r + size, downloaded, uploadedTotal, ratioRaw, + dlSpeed, ulSpeed, leftBytes, directory, label] = r const progress = size > 0 ? Math.min(100, Math.round(((size - leftBytes) / size) * 100)) : 0 - const eta = speed > 0 && leftBytes > 0 ? Math.round(leftBytes / speed) : -1 + const eta = Number(dlSpeed) > 0 && Number(leftBytes) > 0 ? Math.round(Number(leftBytes) / Number(dlSpeed)) : -1 return { hash : String(hash).toLowerCase(), @@ -202,7 +205,10 @@ const RT: TorrentClientDriver = { progress, size : Number(size), downloaded: Number(downloaded), - speed : Number(speed), + uploaded : Number(uploadedTotal ?? 0), + ratio : Math.round((Number(ratioRaw ?? 0) / 1000) * 100) / 100, + speed : Number(dlSpeed), + upspeed : Number(ulSpeed ?? 0), eta, save_path : String(directory), category : String(label ?? ''), diff --git a/server/torrent-clients/synology-ds.ts b/server/torrent-clients/synology-ds.ts index 0178925..bd03690 100644 --- a/server/torrent-clients/synology-ds.ts +++ b/server/torrent-clients/synology-ds.ts @@ -108,11 +108,6 @@ const DS: TorrentClientDriver = { const tasks: any[] = data?.tasks ?? [] return tasks - .filter(t => { - if (!category) return true - // Synology n'a pas de labels natifs — on filtre par destination si elle contient la catégorie - return (t.additional?.detail?.destination as string)?.includes(String(category)) - }) .map(t => { const transfer = t.additional?.transfer ?? {} const size = t.size ?? 0 @@ -124,10 +119,13 @@ const DS: TorrentClientDriver = { progress : size > 0 ? Math.min(100, Math.round((dl / size) * 100)) : 0, size, downloaded: dl, + uploaded : transfer.size_uploaded ?? 0, + ratio : size > 0 ? Math.round(((transfer.size_uploaded ?? 0) / size) * 100) / 100 : 0, speed : transfer.speed_download ?? 0, + upspeed : transfer.speed_upload ?? 0, eta : -1, save_path : t.additional?.detail?.destination ?? '', - category : '', + category : category ?? '', } satisfies TorrentInfo }) }, diff --git a/server/torrent-clients/transmission.ts b/server/torrent-clients/transmission.ts index 89e2c8b..c2273ed 100644 --- a/server/torrent-clients/transmission.ts +++ b/server/torrent-clients/transmission.ts @@ -94,7 +94,8 @@ const TR: TorrentClientDriver = { async list(config, category) { const fields = [ 'hashString', 'name', 'status', 'percentDone', 'totalSize', - 'downloadedEver', 'rateDownload', 'eta', 'downloadDir', 'labels', + 'downloadedEver', 'uploadedEver', 'uploadRatio', + 'rateDownload', 'rateUpload', 'eta', 'downloadDir', 'labels', ] const data = await trRequest(config, 'torrent-get', { fields }) const torrents: any[] = data.arguments?.torrents ?? [] @@ -111,7 +112,10 @@ const TR: TorrentClientDriver = { progress : Math.round(t.percentDone * 100), size : t.totalSize, downloaded: t.downloadedEver, + uploaded : t.uploadedEver ?? 0, + ratio : Math.round((t.uploadRatio ?? 0) * 100) / 100, speed : t.rateDownload, + upspeed : t.rateUpload ?? 0, eta : t.eta ?? -1, save_path : t.downloadDir, category : t.labels?.[0] ?? '', diff --git a/server/torrent-clients/utorrent.ts b/server/torrent-clients/utorrent.ts index 3437bc8..a345185 100644 --- a/server/torrent-clients/utorrent.ts +++ b/server/torrent-clients/utorrent.ts @@ -62,7 +62,7 @@ function mapTorrent(t: any[]): TorrentInfo { // [hash, status, name, size, progress, downloaded, uploaded, ratio, // ul_speed, dl_speed, eta, label, peers_connected, peers_swarm, // seeds_connected, seeds_swarm, availability, queue_order, remaining] - const [hash, status, name, size, progress, downloaded, , , , dlSpeed, eta, label] = t + const [hash, status, name, size, progress, downloaded, uploaded, ratio, ulSpeed, dlSpeed, eta, label] = t return { hash : hash.toLowerCase(), name, @@ -70,9 +70,12 @@ function mapTorrent(t: any[]): TorrentInfo { progress : Math.min(100, Math.round(progress / 10)), size, downloaded, + uploaded : uploaded ?? 0, + ratio : Math.round(((ratio ?? 0) / 1000) * 100) / 100, // uTorrent ratio est x1000 speed : dlSpeed ?? 0, + upspeed : ulSpeed ?? 0, eta : eta ?? -1, - save_path : '', // récupéré séparément via torrent properties si besoin + save_path : '', category : label ?? '', } } diff --git a/src/views/DownloadsView.vue b/src/views/DownloadsView.vue index 7ed14ca..b9ec283 100644 --- a/src/views/DownloadsView.vue +++ b/src/views/DownloadsView.vue @@ -39,7 +39,7 @@ - +
@@ -51,6 +51,7 @@ />
+
-
+ + +
+ +
+

Colonnes affichées

+ +
+
- +
-
- Masquer importés - +
+ + + +
@@ -162,12 +202,12 @@ {{ stateBadge(t.state).label }} - {{ t.client_name }} + {{ t.client_name }}

{{ clampedProgress(t) }}%

-

{{ formatSize(t.downloaded) }} / {{ formatSize(t.size) }}

+

{{ formatSize(t.downloaded) }} / {{ formatSize(t.size) }}

@@ -185,14 +225,29 @@
+ ↓ {{ formatSpeed(t.speed) }} ETA {{ formatEta(t.eta) }} - Complété + + + + État non remonté par le client
-
@@ -256,19 +311,41 @@ const importing = ref>({}) const importingAll = ref(false) const activeTab = ref<'active' | 'done'>('active') const hideImported = ref(false) -const search = ref('') -const sortOpen = ref(false) -const activeSort = ref<'none' | 'name' | 'size' | 'progress' | 'state'>('none') -const sortDir = ref<'asc' | 'desc'>('asc') -const sortRef = ref(null) +const seedingOnly = ref(false) +const search = ref('') +const sortOpen = ref(false) +const colOpen = ref(false) +const activeSort = ref<'none' | 'name' | 'size' | 'progress' | 'state' | 'ratio'>('none') +const sortDir = ref<'asc' | 'desc'>('asc') +const sortRef = ref(null) +const colRef = ref(null) + +// Colonnes visibles +const columns = ref({ + client : true, + size : true, + ratio : true, + uploaded: true, + upspeed : true, +}) + +const columnOptions = [ + { key: 'client', label: 'Client' }, + { key: 'size', label: 'Taille / Téléchargé' }, + { key: 'ratio', label: 'Ratio' }, + { key: 'uploaded', label: 'Uploadé (total)' }, + { key: 'upspeed', label: 'Vitesse upload' }, +] onClickOutside(sortRef, () => { sortOpen.value = false }) +onClickOutside(colRef, () => { colOpen.value = false }) const sortOptions = [ { label: 'Nom', value: 'name' }, { label: 'Taille', value: 'size' }, { label: 'Progression', value: 'progress' }, { label: 'État', value: 'state' }, + { label: 'Ratio', value: 'ratio' }, ] function setSort(val: typeof activeSort.value) { @@ -283,7 +360,6 @@ function setSort(val: typeof activeSort.value) { let pollInterval: ReturnType | null = null -// FIX : unknown retiré de activeTorrents, ajouté dans doneTorrents const activeTorrents = computed(() => torrents.value.filter(t => ['downloading', 'paused', 'checking', 'error'].includes(t.state)) ) @@ -295,17 +371,18 @@ const doneTorrents = computed(() => const visibleTorrents = computed(() => { let list = activeTab.value === 'active' ? activeTorrents.value - : hideImported.value - ? doneTorrents.value.filter(t => t.organizeState !== 'done') - : doneTorrents.value + : doneTorrents.value + + if (activeTab.value === 'done') { + if (seedingOnly.value) list = list.filter(t => t.state === 'seeding') + if (hideImported.value) list = list.filter(t => t.organizeState !== 'done') + } - // Recherche if (search.value.trim()) { const q = search.value.toLowerCase() list = list.filter(t => t.name.toLowerCase().includes(q)) } - // Tri if (activeSort.value !== 'none') { const dir = sortDir.value === 'asc' ? 1 : -1 list = [...list].sort((a, b) => { @@ -314,6 +391,7 @@ const visibleTorrents = computed(() => { case 'size' : return (a.size - b.size) * dir case 'progress': return (a.progress - b.progress) * dir case 'state' : return a.state.localeCompare(b.state) * dir + case 'ratio' : return ((a.ratio ?? 0) - (b.ratio ?? 0)) * dir default : return 0 } }) @@ -329,7 +407,6 @@ const stats = computed(() => [ { label: 'Erreurs', value: torrents.value.filter(t => t.state === 'error').length }, ]) -// FIX : progress cappé à 100 pour éviter "278.2 GB / 278.1 GB" → 100% function clampedProgress(t: any): number { return Math.min(100, Math.max(0, t.progress ?? 0)) } @@ -420,14 +497,13 @@ function stateBadge(state: string) { paused : { label: 'En pause', class: 'border-border text-muted' }, checking : { label: 'Vérification', class: 'border-yellow-500/40 text-yellow-500' }, error : { label: 'Erreur', class: 'border-red-500/40 text-red-400' }, - // FIX : badge jaune pour signaler état dégradé sans alarmer unknown : { label: 'État inconnu', class: 'border-yellow-500/40 text-yellow-500' }, } return map[state] ?? map.unknown } function formatSize(bytes: number): string { - if (bytes === 0) return '0 B' + if (!bytes || bytes === 0) return '0 B' const units = ['B', 'KB', 'MB', 'GB', 'TB'] const i = Math.floor(Math.log(bytes) / Math.log(1024)) return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`