From 678059b1d31e21914db7238ddc4d021c14a88f7a Mon Sep 17 00:00:00 2001 From: Thomas Camlong <49837342+ajnart@users.noreply.github.com> Date: Thu, 28 Jul 2022 13:37:17 +0200 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20spelling=20error?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4bc288f5b..9699df517 100644 --- a/README.md +++ b/README.md @@ -64,9 +64,9 @@ If you have any questions about Homarr or want to share information with us, ple ## ✨ Features - Integrates with services you use. -- Search the web direcetly from your homepage. +- Search the web directly from your homepage. - Real-time status indicator for every service. -- Automatically finds icons while you type the name of a serivce. +- Automatically finds icons while you type the name of a service. - Widgets that can display all types of information. - Easy deployment with Docker. - Very light-weight and fast. From 818bfad5f4c00299460dcec3cc8fbc2bf44a68ec Mon Sep 17 00:00:00 2001 From: ajnart Date: Mon, 1 Aug 2022 11:25:53 +0200 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=90=B3=20Change=20docker=20image=20to?= =?UTF-8?q?=20Linuxserver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9d2244dd1..af28768ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,6 @@ -FROM node:16-alpine +FROM ghcr.io/linuxserver/baseimage-alpine:3.16 WORKDIR /app -RUN apk add tzdata - ENV NEXT_TELEMETRY_DISABLED 1 ENV NODE_ENV production From ff5a334f799c26d2e7abb3105810ad24dc4c30c6 Mon Sep 17 00:00:00 2001 From: ajnart Date: Mon, 1 Aug 2022 14:13:35 +0200 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=94=A7=20Use=20PasswordInput=20for=20?= =?UTF-8?q?credentials?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/AppShelf/AddAppShelfItem.tsx | 278 ++++++++++---------- 1 file changed, 138 insertions(+), 140 deletions(-) diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx index 2d41feaee..f319138da 100644 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ b/src/components/AppShelf/AddAppShelfItem.tsx @@ -8,7 +8,7 @@ import { LoadingOverlay, Modal, MultiSelect, - ScrollArea, + PasswordInput, Select, Switch, Tabs, @@ -194,146 +194,144 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & > - - - + + - - - - { - e.preventDefault(); - }} - getCreateLabel={(query) => `+ Create "${query}"`} - onCreate={(query) => {}} - {...form.getInputProps('category')} - /> - - {(form.values.type === 'Sonarr' || - form.values.type === 'Radarr' || - form.values.type === 'Lidarr' || - form.values.type === 'Readarr') && ( - <> - { - form.setFieldValue('apiKey', event.currentTarget.value); - }} - error={form.errors.apiKey && 'Invalid API key'} - /> - - Get your API key{' '} - - here. - - - - )} - {form.values.type === 'qBittorrent' && ( - <> - { - form.setFieldValue('username', event.currentTarget.value); - }} - error={form.errors.username && 'Invalid username'} - /> - { - form.setFieldValue('password', event.currentTarget.value); - }} - error={form.errors.password && 'Invalid password'} - /> - - )} - {form.values.type === 'Deluge' && ( - <> - { - form.setFieldValue('password', event.currentTarget.value); - }} - error={form.errors.password && 'Invalid password'} - /> - - )} - {form.values.type === 'Transmission' && ( - <> - { - form.setFieldValue('username', event.currentTarget.value); - }} - error={form.errors.username && 'Invalid username'} - /> - { - form.setFieldValue('password', event.currentTarget.value); - }} - error={form.errors.password && 'Invalid password'} - /> - - )} - - + + + + { + e.preventDefault(); + }} + getCreateLabel={(query) => `+ Create "${query}"`} + onCreate={(query) => {}} + {...form.getInputProps('category')} + /> + + {(form.values.type === 'Sonarr' || + form.values.type === 'Radarr' || + form.values.type === 'Lidarr' || + form.values.type === 'Readarr') && ( + <> + { + form.setFieldValue('apiKey', event.currentTarget.value); + }} + error={form.errors.apiKey && 'Invalid API key'} + /> + + Get your API key{' '} + + here. + + + + )} + {form.values.type === 'qBittorrent' && ( + <> + { + form.setFieldValue('username', event.currentTarget.value); + }} + error={form.errors.username && 'Invalid username'} + /> + { + form.setFieldValue('password', event.currentTarget.value); + }} + error={form.errors.password && 'Invalid password'} + /> + + )} + {form.values.type === 'Deluge' && ( + <> + { + form.setFieldValue('password', event.currentTarget.value); + }} + error={form.errors.password && 'Invalid password'} + /> + + )} + {form.values.type === 'Transmission' && ( + <> + { + form.setFieldValue('username', event.currentTarget.value); + }} + error={form.errors.username && 'Invalid username'} + /> + { + form.setFieldValue('password', event.currentTarget.value); + }} + error={form.errors.password && 'Invalid password'} + /> + + )} + From 1fa2060e2bca91d5708bd054512395542c89527c Mon Sep 17 00:00:00 2001 From: ajnart Date: Mon, 1 Aug 2022 16:36:00 +0200 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=94=A7=20Dashdot=20module=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #316 --- src/modules/dashdot/DashdotModule.tsx | 42 ++++++++++++++++++--------- src/pages/api/modules/dashdot.ts | 29 ++++++++++++++++++ 2 files changed, 57 insertions(+), 14 deletions(-) create mode 100644 src/pages/api/modules/dashdot.ts diff --git a/src/modules/dashdot/DashdotModule.tsx b/src/modules/dashdot/DashdotModule.tsx index 35bc005d4..1cb9bc716 100644 --- a/src/modules/dashdot/DashdotModule.tsx +++ b/src/modules/dashdot/DashdotModule.tsx @@ -13,6 +13,10 @@ export const DashdotModule = asModule({ icon: CalendarIcon, component: DashdotComponent, options: { + url: { + name: 'Dash. URL', + value: '', + }, cpuMultiView: { name: 'CPU Multi-Core View', value: false, @@ -88,12 +92,12 @@ const bytePrettyPrint = (byte: number): string => ? `${(byte / 1024).toFixed(1)} KiB` : `${byte.toFixed(1)} B`; -const useJson = (service: serviceItem | undefined, url: string) => { +const useJson = (targetUrl: string, url: string) => { const [data, setData] = useState(); const doRequest = async () => { try { - const resp = await axios.get(url, { baseURL: service?.url }); + const resp = await axios.get(`/api/modules/dashdot?url=${url}&base=${targetUrl}`); setData(resp.data); // eslint-disable-next-line no-empty @@ -101,10 +105,10 @@ const useJson = (service: serviceItem | undefined, url: string) => { }; useEffect(() => { - if (service?.url) { + if (targetUrl) { doRequest(); } - }, [service?.url]); + }, [targetUrl]); return data; }; @@ -118,8 +122,10 @@ export function DashdotComponent() { const dashConfig = config.modules?.[DashdotModule.title] .options as typeof DashdotModule['options']; const isCompact = dashConfig?.useCompactView?.value ?? false; - const dashdotService = config.services.filter((service) => service.type === 'Dash.')[0]; - + const dashdotService: serviceItem | undefined = config.services.filter( + (service) => service.type === 'Dash.' + )[0]; + const dashdotUrl = dashdotService?.url ?? dashConfig?.url?.value ?? ''; const enabledGraphs = dashConfig?.graphs?.value ?? ['CPU', 'RAM', 'Storage', 'Network']; const cpuEnabled = enabledGraphs.includes('CPU'); const storageEnabled = enabledGraphs.includes('Storage'); @@ -127,8 +133,8 @@ export function DashdotComponent() { const networkEnabled = enabledGraphs.includes('Network'); const gpuEnabled = enabledGraphs.includes('GPU'); - const info = useJson(dashdotService, '/info'); - const storageLoad = useJson(dashdotService, '/load/storage'); + const info = useJson(dashdotUrl, '/info'); + const storageLoad = useJson(dashdotUrl, '/load/storage'); const totalUsed = (storageLoad?.layout as any[])?.reduce((acc, curr) => (curr.load ?? 0) + acc, 0) ?? 0; @@ -166,13 +172,23 @@ export function DashdotComponent() { }, ].filter((g) => g.enabled); + if (dashdotUrl === '') { + return ( +
+

Dash.

+

+ No dash. service found. Please add one to your Homarr dashboard or set a dashdot URL in + the module options +

+
+ ); + } + return (

Dash.

- {!dashdotService ? ( -

No dash. service found. Please add one to your Homarr dashboard.

- ) : !info ? ( + {!info ? (

Cannot acquire information from dash. - are you running the latest version?

) : (
@@ -209,9 +225,7 @@ export function DashdotComponent() { } key={graph.name} title={graph.name} - src={`${ - dashdotService.url - }?singleGraphMode=true&graph=${graph.name.toLowerCase()}&theme=${colorScheme}&surface=${(colorScheme === + src={`${dashdotUrl}?singleGraphMode=true&graph=${graph.name.toLowerCase()}&theme=${colorScheme}&surface=${(colorScheme === 'dark' ? theme.colors.dark[7] : theme.colors.gray[0] diff --git a/src/pages/api/modules/dashdot.ts b/src/pages/api/modules/dashdot.ts new file mode 100644 index 000000000..8f9a990a5 --- /dev/null +++ b/src/pages/api/modules/dashdot.ts @@ -0,0 +1,29 @@ +import axios from 'axios'; +import { NextApiRequest, NextApiResponse } from 'next'; + +async function Get(req: NextApiRequest, res: NextApiResponse) { + // Extract url from req.query as string + const { url, base } = req.query; + + // If no url is provided, return an error + if (!url || !base) { + return res.status(400).json({ + message: 'Missing required parameter in url', + }); + } + // Get the origin URL + const response = await axios.get(url as string, { baseURL: base as string }); + // Return the response + return res.status(200).json(response.data); +} + +export default async (req: NextApiRequest, res: NextApiResponse) => { + // Filter out if the reuqest is a POST or a GET + if (req.method === 'GET') { + return Get(req, res); + } + return res.status(405).json({ + statusCode: 405, + message: 'Method not allowed', + }); +}; From bc0503842718d2960d0288c5ba895e73f5f8f02c Mon Sep 17 00:00:00 2001 From: ajnart Date: Mon, 1 Aug 2022 17:12:04 +0200 Subject: [PATCH 5/5] =?UTF-8?q?=E2=9C=A8=20Add=20caching=20for=20icons=20w?= =?UTF-8?q?ith=20an=20image=20proxy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #307 --- next.config.js | 3 +++ src/components/AppShelf/AppShelfItem.tsx | 10 ++++++---- src/pages/api/imageproxy.ts | 10 ++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 src/pages/api/imageproxy.ts diff --git a/next.config.js b/next.config.js index 59a7bd7a8..b91c26880 100644 --- a/next.config.js +++ b/next.config.js @@ -5,6 +5,9 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({ }); module.exports = withBundleAnalyzer({ + images: { + domains: ['cdn.jsdelivr.net'], + }, reactStrictMode: false, experimental: { outputStandalone: true, diff --git a/src/components/AppShelf/AppShelfItem.tsx b/src/components/AppShelf/AppShelfItem.tsx index e611166c3..6c35923e8 100644 --- a/src/components/AppShelf/AppShelfItem.tsx +++ b/src/components/AppShelf/AppShelfItem.tsx @@ -3,7 +3,6 @@ import { Card, Anchor, AspectRatio, - Image, Center, createStyles, useMantineColorScheme, @@ -12,6 +11,7 @@ import { motion } from 'framer-motion'; import { useState } from 'react'; import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; +import Image from 'next/image'; import { serviceItem } from '../../tools/types'; import PingComponent from '../../modules/ping/PingModule'; import AppShelfMenu from './AppShelfMenu'; @@ -121,11 +121,13 @@ export function AppShelfItem(props: any) { }} > { if (service.openedUrl) { window.open(service.openedUrl, service.newTab === false ? '_top' : '_blank'); diff --git a/src/pages/api/imageproxy.ts b/src/pages/api/imageproxy.ts new file mode 100644 index 000000000..575960467 --- /dev/null +++ b/src/pages/api/imageproxy.ts @@ -0,0 +1,10 @@ +import { NextApiRequest, NextApiResponse } from 'next'; + +export default async (req: NextApiRequest, res: NextApiResponse) => { + const url = decodeURIComponent(req.query.url as string); + const result = await fetch(url); + const body = await result.body; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + body.pipe(res); +};