import { Table, Text, Tooltip, Title, Group, Progress, Skeleton, ScrollArea, Center, Stack, } from '@mantine/core'; import { IconDownload as Download } from '@tabler/icons'; import { useEffect, useState } from 'react'; import axios from 'axios'; import { NormalizedTorrent } from '@ctrl/shared-torrent'; import { useViewportSize } from '@mantine/hooks'; import { showNotification } from '@mantine/notifications'; import { useTranslation } from 'next-i18next'; import { IModule } from '../ModuleTypes'; import { useConfig } from '../../tools/state'; import { AddItemShelfButton } from '../../components/AppShelf/AddAppShelfItem'; import { useSetSafeInterval } from '../../tools/hooks/useSetSafeInterval'; import { humanFileSize } from '../../tools/humanFileSize'; export const DownloadsModule: IModule = { title: 'Torrent', icon: Download, component: DownloadComponent, options: { hidecomplete: { name: 'descriptor.settings.hideComplete', value: false, }, }, id: 'torrents-status', }; export default function DownloadComponent() { const { config } = useConfig(); const { height, width } = useViewportSize(); const downloadServices = config.services.filter( (service) => service.type === 'qBittorrent' || service.type === 'Transmission' || service.type === 'Deluge' ) ?? []; const hideComplete: boolean = (config?.modules?.[DownloadsModule.id]?.options?.hidecomplete?.value as boolean) ?? false; const [torrents, setTorrents] = useState([]); const setSafeInterval = useSetSafeInterval(); const [isLoading, setIsLoading] = useState(true); const { t } = useTranslation(`modules/${DownloadsModule.id}`); useEffect(() => { setIsLoading(true); if (downloadServices.length === 0) return; const interval = setInterval(() => { // Send one request with each download service inside axios .post('/api/modules/downloads') .then((response) => { setTorrents(response.data); setIsLoading(false); }) .catch((error) => { setTorrents([]); // eslint-disable-next-line no-console console.error('Error while fetching torrents', error.response.data); setIsLoading(false); showNotification({ title: 'Error fetching torrents', autoClose: 1000, disallowClose: true, id: 'fail-torrent-downloads-module', color: 'red', message: 'Please check your config for any potential errors, check the console for more info', }); clearInterval(interval); }); }, 5000); }, []); if (downloadServices.length === 0) { return ( {t('card.errors.noDownloadClients.title')} {t('card.errors.noDownloadClients.text')} ); } if (isLoading) { return ( <> ); } const DEVICE_WIDTH = 576; const ths = ( {t('card.table.header.name')} {t('card.table.header.size')} {width > 576 ? {t('card.table.header.download')} : ''} {width > 576 ? {t('card.table.header.upload')} : ''} {t('card.table.header.estimatedTimeOfArrival')} {t('card.table.header.progress')} ); // Convert Seconds to readable format. function calculateETA(givenSeconds: number) { // If its superior than one day return > 1 day if (givenSeconds > 86400) { return '> 1 day'; } // Transform the givenSeconds into a readable format. e.g. 1h 2m 3s const hours = Math.floor(givenSeconds / 3600); const minutes = Math.floor((givenSeconds % 3600) / 60); const seconds = Math.floor(givenSeconds % 60); // Only show hours if it's greater than 0. const hoursString = hours > 0 ? `${hours}h ` : ''; const minutesString = minutes > 0 ? `${minutes}m ` : ''; const secondsString = seconds > 0 ? `${seconds}s` : ''; return `${hoursString}${minutesString}${secondsString}`; } // Loop over qBittorrent torrents merging with deluge torrents const rows = torrents .filter((torrent) => !(torrent.progress === 1 && hideComplete)) .map((torrent) => { const downloadSpeed = torrent.downloadSpeed / 1024 / 1024; const uploadSpeed = torrent.uploadSpeed / 1024 / 1024; const size = torrent.totalSelected; return ( {torrent.name} {humanFileSize(size)} {width > 576 ? ( {downloadSpeed > 0 ? `${downloadSpeed.toFixed(1)} Mb/s` : '-'} ) : ( '' )} {width > 576 ? ( {uploadSpeed > 0 ? `${uploadSpeed.toFixed(1)} Mb/s` : '-'} ) : ( '' )} {torrent.eta <= 0 ? '∞' : calculateETA(torrent.eta)} {(torrent.progress * 100).toFixed(1)}% ); }); return ( {rows.length > 0 ? ( {ths}{rows}
) : (
{t('card.table.body.nothingFound')}
)}
); }