mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-09 23:15:46 +01:00
✨ Add torrent module
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"settings": {
|
"settings": {
|
||||||
"label": "Settings"
|
"label": "Settings"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"unmappedOptions": {
|
||||||
|
"text": "<b>Un-used parameter in configuration detected</b><br /><code>{{key}}</code>. Homarr is unable to interprete and use this parameter. To avoid any unexpected behavior, back up your configuration and correct your configuration."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
{
|
{
|
||||||
"descriptor": {
|
"descriptor": {
|
||||||
"name": "Torrent",
|
"name": "BitTorrent",
|
||||||
"description": "Show the current download speed of supported services",
|
"description": "Displays a list of the torrent which are currently downloading",
|
||||||
"settings": {
|
"settings": {
|
||||||
"hideComplete": {
|
"title": "Settings for BitTorrent integration",
|
||||||
"label": "Hide completed torrents"
|
"displayCompletedTorrents": {
|
||||||
|
"label": "Display completed torrents"
|
||||||
|
},
|
||||||
|
"displayStaleTorrents": {
|
||||||
|
"label": "Display stale torrents"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Button, Group, MultiSelect, Stack, Switch, TextInput } from '@mantine/core';
|
import { Alert, Button, Group, MultiSelect, Stack, Switch, TextInput, Text } from '@mantine/core';
|
||||||
import { ContextModalProps } from '@mantine/modals';
|
import { ContextModalProps } from '@mantine/modals';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { IconAlertTriangle } from '@tabler/icons';
|
||||||
|
import { Trans, useTranslation } from 'next-i18next';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Widgets from '../../../../widgets';
|
import Widgets from '../../../../widgets';
|
||||||
import type { IWidgetOptionValue } from '../../../../widgets/widgets';
|
import type { IWidgetOptionValue } from '../../../../widgets/widgets';
|
||||||
@@ -31,6 +32,8 @@ export const WidgetsEditModal = ({
|
|||||||
|
|
||||||
if (!configName || !innerProps.options) return null;
|
if (!configName || !innerProps.options) return null;
|
||||||
|
|
||||||
|
console.log(`loaded namespace modules/${innerProps.widgetId}`);
|
||||||
|
|
||||||
const handleChange = (key: string, value: IntegrationOptionsValueType) => {
|
const handleChange = (key: string, value: IntegrationOptionsValueType) => {
|
||||||
setModuleProperties((prev) => {
|
setModuleProperties((prev) => {
|
||||||
const copyOfPrev: any = { ...prev };
|
const copyOfPrev: any = { ...prev };
|
||||||
@@ -66,12 +69,28 @@ export const WidgetsEditModal = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
{items.map(([key, value]) => {
|
{items.map(([key, value], index) => {
|
||||||
const option = (currentWidgetDefinition as any).options[key] as IWidgetOptionValue;
|
const option = (currentWidgetDefinition as any).options[key] as IWidgetOptionValue;
|
||||||
|
|
||||||
|
if (!option) {
|
||||||
|
return (
|
||||||
|
<Alert icon={<IconAlertTriangle />} color="red">
|
||||||
|
<Text>
|
||||||
|
<Trans
|
||||||
|
i18nKey="modules/common:errors.unmappedOptions.text"
|
||||||
|
values={{ key }}
|
||||||
|
components={{ b: <b />, code: <code /> }}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
switch (option.type) {
|
switch (option.type) {
|
||||||
case 'switch':
|
case 'switch':
|
||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
|
key={`${option.type}-${index}`}
|
||||||
label={t(`descriptor.settings.${key}.label`)}
|
label={t(`descriptor.settings.${key}.label`)}
|
||||||
checked={value as boolean}
|
checked={value as boolean}
|
||||||
onChange={(ev) => handleChange(key, ev.currentTarget.checked)}
|
onChange={(ev) => handleChange(key, ev.currentTarget.checked)}
|
||||||
@@ -80,6 +99,7 @@ export const WidgetsEditModal = ({
|
|||||||
case 'text':
|
case 'text':
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
|
key={`${option.type}-${index}`}
|
||||||
label={t(`descriptor.settings.${key}.label`)}
|
label={t(`descriptor.settings.${key}.label`)}
|
||||||
value={value as string}
|
value={value as string}
|
||||||
onChange={(ev) => handleChange(key, ev.currentTarget.value)}
|
onChange={(ev) => handleChange(key, ev.currentTarget.value)}
|
||||||
@@ -88,6 +108,7 @@ export const WidgetsEditModal = ({
|
|||||||
case 'multi-select':
|
case 'multi-select':
|
||||||
return (
|
return (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
|
key={`${option.type}-${index}`}
|
||||||
data={getMutliselectData(key)}
|
data={getMutliselectData(key)}
|
||||||
label={t(`descriptor.settings.${key}.label`)}
|
label={t(`descriptor.settings.${key}.label`)}
|
||||||
value={value as string[]}
|
value={value as string[]}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface TorrentsDataRequestParams {
|
|||||||
export const useGetTorrentData = (params: TorrentsDataRequestParams) =>
|
export const useGetTorrentData = (params: TorrentsDataRequestParams) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: ['torrentsData', params.appId],
|
queryKey: ['torrentsData', params.appId],
|
||||||
queryFn: async () => fetchData(),
|
queryFn: fetchData,
|
||||||
refetchOnWindowFocus: true,
|
refetchOnWindowFocus: true,
|
||||||
refetchInterval(_: any, query: Query) {
|
refetchInterval(_: any, query: Query) {
|
||||||
if (query.state.fetchFailureCount < 3) {
|
if (query.state.fetchFailureCount < 3) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const dashboardNamespaces = [
|
|||||||
'modules/dlspeed',
|
'modules/dlspeed',
|
||||||
'modules/usenet',
|
'modules/usenet',
|
||||||
'modules/search',
|
'modules/search',
|
||||||
'modules/torrents',
|
'modules/torrents-status',
|
||||||
'modules/weather',
|
'modules/weather',
|
||||||
'modules/ping',
|
'modules/ping',
|
||||||
'modules/docker',
|
'modules/docker',
|
||||||
|
|||||||
@@ -10,11 +10,9 @@ interface WidgetWrapperProps {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WidgetWrapper = ({ widgetId, widget, className, children }: WidgetWrapperProps) => {
|
export const WidgetWrapper = ({ widgetId, widget, className, children }: WidgetWrapperProps) => (
|
||||||
return (
|
|
||||||
<HomarrCardWrapper className={className}>
|
<HomarrCardWrapper className={className}>
|
||||||
<WidgetsMenu integration={widgetId} widget={widget} />
|
<WidgetsMenu integration={widgetId} widget={widget} />
|
||||||
{children}
|
{children}
|
||||||
</HomarrCardWrapper>
|
</HomarrCardWrapper>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { NormalizedTorrent } from '@ctrl/shared-torrent';
|
import { NormalizedTorrent, TorrentState } from '@ctrl/shared-torrent';
|
||||||
import {
|
import {
|
||||||
Center,
|
Center,
|
||||||
Group,
|
Group,
|
||||||
@@ -52,7 +52,7 @@ interface BitTorrentTileProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function BitTorrentTile({ widget }: BitTorrentTileProps) {
|
function BitTorrentTile({ widget }: BitTorrentTileProps) {
|
||||||
const { t } = useTranslation('modules/torrents');
|
const { t } = useTranslation('modules/torrents-status');
|
||||||
const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
|
const MIN_WIDTH_MOBILE = useMantineTheme().breakpoints.xs;
|
||||||
const { width } = useElementSize();
|
const { width } = useElementSize();
|
||||||
|
|
||||||
@@ -112,6 +112,18 @@ function BitTorrentTile({ widget }: BitTorrentTileProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea sx={{ height: 300, width: '100%' }}>
|
<ScrollArea sx={{ height: 300, width: '100%' }}>
|
||||||
<Table highlightOnHover p="sm">
|
<Table highlightOnHover p="sm">
|
||||||
@@ -126,7 +138,7 @@ function BitTorrentTile({ widget }: BitTorrentTileProps) {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data.map((item: NormalizedTorrent, index: number) => (
|
{data.filter(filter).map((item: NormalizedTorrent, index: number) => (
|
||||||
<BitTorrrentQueueItem key={index} torrent={item} />
|
<BitTorrrentQueueItem key={index} torrent={item} />
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user