2023-01-03 21:13:53 +01:00
|
|
|
import { NormalizedTorrent } from '@ctrl/shared-torrent';
|
2022-12-31 16:07:05 +01:00
|
|
|
import {
|
2023-01-18 21:24:55 +01:00
|
|
|
Badge,
|
2022-12-31 16:07:05 +01:00
|
|
|
Center,
|
2023-01-03 21:13:53 +01:00
|
|
|
Flex,
|
2022-12-31 16:07:05 +01:00
|
|
|
Group,
|
|
|
|
|
Loader,
|
|
|
|
|
ScrollArea,
|
|
|
|
|
Stack,
|
|
|
|
|
Table,
|
|
|
|
|
Text,
|
|
|
|
|
Title,
|
|
|
|
|
useMantineTheme,
|
|
|
|
|
} from '@mantine/core';
|
|
|
|
|
import { useElementSize } from '@mantine/hooks';
|
2022-12-19 18:26:04 +01:00
|
|
|
import { IconFileDownload } from '@tabler/icons';
|
2023-01-03 21:13:53 +01:00
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
import duration from 'dayjs/plugin/duration';
|
|
|
|
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
2022-12-31 16:07:05 +01:00
|
|
|
import { useTranslation } from 'next-i18next';
|
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
|
import { useConfigContext } from '../../config/provider';
|
|
|
|
|
import { useGetTorrentData } from '../../hooks/widgets/torrents/useGetTorrentData';
|
2023-01-18 21:24:55 +01:00
|
|
|
import { NormalizedTorrentListResponse } from '../../types/api/NormalizedTorrentListResponse';
|
2022-12-31 16:07:05 +01:00
|
|
|
import { AppIntegrationType } from '../../types/app';
|
2022-12-18 21:50:08 +01:00
|
|
|
import { defineWidget } from '../helper';
|
|
|
|
|
import { IWidget } from '../widgets';
|
2023-01-13 09:49:29 +00:00
|
|
|
import { BitTorrrentQueueItem } from './TorrentQueueItem';
|
2022-12-31 16:07:05 +01:00
|
|
|
|
2023-01-03 21:13:53 +01:00
|
|
|
dayjs.extend(duration);
|
|
|
|
|
dayjs.extend(relativeTime);
|
|
|
|
|
|
2022-12-31 16:07:05 +01:00
|
|
|
const downloadAppTypes: AppIntegrationType['type'][] = ['deluge', 'qBittorrent', 'transmission'];
|
2022-12-18 21:50:08 +01:00
|
|
|
|
|
|
|
|
const definition = defineWidget({
|
2022-12-18 22:58:00 +01:00
|
|
|
id: 'torrents-status',
|
2022-12-18 23:22:45 +01:00
|
|
|
icon: IconFileDownload,
|
2022-12-31 16:07:05 +01:00
|
|
|
options: {
|
|
|
|
|
displayCompletedTorrents: {
|
|
|
|
|
type: 'switch',
|
|
|
|
|
defaultValue: true,
|
|
|
|
|
},
|
|
|
|
|
displayStaleTorrents: {
|
|
|
|
|
type: 'switch',
|
|
|
|
|
defaultValue: true,
|
|
|
|
|
},
|
2023-01-05 22:45:35 +09:00
|
|
|
refreshInterval: {
|
|
|
|
|
type: 'slider',
|
2023-01-07 20:18:53 +01:00
|
|
|
defaultValue: 10,
|
2023-01-05 22:45:35 +09:00
|
|
|
min: 1,
|
|
|
|
|
max: 60,
|
|
|
|
|
step: 1,
|
|
|
|
|
},
|
2022-12-31 16:07:05 +01:00
|
|
|
},
|
2022-12-18 21:50:08 +01:00
|
|
|
gridstack: {
|
2023-01-08 12:41:55 +09:00
|
|
|
minWidth: 2,
|
|
|
|
|
minHeight: 2,
|
2022-12-31 16:07:05 +01:00
|
|
|
maxWidth: 12,
|
|
|
|
|
maxHeight: 14,
|
2022-12-18 21:50:08 +01:00
|
|
|
},
|
2023-01-13 09:49:29 +00:00
|
|
|
component: TorrentTile,
|
2022-12-18 21:50:08 +01:00
|
|
|
});
|
|
|
|
|
|
2023-01-13 09:49:29 +00:00
|
|
|
export type ITorrent = IWidget<typeof definition['id'], typeof definition>;
|
2022-12-18 21:50:08 +01:00
|
|
|
|
2023-01-13 09:49:29 +00:00
|
|
|
interface TorrentTileProps {
|
|
|
|
|
widget: ITorrent;
|
2022-12-18 21:50:08 +01:00
|
|
|
}
|
|
|
|
|
|
2023-01-13 09:49:29 +00:00
|
|
|
function TorrentTile({ widget }: TorrentTileProps) {
|
2022-12-31 17:48:46 +01:00
|
|
|
const { t } = useTranslation('modules/torrents-status');
|
2022-12-31 16:07:05 +01:00
|
|
|
const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
|
|
|
|
|
const { width } = useElementSize();
|
|
|
|
|
|
|
|
|
|
const { config } = useConfigContext();
|
|
|
|
|
const downloadApps =
|
|
|
|
|
config?.apps.filter((x) => x.integration && downloadAppTypes.includes(x.integration.type)) ??
|
|
|
|
|
[];
|
|
|
|
|
|
|
|
|
|
const [selectedAppId, setSelectedApp] = useState<string | null>(downloadApps[0]?.id);
|
2023-01-18 21:24:55 +01:00
|
|
|
const {
|
|
|
|
|
data,
|
|
|
|
|
isError,
|
|
|
|
|
isInitialLoading,
|
|
|
|
|
dataUpdatedAt,
|
|
|
|
|
}: {
|
|
|
|
|
data: NormalizedTorrentListResponse | undefined;
|
|
|
|
|
isError: boolean;
|
|
|
|
|
isInitialLoading: boolean;
|
|
|
|
|
dataUpdatedAt: number;
|
|
|
|
|
} = useGetTorrentData({
|
2023-01-03 21:13:53 +01:00
|
|
|
appId: selectedAppId!,
|
2023-01-05 22:45:35 +09:00
|
|
|
refreshInterval: widget.properties.refreshInterval * 1000,
|
2023-01-03 21:13:53 +01:00
|
|
|
});
|
2022-12-31 16:07:05 +01:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!selectedAppId && downloadApps.length) {
|
|
|
|
|
setSelectedApp(downloadApps[0].id);
|
|
|
|
|
}
|
|
|
|
|
}, [downloadApps, selectedAppId]);
|
|
|
|
|
|
|
|
|
|
if (downloadApps.length === 0) {
|
|
|
|
|
return (
|
|
|
|
|
<Stack>
|
|
|
|
|
<Title order={3}>{t('card.errors.noDownloadClients.title')}</Title>
|
|
|
|
|
<Group>
|
|
|
|
|
<Text>{t('card.errors.noDownloadClients.text')}</Text>
|
|
|
|
|
</Group>
|
|
|
|
|
</Stack>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isError) {
|
|
|
|
|
return (
|
|
|
|
|
<Stack>
|
|
|
|
|
<Title order={3}>{t('card.errors.generic.title')}</Title>
|
|
|
|
|
<Group>
|
|
|
|
|
<Text>{t('card.errors.generic.text')}</Text>
|
|
|
|
|
</Group>
|
|
|
|
|
</Stack>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-03 21:13:53 +01:00
|
|
|
if (isInitialLoading) {
|
2022-12-31 16:07:05 +01:00
|
|
|
return (
|
2023-01-05 22:45:35 +09:00
|
|
|
<Stack
|
|
|
|
|
align="center"
|
|
|
|
|
justify="center"
|
|
|
|
|
style={{
|
|
|
|
|
height: '100%',
|
|
|
|
|
}}
|
|
|
|
|
>
|
2022-12-31 16:07:05 +01:00
|
|
|
<Loader />
|
|
|
|
|
<Stack align="center" spacing={0}>
|
|
|
|
|
<Text>{t('card.loading.title')}</Text>
|
|
|
|
|
<Text color="dimmed">Homarr is establishing a connection...</Text>
|
|
|
|
|
</Stack>
|
|
|
|
|
</Stack>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-18 21:24:55 +01:00
|
|
|
if (!data || Object.values(data.torrents).length < 1) {
|
2022-12-31 16:07:05 +01:00
|
|
|
return (
|
|
|
|
|
<Center style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
|
|
|
<Title order={3}>{t('card.table.body.nothingFound')}</Title>
|
|
|
|
|
</Center>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-31 17:48:46 +01:00
|
|
|
const filter = (torrent: NormalizedTorrent) => {
|
|
|
|
|
if (!widget.properties.displayCompletedTorrents && torrent.isCompleted) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!widget.properties.displayStaleTorrents && !torrent.isCompleted && torrent.eta <= 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
2023-01-03 21:13:53 +01:00
|
|
|
const difference = new Date().getTime() - dataUpdatedAt;
|
|
|
|
|
const duration = dayjs.duration(difference, 'ms');
|
|
|
|
|
const humanizedDuration = duration.humanize();
|
|
|
|
|
|
2022-12-31 16:07:05 +01:00
|
|
|
return (
|
2023-01-03 21:13:53 +01:00
|
|
|
<Flex direction="column" sx={{ height: '100%' }}>
|
2023-01-18 21:24:55 +01:00
|
|
|
<ScrollArea sx={{ height: '100%', width: '100%' }} mb="xs">
|
2023-01-03 21:13:53 +01:00
|
|
|
<Table highlightOnHover p="sm">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>{t('card.table.header.name')}</th>
|
|
|
|
|
<th>{t('card.table.header.size')}</th>
|
|
|
|
|
{width > MIN_WIDTH_MOBILE && <th>{t('card.table.header.download')}</th>}
|
|
|
|
|
{width > MIN_WIDTH_MOBILE && <th>{t('card.table.header.upload')}</th>}
|
|
|
|
|
{width > MIN_WIDTH_MOBILE && <th>{t('card.table.header.estimatedTimeOfArrival')}</th>}
|
|
|
|
|
<th>{t('card.table.header.progress')}</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
2023-01-18 21:24:55 +01:00
|
|
|
{data.torrents.map((concatenatedTorrentList) => {
|
|
|
|
|
const app = config?.apps.find((x) => x.id === concatenatedTorrentList.appId);
|
|
|
|
|
return concatenatedTorrentList.torrents
|
|
|
|
|
.filter(filter)
|
|
|
|
|
.map((item: NormalizedTorrent, index: number) => (
|
|
|
|
|
<BitTorrrentQueueItem key={index} torrent={item} app={app} />
|
|
|
|
|
));
|
|
|
|
|
})}
|
2023-01-03 21:13:53 +01:00
|
|
|
</tbody>
|
|
|
|
|
</Table>
|
|
|
|
|
</ScrollArea>
|
2023-01-18 21:24:55 +01:00
|
|
|
<Group spacing="sm">
|
|
|
|
|
{!data.allSuccess && (
|
|
|
|
|
<Badge variant="dot" color="red">
|
|
|
|
|
{t('card.footer.error')}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Text color="dimmed" size="xs">
|
|
|
|
|
{t('card.footer.lastUpdated', { time: humanizedDuration })}
|
|
|
|
|
</Text>
|
|
|
|
|
</Group>
|
2023-01-03 21:13:53 +01:00
|
|
|
</Flex>
|
2022-12-31 16:07:05 +01:00
|
|
|
);
|
2022-12-18 21:50:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default definition;
|