diff --git a/.dockerignore b/.dockerignore index 125cd198c..13609ca43 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,5 +2,8 @@ Dockerfile .dockerignore node_modules npm-debug.log -README.md +*.md .git +.github +LICENSE +docs/ diff --git a/README.md b/README.md index 72aa27cd1..e6cccbef6 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Homarr is a simple and lightweight homepage for your server, that helps you easi It integrates with the services you use to display information on the homepage (E.g. Show upcoming Sonarr/Radarr releases). -For a full list of integrations look at: [wiki/integrations](#). +For a full list of integrations look at: [wiki/integrations](https://github.com/ajnart/homarr/wiki/Integrations) If you have any questions about Homarr or want to share information with us, please go to one of the following places: diff --git a/data/constants.ts b/data/constants.ts index 2eadce9ae..25f52f298 100644 --- a/data/constants.ts +++ b/data/constants.ts @@ -1,2 +1,2 @@ export const REPO_URL = 'ajnart/homarr'; -export const CURRENT_VERSION = 'v0.7.1'; +export const CURRENT_VERSION = 'v0.7.2'; diff --git a/package.json b/package.json index 1d407ac5f..3c02b4067 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homarr", - "version": "0.7.1", + "version": "0.7.2", "description": "Homarr - A homepage for your server.", "repository": { "type": "git", diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx index f9229d220..206cb5ee4 100644 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ b/src/components/AppShelf/AddAppShelfItem.tsx @@ -123,13 +123,9 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & validate: { apiKey: () => null, // Validate icon with a regex - icon: (value: string) => { - // Regex to match everything that ends with and icon extension - if (!value.match(/\.(png|jpg|jpeg|gif|svg)$/)) { - return 'Please enter a valid icon URL'; - } - return null; - }, + icon: (value: string) => + // Disable matching to allow any values + null, // Validate url with a regex http/https url: (value: string) => { try { @@ -321,9 +317,20 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & /> )} - {(form.values.type === 'Deluge' || - form.values.type === 'Transmission' || - form.values.type === 'qBittorrent') && ( + {form.values.type === 'Deluge' && ( + <> + { + form.setFieldValue('password', event.currentTarget.value); + }} + error={form.errors.password && 'Invalid password'} + /> + + )} + {form.values.type === 'Transmission' && ( <> void } & /> { form.setFieldValue('password', event.currentTarget.value); diff --git a/src/components/AppShelf/AppShelf.tsx b/src/components/AppShelf/AppShelf.tsx index 05c0d1ada..083de4626 100644 --- a/src/components/AppShelf/AppShelf.tsx +++ b/src/components/AppShelf/AppShelf.tsx @@ -20,15 +20,30 @@ import DownloadComponent from '../modules/downloads/DownloadsModule'; const useStyles = createStyles((theme, _params) => ({ item: { - borderBottom: 0, overflow: 'hidden', - border: '1px solid transparent', - borderRadius: theme.radius.lg, + borderLeft: '3px solid transparent', + borderRight: '3px solid transparent', + borderBottom: '3px solid transparent', + borderRadius: '20px', + borderColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1], marginTop: theme.spacing.md, }, - itemOpened: { - borderColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[3], + control: { + backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1], + borderRadius: theme.spacing.md, + + '&:hover': { + backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1], + }, + }, + + content: { + margin: theme.spacing.md, + }, + + label: { + overflow: 'visible', }, })); @@ -147,11 +162,6 @@ const AppShelf = (props: any) => { order={2} iconPosition="right" multiple - styles={{ - item: { - borderRadius: '20px', - }, - }} initialState={toggledCategories} onChange={(idx) => settoggledCategories(idx)} > diff --git a/src/components/AppShelf/AppShelfMenu.tsx b/src/components/AppShelf/AppShelfMenu.tsx index 5aada187a..78f9b10ad 100644 --- a/src/components/AppShelf/AppShelfMenu.tsx +++ b/src/components/AppShelf/AppShelfMenu.tsx @@ -20,11 +20,7 @@ export default function AppShelfMenu(props: any) { onClose={() => setOpened(false)} title="Modify a service" > - + Search engine + + Tip: %s can be used as a placeholder for the query. + { setCustomSearchUrl(event.currentTarget.value); @@ -98,21 +107,26 @@ export default function CommonSettings(args: any) { {CURRENT_VERSION} - - Made with ❤️ by @ - + - ajnart - - + Made with ❤️ by @ + + ajnart + + + component="a" href="https://discord.gg/aCsmEV5RgA" size="lg"> + + + ); diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 20374f609..cbfd807be 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -78,7 +78,7 @@ export function Header(props: any) { > {(styles) => (
- + diff --git a/src/components/modules/calendar/CalendarModule.tsx b/src/components/modules/calendar/CalendarModule.tsx index 4665f4480..49624e0bc 100644 --- a/src/components/modules/calendar/CalendarModule.tsx +++ b/src/components/modules/calendar/CalendarModule.tsx @@ -71,11 +71,13 @@ export default function CalendarComponent(props: any) { const currentSonarrMedias: any[] = []; Promise.all( sonarrServices.map((service) => - getMedias(service, 'sonarr').then((res) => { - currentSonarrMedias.push(...res.data); - }).catch(() => { - currentSonarrMedias.push([]); - }) + getMedias(service, 'sonarr') + .then((res) => { + currentSonarrMedias.push(...res.data); + }) + .catch(() => { + currentSonarrMedias.push([]); + }) ) ).then(() => { setSonarrMedias(currentSonarrMedias); @@ -83,11 +85,13 @@ export default function CalendarComponent(props: any) { const currentRadarrMedias: any[] = []; Promise.all( radarrServices.map((service) => - getMedias(service, 'radarr').then((res) => { - currentRadarrMedias.push(...res.data); - }).catch(() => { - currentRadarrMedias.push([]); - }) + getMedias(service, 'radarr') + .then((res) => { + currentRadarrMedias.push(...res.data); + }) + .catch(() => { + currentRadarrMedias.push([]); + }) ) ).then(() => { setRadarrMedias(currentRadarrMedias); @@ -95,11 +99,13 @@ export default function CalendarComponent(props: any) { const currentLidarrMedias: any[] = []; Promise.all( lidarrServices.map((service) => - getMedias(service, 'lidarr').then((res) => { - currentLidarrMedias.push(...res.data); - }).catch(() => { - currentLidarrMedias.push([]); - }) + getMedias(service, 'lidarr') + .then((res) => { + currentLidarrMedias.push(...res.data); + }) + .catch(() => { + currentLidarrMedias.push([]); + }) ) ).then(() => { setLidarrMedias(currentLidarrMedias); @@ -107,11 +113,13 @@ export default function CalendarComponent(props: any) { const currentReadarrMedias: any[] = []; Promise.all( readarrServices.map((service) => - getMedias(service, 'readarr').then((res) => { - currentReadarrMedias.push(...res.data); - }).catch(() => { - currentReadarrMedias.push([]); - }) + getMedias(service, 'readarr') + .then((res) => { + currentReadarrMedias.push(...res.data); + }) + .catch(() => { + currentReadarrMedias.push([]); + }) ) ).then(() => { setReadarrMedias(currentReadarrMedias); diff --git a/src/components/modules/downloads/DownloadsModule.tsx b/src/components/modules/downloads/DownloadsModule.tsx index 042ad4b74..03eefef29 100644 --- a/src/components/modules/downloads/DownloadsModule.tsx +++ b/src/components/modules/downloads/DownloadsModule.tsx @@ -59,7 +59,7 @@ export default function DownloadComponent() { setIsLoading(false); }); }, 5000); - }, [config.services]); + }, []); if (downloadServices.length === 0) { return ( diff --git a/src/components/modules/ping/PingModule.tsx b/src/components/modules/ping/PingModule.tsx index b77d8c2fd..b0c333bfa 100644 --- a/src/components/modules/ping/PingModule.tsx +++ b/src/components/modules/ping/PingModule.tsx @@ -23,19 +23,19 @@ export default function PingComponent(props: any) { const exists = config.modules?.[PingModule.title]?.enabled ?? false; function statusCheck(response: AxiosResponse) { - const { status }: {status: string[]} = props; + const { status }: { status: string[] } = props; //Default Status let acceptableStatus = ['200']; if (status !== undefined && status.length) { acceptableStatus = status; } // Checks if reported status is in acceptable status array - if (acceptableStatus.indexOf((response.status).toString()) >= 0) { + if (acceptableStatus.indexOf(response.status.toString()) >= 0) { setOnline('online'); setResponse(response.status); } else { setOnline('down'); - setResponse(response.status) + setResponse(response.status); } } @@ -59,7 +59,13 @@ export default function PingComponent(props: any) { setOpened(false)} target={ service.type === 'qBittorrent') - .at(0); - const delugeService = config.services.filter((service) => service.type === 'Deluge').at(0); - const transmissionService = config.services - .filter((service) => service.type === 'Transmission') - .at(0); - if (!qBittorrentService && !delugeService && !transmissionService) { + const qBittorrentServices = config.services.filter((service) => service.type === 'qBittorrent'); + const delugeServices = config.services.filter((service) => service.type === 'Deluge'); + const transmissionServices = config.services.filter((service) => service.type === 'Transmission'); + + const torrents: NormalizedTorrent[] = []; + + if (!qBittorrentServices && !delugeServices && !transmissionServices) { return res.status(500).json({ statusCode: 500, - message: 'Missing service', + message: 'Missing services', }); } - if (qBittorrentService) { - torrents.push( - ...( - await new QBittorrent({ - baseUrl: qBittorrentService.url, - username: qBittorrentService.username, - password: qBittorrentService.password, - }).getAllData() - ).torrents - ); - } - if (delugeService) { - torrents.push( - ...( - await new Deluge({ - baseUrl: delugeService.url, - password: 'password' in delugeService ? delugeService.password : '', - }).getAllData() - ).torrents - ); - } - if (transmissionService) { - torrents.push( - ...( - await new Transmission({ - baseUrl: transmissionService.url, - username: transmissionService.username, - password: 'password' in transmissionService ? transmissionService.password : '', - }).getAllData() - ).torrents - ); - } + await Promise.all( + qBittorrentServices.map((service) => + new QBittorrent({ + baseUrl: service.url, + username: service.username, + password: service.password, + }) + .getAllData() + .then((e) => torrents.push(...e.torrents)) + ) + ); + await Promise.all( + delugeServices.map((service) => + new Deluge({ + baseUrl: service.url, + password: 'password' in service ? service.password : '', + }) + .getAllData() + .then((e) => torrents.push(...e.torrents)) + ) + ); + // Map transmissionServices + await Promise.all( + transmissionServices.map((service) => + new Transmission({ + baseUrl: service.url, + username: 'username' in service ? service.username : '', + password: 'password' in service ? service.password : '', + }) + .getAllData() + .then((e) => torrents.push(...e.torrents)) + ) + ); res.status(200).json(torrents); } diff --git a/src/tools/types.ts b/src/tools/types.ts index 6ed888930..c66197147 100644 --- a/src/tools/types.ts +++ b/src/tools/types.ts @@ -33,30 +33,30 @@ interface ConfigModule { } export const StatusCodes = [ - {value: '200', label: '200 - OK', group:'Sucessful responses'}, - {value: '204', label: '204 - No Content', group:'Sucessful responses'}, - {value: '301', label: '301 - Moved Permanently', group:'Redirection responses'}, - {value: '302', label: '302 - Found / Moved Temporarily', group:'Redirection responses'}, - {value: '304', label: '304 - Not Modified', group:'Redirection responses'}, - {value: '307', label: '307 - Temporary Redirect', group:'Redirection responses'}, - {value: '308', label: '308 - Permanent Redirect', group:'Redirection responses'}, - {value: '400', label: '400 - Bad Request', group:'Client error responses'}, - {value: '401', label: '401 - Unauthorized', group:'Client error responses'}, - {value: '403', label: '403 - Forbidden', group:'Client error responses'}, - {value: '404', label: '404 - Not Found', group:'Client error responses'}, - {value: '408', label: '408 - Request Timeout', group:'Client error responses'}, - {value: '410', label: '410 - Gone', group:'Client error responses'}, - {value: '429', label: '429 - Too Many Requests', group:'Client error responses'}, - {value: '500', label: '500 - Internal Server Error', group:'Server error responses'}, - {value: '502', label: '502 - Bad Gateway', group:'Server error responses'}, - {value: '503', label: '503 - Service Unavailable', group:'Server error responses'}, - {value: '054', label: '504 - Gateway Timeout Error', group:'Server error responses'}, - ]; + { value: '200', label: '200 - OK', group: 'Sucessful responses' }, + { value: '204', label: '204 - No Content', group: 'Sucessful responses' }, + { value: '301', label: '301 - Moved Permanently', group: 'Redirection responses' }, + { value: '302', label: '302 - Found / Moved Temporarily', group: 'Redirection responses' }, + { value: '304', label: '304 - Not Modified', group: 'Redirection responses' }, + { value: '307', label: '307 - Temporary Redirect', group: 'Redirection responses' }, + { value: '308', label: '308 - Permanent Redirect', group: 'Redirection responses' }, + { value: '400', label: '400 - Bad Request', group: 'Client error responses' }, + { value: '401', label: '401 - Unauthorized', group: 'Client error responses' }, + { value: '403', label: '403 - Forbidden', group: 'Client error responses' }, + { value: '404', label: '404 - Not Found', group: 'Client error responses' }, + { value: '408', label: '408 - Request Timeout', group: 'Client error responses' }, + { value: '410', label: '410 - Gone', group: 'Client error responses' }, + { value: '429', label: '429 - Too Many Requests', group: 'Client error responses' }, + { value: '500', label: '500 - Internal Server Error', group: 'Server error responses' }, + { value: '502', label: '502 - Bad Gateway', group: 'Server error responses' }, + { value: '503', label: '503 - Service Unavailable', group: 'Server error responses' }, + { value: '054', label: '504 - Gateway Timeout Error', group: 'Server error responses' }, +]; export const Targets = [ - {value: '_blank', label: 'New Tab'}, - {value: '_top', label: 'Same Window'} -] + { value: '_blank', label: 'New Tab' }, + { value: '_top', label: 'Same Window' }, +]; export const ServiceTypeList = [ 'Other',