import { Avatar, Box, Card, Group, Stack, Title, Text, Tooltip, useMantineTheme, } from '@mantine/core'; import { useElementSize, useListState } from '@mantine/hooks'; import { linearGradientDef } from '@nivo/core'; import { Serie, Datum, ResponsiveLine } from '@nivo/line'; import { IconDownload, IconUpload } from '@tabler/icons'; import { useTranslation } from 'next-i18next'; import { useEffect } from 'react'; import { AppAvatar } from '../../components/AppAvatar'; import { useConfigContext } from '../../config/provider'; import { useGetDownloadClientsQueue } from '../../hooks/widgets/download-speed/useGetNetworkSpeed'; import { useColorTheme } from '../../tools/color'; import { humanFileSize } from '../../tools/humanFileSize'; import { NormalizedDownloadQueueResponse, TorrentTotalDownload, } from '../../types/api/downloads/queue/NormalizedDownloadQueueResponse'; import definition, { ITorrentNetworkTraffic } from './TorrentNetworkTrafficTile'; interface TorrentNetworkTrafficTileProps { widget: ITorrentNetworkTraffic; } export default function TorrentNetworkTrafficTile({ widget }: TorrentNetworkTrafficTileProps) { const { config } = useConfigContext(); const { ref: refRoot, height: heightRoot } = useElementSize(); const { ref: refTitle, height: heightTitle } = useElementSize(); const { ref: refFooter, height: heightFooter } = useElementSize(); const { primaryColor, secondaryColor } = useColorTheme(); const { t } = useTranslation(`modules/${definition.id}`); const [clientDataHistory, setClientDataHistory] = useListState(); const { data, dataUpdatedAt } = useGetDownloadClientsQueue(); useEffect(() => { if (data) { setClientDataHistory.append(data); } if (clientDataHistory.length < 30) { return; } setClientDataHistory.remove(0); }, [dataUpdatedAt]); if (!data) { return null; } const recoredAppsOverTime = clientDataHistory.flatMap((x) => x.apps.map((app) => app)); // removing duplicates the "naive" way: https://stackoverflow.com/a/9229821/15257712 const uniqueRecordedAppsOverTime = recoredAppsOverTime .map((x) => x.appId) .filter((item, position) => recoredAppsOverTime.map((y) => y.appId).indexOf(item) === position); const lineChartData: Serie[] = uniqueRecordedAppsOverTime.flatMap((appId) => { const records = recoredAppsOverTime.filter((x) => x.appId === appId); const series: Serie[] = [ { id: `download_${appId}`, data: records.map((record, index) => ({ x: index, y: record.totalDownload, })), }, ]; if (records.some((x) => x.type === 'torrent')) { const torrentRecords = records.map((record, index): Datum | null => { if (record.type !== 'torrent') { return null; } return { x: index, y: record.totalUpload, }; }); const filteredRecords = torrentRecords.filter((x) => x !== null) as Datum[]; series.push({ id: `upload_${appId}`, data: filteredRecords, }); } return series; }); const totalDownload = uniqueRecordedAppsOverTime .map((appId) => { const records = recoredAppsOverTime.filter((x) => x.appId === appId); const lastRecord = records.at(-1); return lastRecord?.totalDownload ?? 0; }) .reduce((acc, n) => acc + n, 0); const totalUpload = uniqueRecordedAppsOverTime .map((appId) => { const records = recoredAppsOverTime.filter((x) => x.appId === appId && x.type === 'torrent'); const lastRecord = records.at(-1) as TorrentTotalDownload; return lastRecord?.totalUpload ?? 0; }) .reduce((acc, n) => acc + n, 0); const graphHeight = heightRoot - heightFooter - heightTitle; const { colors } = useMantineTheme(); return ( {t('card.lineChart.title')} { const { points } = slice; const recordsFromPoints = uniqueRecordedAppsOverTime.map((appId) => { const records = recoredAppsOverTime.filter((x) => x.appId === appId); const point = points.find((x) => x.id.includes(appId)); const pointIndex = Number(point?.data.x) ?? 0; const color = point?.serieColor; return { record: records[pointIndex], color, }; }); return ( {recordsFromPoints.map((entry, index) => { const app = config?.apps.find((x) => x.id === entry.record.appId); if (!app) { return null; } return ( {app.name} {humanFileSize(entry.record.totalDownload, false)} {entry.record.type === 'torrent' && ( {humanFileSize(entry.record.totalUpload, false)} )} ); })} ); }} data={lineChartData} curve="monotoneX" yFormat=" >-.2f" axisLeft={null} axisBottom={null} axisRight={null} enablePoints={false} enableGridX={false} enableGridY={false} enableArea defs={[ linearGradientDef('gradientA', [ { offset: 0, color: 'inherit' }, { offset: 100, color: 'inherit', opacity: 0 }, ]), ]} colors={lineChartData.flatMap((data) => data.id.toString().startsWith('upload_') ? colors[secondaryColor][5] : colors[primaryColor][5] )} fill={[{ match: '*', id: 'gradientA' }]} margin={{ bottom: 5 }} animate={false} /> {humanFileSize(totalDownload, false)} {humanFileSize(totalUpload, false)} {uniqueRecordedAppsOverTime.map((appId, index) => { const app = config?.apps.find((x) => x.id === appId); if (!app) { return null; } return ( ); })} ); }