mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-11 07:55:52 +01:00
🔀 Merge pull request #195 from LarveyOfficial/patch-3
More Information in Torrents Module
This commit is contained in:
@@ -155,8 +155,9 @@ const AppShelf = (props: any) => {
|
|||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
) : null}
|
) : null}
|
||||||
<Accordion.Item key="Downloads" label="Your downloads">
|
<Accordion.Item key="Downloads" label="Your downloads">
|
||||||
<ModuleMenu module={DownloadsModule} />
|
|
||||||
<Paper
|
<Paper
|
||||||
|
p="lg"
|
||||||
|
radius="lg"
|
||||||
style={{
|
style={{
|
||||||
background: `rgba(${colorScheme === 'dark' ? '37, 38, 43,' : '255, 255, 255,'} \
|
background: `rgba(${colorScheme === 'dark' ? '37, 38, 43,' : '255, 255, 255,'} \
|
||||||
${(config.settings.appOpacity || 100) / 100}`,
|
${(config.settings.appOpacity || 100) / 100}`,
|
||||||
@@ -164,6 +165,7 @@ const AppShelf = (props: any) => {
|
|||||||
${(config.settings.appOpacity || 100) / 100}`,
|
${(config.settings.appOpacity || 100) / 100}`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<ModuleMenu module={DownloadsModule} />
|
||||||
<DownloadComponent />
|
<DownloadComponent />
|
||||||
</Paper>
|
</Paper>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ function SettingsMenu(props: any) {
|
|||||||
<Tabs.Tab data-autofocus label="Common">
|
<Tabs.Tab data-autofocus label="Common">
|
||||||
<CommonSettings />
|
<CommonSettings />
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
<Tabs.Tab label="Customisations">
|
<Tabs.Tab label="Customizations">
|
||||||
<AdvancedSettings />
|
<AdvancedSettings />
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
/* eslint-disable react/no-children-prop */
|
/* eslint-disable react/no-children-prop */
|
||||||
import { Box, Divider, Indicator, Popover, ScrollArea, createStyles, useMantineTheme } from '@mantine/core';
|
import {
|
||||||
|
Box,
|
||||||
|
Divider,
|
||||||
|
Indicator,
|
||||||
|
Popover,
|
||||||
|
ScrollArea,
|
||||||
|
createStyles,
|
||||||
|
useMantineTheme,
|
||||||
|
} from '@mantine/core';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Calendar } from '@mantine/dates';
|
import { Calendar } from '@mantine/dates';
|
||||||
import { IconCalendar as CalendarIcon } from '@tabler/icons';
|
import { IconCalendar as CalendarIcon } from '@tabler/icons';
|
||||||
@@ -28,7 +36,7 @@ export default function CalendarComponent(props: any) {
|
|||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
const { secondaryColor } = useColorTheme();
|
const { secondaryColor } = useColorTheme();
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
weekend: {
|
weekend: {
|
||||||
color: `${secondaryColor} !important`,
|
color: `${secondaryColor} !important`,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -101,12 +109,13 @@ export default function CalendarComponent(props: any) {
|
|||||||
onChange={(day: any) => {}}
|
onChange={(day: any) => {}}
|
||||||
dayStyle={(date) =>
|
dayStyle={(date) =>
|
||||||
date.getDay() === today.getDay() && date.getDate() === today.getDate()
|
date.getDay() === today.getDay() && date.getDate() === today.getDate()
|
||||||
? { backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[0] }
|
? {
|
||||||
|
backgroundColor:
|
||||||
|
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[0],
|
||||||
|
}
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
dayClassName={(date, modifiers) =>
|
dayClassName={(date, modifiers) => cx({ [classes.weekend]: modifiers.weekend })}
|
||||||
cx({ [classes.weekend]: modifiers.weekend })
|
|
||||||
}
|
|
||||||
renderDay={(renderdate) => (
|
renderDay={(renderdate) => (
|
||||||
<DayComponent
|
<DayComponent
|
||||||
renderdate={renderdate}
|
renderdate={renderdate}
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ import { IconDownload as Download } from '@tabler/icons';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { NormalizedTorrent } from '@ctrl/shared-torrent';
|
import { NormalizedTorrent } from '@ctrl/shared-torrent';
|
||||||
|
import { useViewportSize } from '@mantine/hooks';
|
||||||
import { IModule } from '../modules';
|
import { IModule } from '../modules';
|
||||||
import { useConfig } from '../../../tools/state';
|
import { useConfig } from '../../../tools/state';
|
||||||
import { AddItemShelfButton } from '../../AppShelf/AddAppShelfItem';
|
import { AddItemShelfButton } from '../../AppShelf/AddAppShelfItem';
|
||||||
import { useSetSafeInterval } from '../../../tools/hooks/useSetSafeInterval';
|
import { useSetSafeInterval } from '../../../tools/hooks/useSetSafeInterval';
|
||||||
|
import { humanFileSize } from '../../../tools/humanFileSize';
|
||||||
|
|
||||||
export const DownloadsModule: IModule = {
|
export const DownloadsModule: IModule = {
|
||||||
title: 'Torrent',
|
title: 'Torrent',
|
||||||
@@ -34,6 +36,7 @@ export const DownloadsModule: IModule = {
|
|||||||
|
|
||||||
export default function DownloadComponent() {
|
export default function DownloadComponent() {
|
||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
|
const { height, width } = useViewportSize();
|
||||||
const downloadServices =
|
const downloadServices =
|
||||||
config.services.filter(
|
config.services.filter(
|
||||||
(service) =>
|
(service) =>
|
||||||
@@ -81,21 +84,40 @@ export default function DownloadComponent() {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const DEVICE_WIDTH = 576;
|
||||||
const ths = (
|
const ths = (
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Download</th>
|
<th>Size</th>
|
||||||
<th>Upload</th>
|
{width > 576 ? <th>Down</th> : ''}
|
||||||
|
{width > 576 ? <th>Up</th> : ''}
|
||||||
|
<th>ETA</th>
|
||||||
<th>Progress</th>
|
<th>Progress</th>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
// 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
|
// Loop over qBittorrent torrents merging with deluge torrents
|
||||||
const rows = torrents
|
const rows = torrents
|
||||||
.filter((torrent) => !(torrent.progress === 1 && hideComplete))
|
.filter((torrent) => !(torrent.progress === 1 && hideComplete))
|
||||||
.map((torrent) => {
|
.map((torrent) => {
|
||||||
const downloadSpeed = torrent.downloadSpeed / 1024 / 1024;
|
const downloadSpeed = torrent.downloadSpeed / 1024 / 1024;
|
||||||
const uploadSpeed = torrent.uploadSpeed / 1024 / 1024;
|
const uploadSpeed = torrent.uploadSpeed / 1024 / 1024;
|
||||||
|
const size = torrent.totalSelected;
|
||||||
return (
|
return (
|
||||||
<tr key={torrent.id}>
|
<tr key={torrent.id}>
|
||||||
<td>
|
<td>
|
||||||
@@ -112,16 +134,32 @@ export default function DownloadComponent() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Text size="xs">{downloadSpeed > 0 ? `${downloadSpeed.toFixed(1)} Mb/s` : '-'}</Text>
|
<Text size="xs">{humanFileSize(size)}</Text>
|
||||||
</td>
|
</td>
|
||||||
|
{width > 576 ? (
|
||||||
|
<td>
|
||||||
|
<Text size="xs">{downloadSpeed > 0 ? `${downloadSpeed.toFixed(1)} Mb/s` : '-'}</Text>
|
||||||
|
</td>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{width > 576 ? (
|
||||||
|
<td>
|
||||||
|
<Text size="xs">{uploadSpeed > 0 ? `${uploadSpeed.toFixed(1)} Mb/s` : '-'}</Text>
|
||||||
|
</td>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
<td>
|
<td>
|
||||||
<Text size="xs">{uploadSpeed > 0 ? `${uploadSpeed.toFixed(1)} Mb/s` : '-'}</Text>
|
<Text size="xs">{torrent.eta <= 0 ? '∞' : calculateETA(torrent.eta)}</Text>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Text>{(torrent.progress * 100).toFixed(1)}%</Text>
|
<Text>{(torrent.progress * 100).toFixed(1)}%</Text>
|
||||||
<Progress
|
<Progress
|
||||||
radius="lg"
|
radius="lg"
|
||||||
color={torrent.progress === 1 ? 'green' : 'blue'}
|
color={
|
||||||
|
torrent.state === 'paused' ? 'yellow' : torrent.progress === 1 ? 'green' : 'blue'
|
||||||
|
}
|
||||||
value={torrent.progress * 100}
|
value={torrent.progress * 100}
|
||||||
size="lg"
|
size="lg"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,41 +8,10 @@ import { Datum, ResponsiveLine } from '@nivo/line';
|
|||||||
import { useListState } from '@mantine/hooks';
|
import { useListState } from '@mantine/hooks';
|
||||||
import { AddItemShelfButton } from '../../AppShelf/AddAppShelfItem';
|
import { AddItemShelfButton } from '../../AppShelf/AddAppShelfItem';
|
||||||
import { useConfig } from '../../../tools/state';
|
import { useConfig } from '../../../tools/state';
|
||||||
|
import { humanFileSize } from '../../../tools/humanFileSize';
|
||||||
import { IModule } from '../modules';
|
import { IModule } from '../modules';
|
||||||
import { useSetSafeInterval } from '../../../tools/hooks/useSetSafeInterval';
|
import { useSetSafeInterval } from '../../../tools/hooks/useSetSafeInterval';
|
||||||
|
|
||||||
/**
|
|
||||||
* Format bytes as human-readable text.
|
|
||||||
*
|
|
||||||
* @param bytes Number of bytes.
|
|
||||||
* @param si True to use metric (SI) units, aka powers of 1000. False to use
|
|
||||||
* binary (IEC), aka powers of 1024.
|
|
||||||
* @param dp Number of decimal places to display.
|
|
||||||
*
|
|
||||||
* @return Formatted string.
|
|
||||||
*/
|
|
||||||
function humanFileSize(initialBytes: number, si = true, dp = 1) {
|
|
||||||
const thresh = si ? 1000 : 1024;
|
|
||||||
let bytes = initialBytes;
|
|
||||||
|
|
||||||
if (Math.abs(bytes) < thresh) {
|
|
||||||
return `${bytes} B`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const units = si
|
|
||||||
? ['kb', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
||||||
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
|
||||||
let u = -1;
|
|
||||||
const r = 10 ** dp;
|
|
||||||
|
|
||||||
do {
|
|
||||||
bytes /= thresh;
|
|
||||||
u += 1;
|
|
||||||
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
|
||||||
|
|
||||||
return `${bytes.toFixed(dp)} ${units[u]}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TotalDownloadsModule: IModule = {
|
export const TotalDownloadsModule: IModule = {
|
||||||
title: 'Download Speed',
|
title: 'Download Speed',
|
||||||
description: 'Show the current download speed of supported services',
|
description: 'Show the current download speed of supported services',
|
||||||
|
|||||||
31
src/tools/humanFileSize.ts
Normal file
31
src/tools/humanFileSize.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Format bytes as human-readable text.
|
||||||
|
*
|
||||||
|
* @param bytes Number of bytes.
|
||||||
|
* @param si True to use metric (SI) units, aka powers of 1000. False to use
|
||||||
|
* binary (IEC), aka powers of 1024.
|
||||||
|
* @param dp Number of decimal places to display.
|
||||||
|
*
|
||||||
|
* @return Formatted string.
|
||||||
|
*/
|
||||||
|
export function humanFileSize(initialBytes: number, si = true, dp = 1) {
|
||||||
|
const thresh = si ? 1000 : 1024;
|
||||||
|
let bytes = initialBytes;
|
||||||
|
|
||||||
|
if (Math.abs(bytes) < thresh) {
|
||||||
|
return `${bytes} B`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const units = si
|
||||||
|
? ['kb', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
|
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||||
|
let u = -1;
|
||||||
|
const r = 10 ** dp;
|
||||||
|
|
||||||
|
do {
|
||||||
|
bytes /= thresh;
|
||||||
|
u += 1;
|
||||||
|
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
||||||
|
|
||||||
|
return `${bytes.toFixed(dp)} ${units[u]}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user