diff --git a/packages/common/src/number.ts b/packages/common/src/number.ts index 66e4daab84..d381e2d50c 100644 --- a/packages/common/src/number.ts +++ b/packages/common/src/number.ts @@ -30,7 +30,7 @@ export const randomInt = (min: number, max: number) => { export const humanFileSize = (size: number) => { if (!Number.isInteger(size)) return NaN; let count = 0; - while (true) { + while (count < siRanges.length) { const tempSize = size / Math.pow(1024, count); if (tempSize < 1024 || count === siRanges.length - 1) { return tempSize.toFixed(Math.min(count, 1)) + siRanges[count]; diff --git a/packages/integrations/src/download-client/deluge/deluge-integration.ts b/packages/integrations/src/download-client/deluge/deluge-integration.ts index ea3961fde3..cef3ea44b0 100644 --- a/packages/integrations/src/download-client/deluge/deluge-integration.ts +++ b/packages/integrations/src/download-client/deluge/deluge-integration.ts @@ -23,7 +23,7 @@ export class DelugeIntegration extends DownloadClientIntegration { ...(torrent as { completed_time: number } & typeof torrent), id, })); - const paused = torrents.find(({ state }) => this.getTorrentState(state) !== "paused") === undefined; + const paused = torrents.find(({ state }) => DelugeIntegration.getTorrentState(state) !== "paused") === undefined; const status: DownloadClientStatus = { paused, rates: { @@ -33,7 +33,7 @@ export class DelugeIntegration extends DownloadClientIntegration { type, }; const items = torrents.map((torrent): DownloadClientItem => { - const state = this.getTorrentState(torrent.state); + const state = DelugeIntegration.getTorrentState(torrent.state); return { type, id: torrent.id, @@ -96,7 +96,7 @@ export class DelugeIntegration extends DownloadClientIntegration { }); } - private getTorrentState(state: string): DownloadClientItem["state"] { + private static getTorrentState(state: string): DownloadClientItem["state"] { switch (state) { case "Queued": case "Checking": diff --git a/packages/integrations/src/download-client/nzbget/nzbget-integration.ts b/packages/integrations/src/download-client/nzbget/nzbget-integration.ts index 8b8d9a784b..3b0f282b2d 100644 --- a/packages/integrations/src/download-client/nzbget/nzbget-integration.ts +++ b/packages/integrations/src/download-client/nzbget/nzbget-integration.ts @@ -25,7 +25,7 @@ export class NzbGetIntegration extends DownloadClientIntegration { }; const items = queue .map((file): DownloadClientItem => { - const state = this.getNzbQueueState(file.Status); + const state = NzbGetIntegration.getNzbQueueState(file.Status); return { type, id: file.NZBID.toString(), @@ -42,7 +42,7 @@ export class NzbGetIntegration extends DownloadClientIntegration { }) .concat( history.map((file, index): DownloadClientItem => { - const state = this.getNzbHistoryState(file.ScriptStatus); + const state = NzbGetIntegration.getNzbHistoryState(file.ScriptStatus); return { type, id: file.NZBID.toString(), @@ -98,7 +98,7 @@ export class NzbGetIntegration extends DownloadClientIntegration { return new NzbGetClient(url); } - private getNzbQueueState(status: string): DownloadClientItem["state"] { + private static getNzbQueueState(status: string): DownloadClientItem["state"] { switch (status) { case "QUEUED": return "queued"; @@ -109,7 +109,7 @@ export class NzbGetIntegration extends DownloadClientIntegration { } } - private getNzbHistoryState(status: string): DownloadClientItem["state"] { + private static getNzbHistoryState(status: string): DownloadClientItem["state"] { switch (status) { case "FAILURE": return "failed"; diff --git a/packages/integrations/src/download-client/qbittorrent/qbittorrent-integration.ts b/packages/integrations/src/download-client/qbittorrent/qbittorrent-integration.ts index 6d633abf1f..3530604050 100644 --- a/packages/integrations/src/download-client/qbittorrent/qbittorrent-integration.ts +++ b/packages/integrations/src/download-client/qbittorrent/qbittorrent-integration.ts @@ -20,10 +20,10 @@ export class QBitTorrentIntegration extends DownloadClientIntegration { ({ down, up }, { dlspeed, upspeed }) => ({ down: down + dlspeed, up: up + upspeed }), { down: 0, up: 0 }, ); - const paused = torrents.find(({ state }) => this.getTorrentState(state) !== "paused") === undefined; + const paused = torrents.find(({ state }) => QBitTorrentIntegration.getTorrentState(state) !== "paused") === undefined; const status: DownloadClientStatus = { paused, rates, type }; const items = torrents.map((torrent): DownloadClientItem => { - const state = this.getTorrentState(torrent.state); + const state = QBitTorrentIntegration.getTorrentState(torrent.state); return { type, id: torrent.hash, @@ -77,7 +77,7 @@ export class QBitTorrentIntegration extends DownloadClientIntegration { }); } - private getTorrentState(state: string): DownloadClientItem["state"] { + private static getTorrentState(state: string): DownloadClientItem["state"] { switch (state) { case "allocating": case "checkingDL": diff --git a/packages/integrations/src/download-client/sabnzbd/sabnzbd-integration.ts b/packages/integrations/src/download-client/sabnzbd/sabnzbd-integration.ts index 5fefebc2c0..508fcc21ca 100644 --- a/packages/integrations/src/download-client/sabnzbd/sabnzbd-integration.ts +++ b/packages/integrations/src/download-client/sabnzbd/sabnzbd-integration.ts @@ -26,7 +26,7 @@ export class SabnzbdIntegration extends DownloadClientIntegration { }; const items = queue.slots .map((slot): DownloadClientItem => { - const state = this.getNzbQueueState(slot.status); + const state = SabnzbdIntegration.getNzbQueueState(slot.status); const times = slot.timeleft.split(":").reverse(); const time = dayjs .duration({ @@ -52,7 +52,7 @@ export class SabnzbdIntegration extends DownloadClientIntegration { }) .concat( history.slots.map((slot, index): DownloadClientItem => { - const state = this.getNzbHistoryState(slot.status); + const state = SabnzbdIntegration.getNzbHistoryState(slot.status); return { type, id: slot.nzo_id, @@ -109,12 +109,12 @@ export class SabnzbdIntegration extends DownloadClientIntegration { url.searchParams.append(key, value); }); url.searchParams.append("apikey", this.getSecretValue("apiKey")); - return fetch(url) + return await fetch(url) .then((response) => { if (!response.ok) { throw new Error(response.statusText); } - return response.json(); + return response.json() as Promise; }) .catch((error) => { if (error instanceof Error) { @@ -125,7 +125,7 @@ export class SabnzbdIntegration extends DownloadClientIntegration { }); } - private getNzbQueueState(status: string): DownloadClientItem["state"] { + private static getNzbQueueState(status: string): DownloadClientItem["state"] { switch (status) { case "Queued": return "queued"; @@ -136,7 +136,7 @@ export class SabnzbdIntegration extends DownloadClientIntegration { } } - private getNzbHistoryState(status: string): DownloadClientItem["state"] { + private static getNzbHistoryState(status: string): DownloadClientItem["state"] { switch (status) { case "Completed": return "completed"; diff --git a/packages/integrations/src/download-client/transmission/transmission-integration.ts b/packages/integrations/src/download-client/transmission/transmission-integration.ts index f99ffdfab2..0d2e2aa8a7 100644 --- a/packages/integrations/src/download-client/transmission/transmission-integration.ts +++ b/packages/integrations/src/download-client/transmission/transmission-integration.ts @@ -19,10 +19,10 @@ export class TransmissionIntegration extends DownloadClientIntegration { ({ down, up }, { rateDownload, rateUpload }) => ({ down: down + rateDownload, up: up + rateUpload }), { down: 0, up: 0 }, ); - const paused = torrents.find(({ status }) => this.getTorrentState(status) !== "paused") === undefined; + const paused = torrents.find(({ status }) => TransmissionIntegration.getTorrentState(status) !== "paused") === undefined; const status: DownloadClientStatus = { paused, rates, type }; const items = torrents.map((torrent): DownloadClientItem => { - const state = this.getTorrentState(torrent.status); + const state = TransmissionIntegration.getTorrentState(torrent.status); return { type, id: torrent.hashString, @@ -78,7 +78,7 @@ export class TransmissionIntegration extends DownloadClientIntegration { }); } - private getTorrentState(status: number): DownloadClientItem["state"] { + private static getTorrentState(status: number): DownloadClientItem["state"] { switch (status) { case 0: return "paused"; diff --git a/packages/widgets/src/downloads/component.tsx b/packages/widgets/src/downloads/component.tsx index cabdc5c60f..3d62dcaab1 100644 --- a/packages/widgets/src/downloads/component.tsx +++ b/packages/widgets/src/downloads/component.tsx @@ -49,6 +49,7 @@ import { useScopedI18n } from "@homarr/translation/client"; import type { WidgetComponentProps } from "../definition"; //TODO: +// - NzbGet API not working I think // - Data Subscription permission issues <- Need help // - table tbody hide under thead and keep transparency <- Need help // - Add integrations to shouldHide options <- Potential help needed @@ -156,7 +157,7 @@ export default function DownloadClientsWidget({ (type === "torrent" && ((progress === 1 && options.showCompletedTorrent && - upSpeed! >= Number(options.activeTorrentThreshold) * 1024) || + (upSpeed ?? 0) >= Number(options.activeTorrentThreshold) * 1024) || progress !== 1)) || (type === "usenet" && ((progress === 1 && options.showCompletedUsenet) || progress !== 1)), ) @@ -199,7 +200,7 @@ export default function DownloadClientsWidget({ ) .reduce( ({ totalUp, totalDown }, { sent, size, progress }) => ({ - totalUp: isTorrent ? totalUp! + sent! : undefined, + totalUp: isTorrent ? (totalUp ?? 0) + (sent ?? 0) : undefined, totalDown: totalDown + size * progress, }), { totalDown: 0, totalUp: isTorrent ? 0 : undefined }, @@ -211,7 +212,8 @@ export default function DownloadClientsWidget({ ratio: totalUp === undefined ? undefined : totalUp / totalDown, ...pair.data.status, }; - }).sort(({type: typeA},{type: typeB}) => typeA.length - typeB.length), + }) + .sort(({ type: typeA }, { type: typeB }) => typeA.length - typeB.length), [currentItems, integrationIds, options], ); @@ -383,7 +385,7 @@ export default function DownloadClientsWidget({ sortUndefined: "last", Cell: ({ cell }) => { const downSpeed = cell.getValue(); - return {downSpeed !== undefined && humanFileSize(downSpeed) + "/s"}; + return {downSpeed !== undefined && humanFileSize(downSpeed)?.toString().concat("/s")}; }, }, { @@ -510,7 +512,7 @@ export default function DownloadClientsWidget({ sortUndefined: "last", Cell: ({ cell }) => { const upSpeed = cell.getValue(); - return upSpeed !== undefined && {humanFileSize(upSpeed) + "/s"}; + return upSpeed !== undefined && {humanFileSize(upSpeed)?.toString().concat("/s")}; }, }, ], @@ -584,8 +586,8 @@ export default function DownloadClientsWidget({ .filter(({ integration: { kind } }) => ["qBittorrent", "deluge", "transmission"].includes(kind)) .reduce( ({ up, down }, { totalUp, totalDown }) => ({ - up: up + totalUp!, - down: down + totalDown!, + up: up + (totalUp ?? 0), + down: down + (totalDown ?? 0), }), { up: 0, down: 0 }, ); @@ -620,9 +622,9 @@ interface ItemInfoModalProps { } const ItemInfoModal = ({ items, currentIndex, opened, onClose }: ItemInfoModalProps) => { - const item = useMemo(() => items[currentIndex], [currentIndex, opened]); + const item = useMemo(() => items[currentIndex], [items, currentIndex, opened]); const t = useScopedI18n("widget.downloads.states"); - if (item === undefined) return; + if (item === undefined) return <>; return ( @@ -636,11 +638,11 @@ const ItemInfoModal = ({ items, currentIndex, opened, onClose }: ItemInfoModalPr @@ -652,7 +654,7 @@ const ItemInfoModal = ({ items, currentIndex, opened, onClose }: ItemInfoModalPr )} /> - + @@ -667,7 +669,7 @@ const NormalizedLine = ({ itemKey: Exclude; values?: number | string | string[]; }) => { - if (typeof values !== "number" && (values === undefined || values.length === 0)) return; + if (typeof values !== "number" && (values === undefined || values.length === 0)) return <>; const t = useScopedI18n("widget.downloads.items"); const tCommon = useScopedI18n("common"); const translatedKey = t(`${itemKey}.detailsTitle`); @@ -686,7 +688,7 @@ const NormalizedLine = ({ {Array.isArray(values) ? ( {values.map((value) => ( - {value} + {value} ))} ) : ( @@ -704,10 +706,10 @@ interface ClientsControlProps { const ClientsControl = ({ clients, style }: ClientsControlProps) => { const pausedIntegrations: string[] = []; const activeIntegrations: string[] = []; - clients.map((client) => + clients.forEach((client) => client.paused ? pausedIntegrations.push(client.integration.id) : activeIntegrations.push(client.integration.id), ); - const totalSpeed = humanFileSize(clients.reduce((count, { rates: { down } }) => count + down, 0)) + "/s"; + const totalSpeed = humanFileSize(clients.reduce((count, { rates: { down } }) => count + down, 0))?.toString().concat("/s"); const { mutate: mutateResumeQueue } = clientApi.widget.downloads.resume.useMutation(); const { mutate: mutatePauseQueue } = clientApi.widget.downloads.pause.useMutation(); const [opened, { open, close }] = useDisclosure(false); @@ -742,18 +744,24 @@ const ClientsControl = ({ clients, style }: ClientsControlProps) => { {client.rates.up !== undefined ? ( - {"↑ " + humanFileSize(client.rates.up) + "/s"} + + {`↑ ${humanFileSize(client.rates.up)}/s`} + {"-"} - {humanFileSize(client.totalUp ?? 0)} + + {humanFileSize(client.totalUp ?? 0)} + ) : undefined} - - {"↓ " + humanFileSize(client.rates.down) + "/s"} - {"-"} - {humanFileSize(Math.floor(client.totalDown ?? 0))} - - + + {`↓ ${humanFileSize(client.rates.down)}/s`} + + {"-"} + + {humanFileSize(Math.floor(client.totalDown ?? 0))} + + diff --git a/packages/widgets/src/downloads/index.ts b/packages/widgets/src/downloads/index.ts index 392c794200..9016767d1d 100644 --- a/packages/widgets/src/downloads/index.ts +++ b/packages/widgets/src/downloads/index.ts @@ -47,7 +47,8 @@ export const { definition, componentLoader, serverDataLoader } = createWidgetDef step: 1, }), categoryFilter: factory.multiText({ - //defaultValue: [] as string[]; + defaultValue: [] as string[], + validate: z.string(), }), filterIsWhitelist: factory.switch({ defaultValue: false, diff --git a/packages/widgets/src/modals/widget-edit-modal.tsx b/packages/widgets/src/modals/widget-edit-modal.tsx index 7a6f920420..005597dc20 100644 --- a/packages/widgets/src/modals/widget-edit-modal.tsx +++ b/packages/widgets/src/modals/widget-edit-modal.tsx @@ -47,9 +47,9 @@ export const WidgetEditModal = createModal>(({ actions, i z.object({ options: z.object( objectEntries(widgetImports[innerProps.kind].definition.options).reduce( - (acc, [key, value]: [string, { validate?: z.ZodType }]) => { + (acc, [key, value]: [string, { type: string, validate?: z.ZodType }]) => { if (value.validate) { - acc[key] = value.validate; + acc[key] = value.type === "multiText" ? z.array(value.validate).optional() : value.validate; } return acc; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 811a3dfb9c..095a6c88cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -673,7 +673,7 @@ importers: dependencies: '@extractus/feed-extractor': specifier: ^7.1.3 - version: 7.1.3 + version: 7.1.3(encoding@0.1.13) '@homarr/analytics': specifier: workspace:^0.1.0 version: link:../analytics @@ -1271,7 +1271,7 @@ importers: dependencies: '@extractus/feed-extractor': specifier: ^7.1.3 - version: 7.1.3 + version: 7.1.3(encoding@0.1.13) '@homarr/api': specifier: workspace:^0.1.0 version: link:../api @@ -2449,13 +2449,8 @@ packages: '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} - '@szmarczak/http-timer@4.0.6': - resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} - engines: {node: '>=10'} - '@t3-oss/env-core@0.11.0': resolution: {integrity: sha512-PSalC5bG0a7XbyoLydiQdAnx3gICX6IQNctvh+TyLrdFxsxgocdj9Ui7sd061UlBzi+z4aIGjnem1kZx9QtUgQ==} - peerDependencies: typescript: '>=5.0.0' zod: ^3.0.0 @@ -6548,10 +6543,6 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - uuidjs@4.2.14: resolution: {integrity: sha512-Z4iL8AWHlTWeAmi6v1TCPKBF5QkTxpdLDS2yrAm9cA9GvEwWFxnRm7uEEcDY5TrZuO2A/cLQyeuXjlohAMcCIQ==} hasBin: true @@ -7295,10 +7286,10 @@ snapshots: '@eslint/object-schema@2.1.4': {} - '@extractus/feed-extractor@7.1.3': + '@extractus/feed-extractor@7.1.3(encoding@0.1.13)': dependencies: bellajs: 11.2.0 - cross-fetch: 4.0.0 + cross-fetch: 4.0.0(encoding@0.1.13) fast-xml-parser: 4.4.0 html-entities: 2.5.2 transitivePeerDependencies: @@ -7658,10 +7649,6 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.6.2 - '@szmarczak/http-timer@4.0.6': - dependencies: - defer-to-connect: 2.0.1 - '@t3-oss/env-core@0.11.0(typescript@5.5.4)(zod@3.23.8)': dependencies: zod: 3.23.8 @@ -8035,14 +8022,7 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.14.11 - - '@types/cacheable-request@6.0.3': - dependencies: - '@types/http-cache-semantics': 4.0.4 - '@types/keyv': 3.1.4 - '@types/node': 20.14.11 - '@types/responselike': 1.0.3 + '@types/node': 20.14.13 '@types/chroma-js@2.4.4': {} @@ -8101,9 +8081,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.14.11 - - '@types/http-cache-semantics@4.0.4': {} + '@types/node': 20.14.13 '@types/http-errors@2.0.4': {} @@ -9075,9 +9053,9 @@ snapshots: dependencies: cross-spawn: 7.0.3 - cross-fetch@4.0.0: + cross-fetch@4.0.0(encoding@0.1.13): dependencies: - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) transitivePeerDependencies: - encoding @@ -12038,7 +12016,6 @@ snapshots: docker-compose: 0.24.8 dockerode: 3.3.5 get-port: 5.1.1 - node-fetch: 2.7.0(encoding@0.1.13) proper-lockfile: 4.1.2 properties-reader: 2.3.0 ssh-remote-port-forward: 1.0.4 @@ -12380,8 +12357,6 @@ snapshots: uuid@8.3.2: {} - uuid@9.0.1: {} - uuidjs@4.2.14: {} v8-compile-cache-lib@3.0.1: {} @@ -12700,8 +12675,6 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.5.2 - - zipcodes-regex@1.0.3: {} zod@3.23.8: {}