diff --git a/README.md b/README.md
index 3c1ad52c0..5a45f1438 100644
--- a/README.md
+++ b/README.md
@@ -1,69 +1,97 @@
-
Homarr
-
-
- Don't forget to star the repo if you enjoy the Homarr project!
-
-
-
-
-
-
-
-
-
-
-
-
-
- A homepage for your server.
-
- Demo ↗️ • Install ➡️
-
-
- Join the discord!
-
-
-
-
+
+
Homarr
+
+
+
+
+
+
+
+
+
+
+
+
+
-# 📃 Table of Contents
-- [📃 Table of Contents](#-table-of-contents)
-- [🚀 Getting Started](#-getting-started)
- - [ℹ️ About](#ℹ️-about)
- - [💥 Known Issues](#-known-issues)
- - [⚡ Installation](#-installation)
- - [🐳 Deploying from Docker Image](#-deploying-from-docker-image)
- - [🛠️ Building from Source](#%EF%B8%8F-building-from-source)
- - [📖 Guides](#-guides)
- - [🔁 Drag and Drop (Rearrange)](#-drag-and-drop-rearrange)
- - [🔧 Configuration](#-configuration)
- - [🧩 Integrations](#--integrations)
- - [🧑🤝🧑 Multiple Configs](#-multiple-configs)
- - [🐻 Icons](#-icons)
- - [📊 Modules](#-modules)
- - [🔍 Search Bar](#-search-bar)
-- [💖 Contributing](#-contributing)
- - [🍏 Request Icons](#-request-icons)
+
+
+Join the discord! — Don't forget to star the repo if you are enjoying the project!
+
+
+ Demo ↗️ • Install ➡️ • Read the Wiki 📄
+
+---
-
-# 🚀 Getting Started
-
-## ℹ️ About
+
+
Homarr is a simple and lightweight homepage for your server, that helps you easily access all of your services in one place.
-
-**[⤴️ Back to Top](#-table-of-contents)**
+
+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](#).
+
+If you have any questions about Homarr or want to share information with us, please go to one of the following places:
+
+- [Github Discussions](https://github.com/ajnart/homarr/discussions)
+- [Discord Server](https://discord.gg/aCsmEV5RgA)
+
+*Before you file an [issue](https://github.com/ajnart/homarr/issues/new/choose), make sure you have the read [known issues](#-known-issues) section.*
+
+**For more information, [read the wiki!](https://github.com/ajnart/homarr/wiki)**
+
+
+ Table of Contents
+
+
+- [✨ Features](#-features)
+- [👀 Preview](#-preview)
+- [💥 Known Issues](#-known-issues)
+- [🚀 Installation](#-installation)
+ - [🐳 Deploying from Docker Image](#-deploying-from-docker-image)
+ - [🛠️ Building from Source](#️-building-from-source)
+- [💖 Contributing](#-contributing)
+- [📜 License](#-license)
+
+
+
+
+---
+
+## ✨ Features
+- Integrates with services you use.
+- Search the web direcetly from your homepage.
+- Real-time status indicator for every service.
+- Automatically finds icons while you type the name of a serivce.
+- Widgets that can display all types of information.
+- Easy deployment with Docker.
+- Very light-weight and fast.
+- Free and Open-Source.
+- And more...
+
+**[⤴️ Back to Top](#homarr)**
+
+---
+
+## 👀 Preview
+
+
+**[⤴️ Back to Top](#homarr)**
+
+---
## 💥 Known Issues
- Posters on the Calendar get blocked by adblockers. (IMDb posters)
-**[⤴️ Back to Top](#-table-of-contents)**
+**[⤴️ Back to Top](#homarr)**
-## ⚡ Installation
+---
+## 🚀 Installation
### 🐳 Deploying from Docker Image
> Supported architectures: x86-64, ARM, ARM64
@@ -72,16 +100,16 @@ _Requirements_:
**Standard Docker Install**
```sh
-docker run --name homarr -p 7575:7575 -v /data/docker/homarr:/app/data/configs -d ghcr.io/ajnart/homarr:latest
+docker run --name homarr --restart unless-stopped -p 7575:7575 -v /data/docker/homarr:/app/data/configs -d ghcr.io/ajnart/homarr:latest
```
**Docker Compose**
```yml
---
version: '3'
-#--------------------------------------------------------------------------------------------#
-# Homarr - A homepage for your server. #
-#--------------------------------------------------------------------------------------------#
+#---------------------------------------------------------------------#
+# Homarr - A homepage for your server. #
+#---------------------------------------------------------------------#
services:
homarr:
container_name: homarr
@@ -93,7 +121,13 @@ services:
- '7575:7575'
```
-***Getting EACCESS errors in the logs? Try running `sudo chmod 775 /directory-you-mounted-to`!***
+```sh
+docker compose up -d
+```
+
+*Getting EACCESS errors in the logs? Try running `sudo chmod 777 /directory-you-mounted-to`!*
+
+**[⤴️ Back to Top](#homarr)**
### 🛠️ Building from Source
@@ -110,101 +144,54 @@ _Requirements_:
- Start the NextJS web server: ``yarn start``
- *Note: If you want to update the code in real time, launch with ``yarn dev``*
-## 📖 Guides
+**[⤴️ Back to Top](#homarr)**
-### 🔁 Drag and Drop (Rearrange)
-You can rearrange items by Drag and Dropping them to any position. To Drag an Drop, click and hold an icon for 250ms and then drag it to the desired position.
+---
-## 🔧 Configuration
-
-### 🧩 Integrations
-
-Homarr natively integrates with your services. Here is a list of all supported services.
-
-**Emby**
-*The Emby integration is still in development.*
-
-**Lidarr**
-*The Lidarr integration is still in development.*
-
-**Sonarr**
-*Sonarr needs an API key.*
-Make a new API key in `Advanced > Security > Create new API key`
-**Current integration:** Upcoming media is displayed in the **Calendar** module.
-
-**Plex**
-*The Plex integration is still in development.*
-
-**Radarr**
-*Radarr needs an API key.*
-Make a new API key in `Advanced > Security > Create new API key`
-**Current integration:** Upcoming media is displayed in the **Calendar** module.
-
-**qBittorent**
-*The qBittorent integration is still in development.*
-
-**[⤴️ Back to Top](#-table-of-contents)**
-
-### 🧑🤝🧑 Multiple Configs
-
-Homarr allows the usage of multiple configs. You can add a new config in two ways.
-
-**Drag-and-Drop**
-1. Download your config from the Homarr settings.
-2. Change the name of the `.json` file and the name in the `.json` file to any name you want *(just make sure it's different)*.
-3. Drag-and-Drop the file into the Homarr tab in your browser.
-4. Change the config in settings.
-
-**Using a filebrowser**
-1. Locate your mounted `default.json` file.
-2. Duplicate your `default.json` file.
-3. Change the name of the `.json` file and the name in the `.json` file to any name you want *(just make sure it's different)*.
-4. Refresh the Homarr tab in your browser.
-5. Change the config in settings.
-
-**[⤴️ Back to Top](#-table-of-contents)**
-
-### 🐻 Icons
-
-The icons used in Homarr are automatically requested from the [dashboard-icons](https://github.com/walkxhub/dashboard-icons) repo.
-
-Icons are requested in the following way:
-`Grab name > Replace ' ' with '-' > .toLower() > https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/{name}.png`
-
-**[⤴️ Back to Top](#-table-of-contents)**
-
-### 📊 Modules
-
-Modules are blocks shown on the sides of the Homarr dashboard that display information. They can be enabled in settings.
-
-**Clock Module**
-The Clock Module will display your current time and date.
-
-**Calendar Module**
-The Calendar Module uses [integrations](#--integrations-1) to display new content.
-
-**Weather Module**
-The Weather Module uses your devices location to display the current, highest, and lowest temperature.
-
-**[⤴️ Back to Top](#-table-of-contents)**
-
-### 🔍 Search Bar
-
-The Search Bar will open any Search Query after the Query URL you've specified in settings.
-
-*(Eg. `https://www.google.com/search?q=*Your Query will be inserted here*`)*
-
-**[⤴️ Back to Top](#-table-of-contents)**
-
-# 💖 Contributing
+## 💖 Contributing
**Please read our [Contribution Guidelines](/CONTRIBUTING.md)**
All contributions are highly appreciated.
-**[⤴️ Back to Top](#-table-of-contents)**
+**[⤴️ Back to Top](#homarr)**
-## 🍏 Request Icons
+---
-The icons used in Homarr are automatically requested from the [dashboard-icons](https://github.com/walkxhub/dashboard-icons) repo. You can make a icon request by creating an [issue](https://github.com/walkxhub/dashboard-icons/issues/new/choose).
-
-**[⤴️ Back to Top](#-table-of-contents)**
+
+## 📜 License
+Homarr is Licensed under [MIT](https://en.wikipedia.org/wiki/MIT_License)
+
+```txt
+Copyright © 2022 Thomas "ajnart" Camlong
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
+
+**[⤴️ Back to Top](#homarr)**
+
+---
+
+
+ Thank you for visiting! For more information read the wiki!
+
+
+
+
+
+
diff --git a/data/constants.ts b/data/constants.ts
index 9c9793518..0e475f1c6 100644
--- a/data/constants.ts
+++ b/data/constants.ts
@@ -1,2 +1,2 @@
export const REPO_URL = 'ajnart/homarr';
-export const CURRENT_VERSION = 'v0.5.0';
+export const CURRENT_VERSION = 'v0.5.1';
diff --git a/package.json b/package.json
index 59d5c5b6d..203aa805d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "homarr",
- "version": "0.5.0",
+ "version": "0.5.1",
"private": "false",
"description": "Homarr - A homepage for your server.",
"repository": {
diff --git a/public/icons/.gitkeep b/public/icons/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx
index c061930e9..871e45f05 100644
--- a/src/components/AppShelf/AddAppShelfItem.tsx
+++ b/src/components/AppShelf/AddAppShelfItem.tsx
@@ -173,7 +173,10 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
{...form.getInputProps('type')}
/>
- {(form.values.type === 'Sonarr' || form.values.type === 'Radarr') && (
+ {(form.values.type === 'Sonarr' ||
+ form.values.type === 'Radarr' ||
+ form.values.type === 'Lidarr' ||
+ form.values.type === 'Readarr') && (
{
getConfigs().then((configs) => setConfigList(configs));
// setConfig(initialConfig);
- }, [config]);
+ }, [config.name]);
// If configlist is empty, return a loading indicator
if (configList.length === 0) {
return (
diff --git a/src/components/Config/SaveConfig.tsx b/src/components/Config/SaveConfig.tsx
index cfaf196d4..a0b7997e7 100644
--- a/src/components/Config/SaveConfig.tsx
+++ b/src/components/Config/SaveConfig.tsx
@@ -1,18 +1,96 @@
-import { Button } from '@mantine/core';
+import { Button, Group, Modal, TextInput } from '@mantine/core';
+import { useForm } from '@mantine/form';
+import { showNotification } from '@mantine/notifications';
+import axios from 'axios';
import fileDownload from 'js-file-download';
-import { Download } from 'tabler-icons-react';
+import { useState } from 'react';
+import { Check, Download, Plus, Trash, X } from 'tabler-icons-react';
import { useConfig } from '../../tools/state';
export default function SaveConfigComponent(props: any) {
- const { config } = useConfig();
+ const [opened, setOpened] = useState(false);
+ const { config, setConfig } = useConfig();
+ const form = useForm({
+ initialValues: {
+ configName: config.name,
+ },
+ });
function onClick(e: any) {
if (config) {
fileDownload(JSON.stringify(config, null, '\t'), `${config.name}.json`);
}
}
return (
- } variant="outline" onClick={onClick}>
- Download your config
-
+
+ setOpened(false)}
+ title="Choose the name of your new config"
+ >
+
+
+ } variant="outline" onClick={onClick}>
+ Download your config
+
+ }
+ variant="outline"
+ onClick={() => {
+ axios
+ .delete(`/api/configs/${config.name}`)
+ .then(() => {
+ showNotification({
+ title: 'Config deleted',
+ icon: ,
+ color: 'green',
+ autoClose: 1500,
+ radius: 'md',
+ message: 'Config deleted',
+ });
+ })
+ .catch(() => {
+ showNotification({
+ title: 'Config delete failed',
+ icon: ,
+ color: 'red',
+ autoClose: 1500,
+ radius: 'md',
+ message: 'Config delete failed',
+ });
+ });
+ setConfig({ ...config, name: 'default' });
+ }}
+ >
+ Delete current config
+
+ } variant="outline" onClick={() => setOpened(true)}>
+ Save a copy of your config
+
+
);
}
diff --git a/src/components/Settings/SettingsMenu.tsx b/src/components/Settings/SettingsMenu.tsx
index ed7c425c6..d0d57fd5a 100644
--- a/src/components/Settings/SettingsMenu.tsx
+++ b/src/components/Settings/SettingsMenu.tsx
@@ -1,14 +1,14 @@
import {
ActionIcon,
Group,
- Modal,
Title,
Text,
Tooltip,
SegmentedControl,
TextInput,
+ Drawer,
} from '@mantine/core';
-import { useColorScheme } from '@mantine/hooks';
+import { useColorScheme, useHotkeys } from '@mantine/hooks';
import { useState } from 'react';
import { Settings as SettingsIcon } from 'tabler-icons-react';
import { useConfig } from '../../tools/state';
@@ -97,18 +97,21 @@ function SettingsMenu(props: any) {
}
export function SettingsMenuButton(props: any) {
+ useHotkeys([['ctrl+L', () => setOpened(!opened)]]);
+
const [opened, setOpened] = useState(false);
return (
<>
- Settings}
opened={props.opened || opened}
onClose={() => setOpened(false)}
>
-
+
diff --git a/src/components/modules/calendar/CalendarModule.tsx b/src/components/modules/calendar/CalendarModule.tsx
index 17a40c3e6..39350e671 100644
--- a/src/components/modules/calendar/CalendarModule.tsx
+++ b/src/components/modules/calendar/CalendarModule.tsx
@@ -1,12 +1,17 @@
/* eslint-disable react/no-children-prop */
-import { Popover, Box, ScrollArea, Divider, Indicator, useMantineTheme } from '@mantine/core';
+import { Box, Divider, Indicator, Popover, ScrollArea, useMantineTheme } from '@mantine/core';
import React, { useEffect, useState } from 'react';
import { Calendar } from '@mantine/dates';
import { showNotification } from '@mantine/notifications';
import { Calendar as CalendarIcon, Check } from 'tabler-icons-react';
-import { RadarrMediaDisplay, SonarrMediaDisplay } from './MediaDisplay';
import { useConfig } from '../../../tools/state';
import { IModule } from '../modules';
+import {
+ SonarrMediaDisplay,
+ RadarrMediaDisplay,
+ LidarrMediaDisplay,
+ ReadarrMediaDisplay,
+} from './MediaDisplay';
export const CalendarModule: IModule = {
title: 'Calendar',
@@ -19,17 +24,25 @@ export const CalendarModule: IModule = {
export default function CalendarComponent(props: any) {
const { config } = useConfig();
const [sonarrMedias, setSonarrMedias] = useState([] as any);
+ const [lidarrMedias, setLidarrMedias] = useState([] as any);
const [radarrMedias, setRadarrMedias] = useState([] as any);
+ const [readarrMedias, setReadarrMedias] = useState([] as any);
useEffect(() => {
// Filter only sonarr and radarr services
const filtered = config.services.filter(
- (service) => service.type === 'Sonarr' || service.type === 'Radarr'
+ (service) =>
+ service.type === 'Sonarr' ||
+ service.type === 'Radarr' ||
+ service.type === 'Lidarr' ||
+ service.type === 'Readarr'
);
// Get the url and apiKey for all Sonarr and Radarr services
const sonarrService = filtered.filter((service) => service.type === 'Sonarr').at(0);
const radarrService = filtered.filter((service) => service.type === 'Radarr').at(0);
+ const lidarrService = filtered.filter((service) => service.type === 'Lidarr').at(0);
+ const readarrService = filtered.filter((service) => service.type === 'Readarr').at(0);
const nextMonth = new Date(new Date().setMonth(new Date().getMonth() + 2)).toISOString();
if (sonarrService && sonarrService.apiKey) {
const baseUrl = new URL(sonarrService.url).origin;
@@ -69,6 +82,44 @@ export default function CalendarComponent(props: any) {
}
);
}
+ if (lidarrService && lidarrService.apiKey) {
+ const baseUrl = new URL(lidarrService.url).origin;
+ fetch(`${baseUrl}/api/v1/calendar?apikey=${lidarrService?.apiKey}&end=${nextMonth}`).then(
+ (response) => {
+ response.ok &&
+ response.json().then((data) => {
+ setLidarrMedias(data);
+ showNotification({
+ title: 'Lidarr',
+ icon: ,
+ color: 'green',
+ autoClose: 1500,
+ radius: 'md',
+ message: `Loaded ${data.length} releases`,
+ });
+ });
+ }
+ );
+ }
+ if (readarrService && readarrService.apiKey) {
+ const baseUrl = new URL(readarrService.url).origin;
+ fetch(`${baseUrl}/api/v1/calendar?apikey=${readarrService?.apiKey}&end=${nextMonth}`).then(
+ (response) => {
+ response.ok &&
+ response.json().then((data) => {
+ setReadarrMedias(data);
+ showNotification({
+ title: 'Readarr',
+ icon: ,
+ color: 'green',
+ autoClose: 1500,
+ radius: 'md',
+ message: `Loaded ${data.length} releases`,
+ });
+ });
+ }
+ );
+ }
}, [config.services]);
if (sonarrMedias === undefined && radarrMedias === undefined) {
@@ -82,6 +133,8 @@ export default function CalendarComponent(props: any) {
renderdate={renderdate}
sonarrmedias={sonarrMedias}
radarrmedias={radarrMedias}
+ lidarrmedias={lidarrMedias}
+ readarrmedias={readarrMedias}
/>
)}
/>
@@ -93,12 +146,25 @@ function DayComponent(props: any) {
renderdate,
sonarrmedias,
radarrmedias,
- }: { renderdate: Date; sonarrmedias: []; radarrmedias: [] } = props;
+ lidarrmedias,
+ readarrmedias,
+ }: { renderdate: Date; sonarrmedias: []; radarrmedias: []; lidarrmedias: []; readarrmedias: [] } =
+ props;
const [opened, setOpened] = useState(false);
const theme = useMantineTheme();
const day = renderdate.getDate();
- // Itterate over the medias and filter the ones that are on the same day
+
+ const readarrFiltered = readarrmedias.filter((media: any) => {
+ const date = new Date(media.releaseDate);
+ return date.getDate() === day && date.getMonth() === renderdate.getMonth();
+ });
+
+ const lidarrFiltered = lidarrmedias.filter((media: any) => {
+ const date = new Date(media.releaseDate);
+ // Return true if the date is renerdate without counting hours and minutes
+ return date.getDate() === day && date.getMonth() === renderdate.getMonth();
+ });
const sonarrFiltered = sonarrmedias.filter((media: any) => {
const date = new Date(media.airDate);
// Return true if the date is renerdate without counting hours and minutes
@@ -109,7 +175,12 @@ function DayComponent(props: any) {
// Return true if the date is renerdate without counting hours and minutes
return date.getDate() === day && date.getMonth() === renderdate.getMonth();
});
- if (sonarrFiltered.length === 0 && radarrFiltered.length === 0) {
+ if (
+ sonarrFiltered.length === 0 &&
+ radarrFiltered.length === 0 &&
+ lidarrFiltered.length === 0 &&
+ readarrFiltered.length === 0
+ ) {
return {day}
;
}
@@ -119,8 +190,58 @@ function DayComponent(props: any) {
setOpened(true);
}}
>
- {radarrFiltered.length > 0 && }
- {sonarrFiltered.length > 0 && }
+ {readarrFiltered.length > 0 && (
+
+ )}
+ {radarrFiltered.length > 0 && (
+
+ )}
+ {sonarrFiltered.length > 0 && (
+
+ )}
+ {lidarrFiltered.length > 0 && (
+
+ )}
}
))}
+ {lidarrFiltered.map((media: any, index: number) => (
+
+
+ {index < lidarrFiltered.length - 1 && }
+
+ ))}
+ {readarrFiltered.map((media: any, index: number) => (
+
+
+ {index < readarrFiltered.length - 1 && }
+
+ ))}
diff --git a/src/components/modules/calendar/MediaDisplay.tsx b/src/components/modules/calendar/MediaDisplay.tsx
index ccf6745b3..e3a4a90b7 100644
--- a/src/components/modules/calendar/MediaDisplay.tsx
+++ b/src/components/modules/calendar/MediaDisplay.tsx
@@ -1,11 +1,14 @@
-import { Stack, Image, Group, Title, Badge, Text, ActionIcon, Anchor } from '@mantine/core';
+import { Image, Group, Title, Badge, Text, ActionIcon, Anchor, ScrollArea } from '@mantine/core';
import { Link } from 'tabler-icons-react';
+import { useConfig } from '../../../tools/state';
+import { serviceItem } from '../../../tools/types';
export interface IMedia {
overview: string;
- imdbId: any;
+ imdbId?: any;
+ artist?: string;
title: string;
- poster: string;
+ poster?: string;
genres: string[];
seasonNumber?: number;
episodeNumber?: number;
@@ -14,30 +17,44 @@ export interface IMedia {
function MediaDisplay(props: { media: IMedia }) {
const { media }: { media: IMedia } = props;
return (
-
-
- ({
- height: 400,
- })}
- >
-
-
- {media.title}
-
+
+
+ {media.poster && (
+
+ )}
+
+ {media.title}
+ {media.imdbId && (
+
-
+ )}
+ {media.artist && (
+
+ New release from {media.artist}
+
+ )}
{media.episodeNumber && media.seasonNumber && (
)}
-
- {media.overview}
-
- {/*Add the genres at the bottom of the poster*/}
-
- {media.genres.map((genre: string, i: number) => (
- {genre}
- ))}
+
+ {media.overview}
+
+ {media.genres.map((genre: string, i: number) => (
+
+ {genre}
+
+ ))}
+
-
+
);
}
+export function ReadarrMediaDisplay(props: any) {
+ const { media }: { media: any } = props;
+ const { config } = useConfig();
+ // Find lidarr in services
+ const readarr = config.services.find((service: serviceItem) => service.type === 'Readarr');
+ // Find a poster CoverType
+ const poster = media.images.find((image: any) => image.coverType === 'cover');
+ if (!readarr) {
+ return null;
+ }
+ const baseUrl = new URL(readarr.url).origin;
+ // Remove '/' from the end of the lidarr url
+ console.log(poster);
+ const fullLink = `${baseUrl}${poster.url}`;
+ // Return a movie poster containting the title and the description
+ return (
+
+ );
+}
+
+export function LidarrMediaDisplay(props: any) {
+ const { media }: { media: any } = props;
+ const { config } = useConfig();
+ // Find lidarr in services
+ const lidarr = config.services.find((service: serviceItem) => service.type === 'Lidarr');
+ // Find a poster CoverType
+ const poster = media.images.find((image: any) => image.coverType === 'cover');
+ if (!lidarr) {
+ return null;
+ }
+ const baseUrl = new URL(lidarr.url).origin;
+ // Remove '/' from the end of the lidarr url
+ const fullLink = `${baseUrl}${poster.url}`;
+ // Return a movie poster containting the title and the description
+ return (
+
+ );
+}
+
export function RadarrMediaDisplay(props: any) {
const { media }: { media: any } = props;
// Find a poster CoverType
diff --git a/src/components/modules/moduleWrapper.tsx b/src/components/modules/moduleWrapper.tsx
index dada02e2e..0afc0dc85 100644
--- a/src/components/modules/moduleWrapper.tsx
+++ b/src/components/modules/moduleWrapper.tsx
@@ -1,4 +1,4 @@
-import { Card, Menu, Switch, useMantineTheme } from '@mantine/core';
+import { Button, Card, Group, Menu, Switch, TextInput, useMantineTheme } from '@mantine/core';
import { useConfig } from '../../tools/state';
import { IModule } from './modules';
@@ -19,13 +19,51 @@ export function ModuleWrapper(props: any) {
types.forEach((type, index) => {
const optionName = `${module.title}.${keys[index]}`;
const moduleInConfig = config.modules?.[module.title];
+ if (type === 'string') {
+ items.push(
+
+ );
+ }
// TODO: Add support for other types
if (type === 'boolean') {
items.push(
{
@@ -59,7 +97,7 @@ export function ModuleWrapper(props: any) {
{module.options && (
{
- setLocation({ lat: position.coords.latitude, lng: position.coords.longitude });
- });
- }
+ (config?.modules?.[WeatherModule.title]?.options?.freedomunit?.value as boolean) ?? false;
useEffect(() => {
axios
- .get(
- `https://api.open-meteo.com/v1/forecast?latitude=${location.lat}&longitude=${location.lng}&daily=weathercode,temperature_2m_max,temperature_2m_min¤t_weather=true&timezone=Europe%2FLondon`
- )
- .then((res) => {
- setWeather(res.data);
+ .get(`https://geocoding-api.open-meteo.com/v1/search?name=${cityInput}`)
+ .then((response) => {
+ // Check if results exists
+ const { latitude, longitude } = response.data.results
+ ? response.data.results[0]
+ : { latitude: 0, longitude: 0 };
+ axios
+ .get(
+ `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=weathercode,temperature_2m_max,temperature_2m_min¤t_weather=true&timezone=Europe%2FLondon`
+ )
+ .then((res) => {
+ setWeather(res.data);
+ });
});
- }, []);
+ }, [cityInput]);
if (!weather.current_weather) {
return null;
}
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 610e21ad4..2a37b4008 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -38,7 +38,7 @@ export default function App(props: AppProps & { colorScheme: ColorScheme }) {
withGlobalStyles
withNormalizeCSS
>
-
+
diff --git a/src/pages/api/configs/[slug].ts b/src/pages/api/configs/[slug].ts
index dfa8ed8d0..6f71ca257 100644
--- a/src/pages/api/configs/[slug].ts
+++ b/src/pages/api/configs/[slug].ts
@@ -51,6 +51,9 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'PUT') {
return Put(req, res);
}
+ if (req.method === 'DELETE') {
+ return Delete(req, res);
+ }
if (req.method === 'GET') {
return Get(req, res);
}
@@ -59,3 +62,28 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
message: 'Method not allowed',
});
};
+
+function Delete(req: NextApiRequest, res: NextApiResponse) {
+ // Get the slug of the request
+ const { slug } = req.query as { slug: string };
+ if (!slug) {
+ return res.status(400).json({
+ message: 'Wrong request',
+ });
+ }
+ // Loop over all the files in the /data/configs directory
+ const files = fs.readdirSync('data/configs');
+ // Strip the .json extension from the file name
+ const configs = files.map((file) => file.replace('.json', ''));
+ // If the target is not in the list of files, return an error
+ if (!configs.includes(slug)) {
+ return res.status(404).json({
+ message: 'Target not found',
+ });
+ }
+ // Delete the file
+ fs.unlinkSync(path.join('data/configs', `${slug}.json`));
+ return res.status(200).json({
+ message: 'Configuration deleted with success',
+ });
+}
diff --git a/src/tools/types.ts b/src/tools/types.ts
index 92a56f8fc..71a0b3fea 100644
--- a/src/tools/types.ts
+++ b/src/tools/types.ts
@@ -27,6 +27,7 @@ export const ServiceTypeList = [
'Lidarr',
'Plex',
'Radarr',
+ 'Readarr',
'Sonarr',
'qBittorrent',
];
@@ -36,6 +37,7 @@ export type ServiceType =
| 'Lidarr'
| 'Plex'
| 'Radarr'
+ | 'Readarr'
| 'Sonarr'
| 'qBittorrent';