mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-11 07:55:52 +01:00
Merge branch 'dev' into ui/docker
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
MultiSelect,
|
||||
PasswordInput,
|
||||
Select,
|
||||
Space,
|
||||
Stack,
|
||||
Switch,
|
||||
Tabs,
|
||||
@@ -18,11 +19,11 @@ import {
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { IconApps } from '@tabler/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useConfig } from '../../tools/state';
|
||||
import { tryMatchPort, ServiceTypeList, StatusCodes, Config } from '../../tools/types';
|
||||
import Tip from '../layout/Tip';
|
||||
@@ -61,7 +62,7 @@ export function AddItemShelfButton(props: any) {
|
||||
function MatchIcon(name: string | undefined, form: any) {
|
||||
if (name === undefined || name === '') return null;
|
||||
fetch(
|
||||
`https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/${name
|
||||
`https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${name
|
||||
.replace(/\s+/g, '-')
|
||||
.toLowerCase()
|
||||
.replace(/^dash\.$/, 'dashdot')}.png`
|
||||
@@ -118,6 +119,7 @@ export function AddAppShelfItemForm(props: AddAppShelfItemFormProps) {
|
||||
username: props.username ?? undefined,
|
||||
password: props.password ?? undefined,
|
||||
openedUrl: props.openedUrl ?? undefined,
|
||||
ping: props.ping ?? true,
|
||||
status: props.status ?? ['200'],
|
||||
newTab: props.newTab ?? true,
|
||||
},
|
||||
@@ -186,7 +188,11 @@ export function AddAppShelfItemForm(props: AddAppShelfItemFormProps) {
|
||||
if (newForm.newTab === true) newForm.newTab = undefined;
|
||||
if (newForm.openedUrl === '') newForm.openedUrl = undefined;
|
||||
if (newForm.category === null) newForm.category = undefined;
|
||||
if (newForm.status.length === 1 && newForm.status[0] === '200') {
|
||||
if (newForm.ping === true) newForm.ping = undefined;
|
||||
if (
|
||||
(newForm.status.length === 1 && newForm.status[0] === '200') ||
|
||||
newForm.ping === false
|
||||
) {
|
||||
delete newForm.status;
|
||||
}
|
||||
// If service already exists, update it.
|
||||
@@ -220,6 +226,7 @@ export function AddAppShelfItemForm(props: AddAppShelfItemFormProps) {
|
||||
<Tabs.Tab value="Advanced Options">{t('modal.tabs.advancedOptions.title')}</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel value="Options">
|
||||
<Space h="sm" />
|
||||
<Stack>
|
||||
<TextInput
|
||||
required
|
||||
@@ -279,7 +286,8 @@ export function AddAppShelfItemForm(props: AddAppShelfItemFormProps) {
|
||||
form.values.type === 'Lidarr' ||
|
||||
form.values.type === 'Overseerr' ||
|
||||
form.values.type === 'Jellyseerr' ||
|
||||
form.values.type === 'Readarr') && (
|
||||
form.values.type === 'Readarr' ||
|
||||
form.values.type === 'Sabnzbd') && (
|
||||
<>
|
||||
<TextInput
|
||||
required
|
||||
@@ -405,20 +413,27 @@ export function AddAppShelfItemForm(props: AddAppShelfItemFormProps) {
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value="Advanced Options">
|
||||
<Stack>
|
||||
<MultiSelect
|
||||
required
|
||||
label={t('modal.tabs.advancedOptions.form.httpStatusCodes.label')}
|
||||
data={StatusCodes}
|
||||
placeholder={t('modal.tabs.advancedOptions.form.httpStatusCodes.placeholder')}
|
||||
clearButtonLabel={t(
|
||||
'modal.tabs.advancedOptions.form.httpStatusCodes.clearButtonLabel'
|
||||
)}
|
||||
nothingFound={t('modal.tabs.advancedOptions.form.httpStatusCodes.nothingFound')}
|
||||
defaultValue={['200']}
|
||||
clearable
|
||||
searchable
|
||||
{...form.getInputProps('status')}
|
||||
<Switch
|
||||
label={t('modal.tabs.advancedOptions.form.ping.label')}
|
||||
defaultChecked={form.values.ping}
|
||||
{...form.getInputProps('ping')}
|
||||
/>
|
||||
{form.values.ping && (
|
||||
<MultiSelect
|
||||
required
|
||||
label={t('modal.tabs.advancedOptions.form.httpStatusCodes.label')}
|
||||
data={StatusCodes}
|
||||
placeholder={t('modal.tabs.advancedOptions.form.httpStatusCodes.placeholder')}
|
||||
clearButtonLabel={t(
|
||||
'modal.tabs.advancedOptions.form.httpStatusCodes.clearButtonLabel'
|
||||
)}
|
||||
nothingFound={t('modal.tabs.advancedOptions.form.httpStatusCodes.nothingFound')}
|
||||
defaultValue={['200']}
|
||||
clearable
|
||||
searchable
|
||||
{...form.getInputProps('status')}
|
||||
/>
|
||||
)}
|
||||
<Switch
|
||||
label={t('modal.tabs.advancedOptions.form.openServiceInNewTab.label')}
|
||||
defaultChecked={form.values.newTab}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Accordion, Grid, Paper, Stack, useMantineColorScheme } from '@mantine/core';
|
||||
import {
|
||||
Accordion,
|
||||
Divider,
|
||||
Grid,
|
||||
Paper,
|
||||
Stack,
|
||||
Title,
|
||||
useMantineColorScheme,
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
@@ -16,8 +24,9 @@ import { useConfig } from '../../tools/state';
|
||||
|
||||
import { SortableAppShelfItem, AppShelfItem } from './AppShelfItem';
|
||||
import { ModuleMenu, ModuleWrapper } from '../../modules/moduleWrapper';
|
||||
import { DownloadsModule } from '../../modules';
|
||||
import DownloadComponent from '../../modules/downloads/DownloadsModule';
|
||||
import { UsenetModule, TorrentsModule } from '../../modules';
|
||||
import TorrentsComponent from '../../modules/torrents/TorrentsModule';
|
||||
import { UsenetComponent } from '../../modules/usenet/UsenetModule';
|
||||
|
||||
const AppShelf = (props: any) => {
|
||||
const { config, setConfig } = useConfig();
|
||||
@@ -126,7 +135,11 @@ const AppShelf = (props: any) => {
|
||||
const noCategory = config.services.filter(
|
||||
(e) => e.category === undefined || e.category === null
|
||||
);
|
||||
const downloadEnabled = config.modules?.[DownloadsModule.id]?.enabled ?? false;
|
||||
|
||||
const torrentEnabled = config.modules?.[TorrentsModule.id]?.enabled ?? false;
|
||||
const usenetEnabled = config.modules?.[UsenetModule.id]?.enabled ?? false;
|
||||
|
||||
const downloadEnabled = usenetEnabled || torrentEnabled;
|
||||
// Create an item with 0: true, 1: true, 2: true... For each category
|
||||
return (
|
||||
// TODO: Style accordion so that the bar is transparent to the user settings
|
||||
@@ -159,7 +172,6 @@ const AppShelf = (props: any) => {
|
||||
<Accordion.Control>{t('accordions.downloads.text')}</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<Paper
|
||||
p="lg"
|
||||
radius="lg"
|
||||
style={{
|
||||
background: `rgba(${colorScheme === 'dark' ? '37, 38, 43,' : '255, 255, 255,'} \
|
||||
@@ -170,8 +182,23 @@ const AppShelf = (props: any) => {
|
||||
${(config.settings.appOpacity || 100) / 100}`,
|
||||
}}
|
||||
>
|
||||
<ModuleMenu module={DownloadsModule} />
|
||||
<DownloadComponent />
|
||||
{torrentEnabled && (
|
||||
<>
|
||||
<Title size="h2">Torrents</Title>
|
||||
<ModuleMenu module={TorrentsModule} />
|
||||
<TorrentsComponent />
|
||||
</>
|
||||
)}
|
||||
{usenetEnabled && (
|
||||
<>
|
||||
{torrentEnabled && <Divider my="sm" />}
|
||||
<Title size="h2" mt={0}>
|
||||
Usenet
|
||||
</Title>
|
||||
<ModuleMenu module={UsenetModule} />
|
||||
<UsenetComponent />
|
||||
</>
|
||||
)}
|
||||
</Paper>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
@@ -183,7 +210,8 @@ const AppShelf = (props: any) => {
|
||||
return (
|
||||
<Stack>
|
||||
{getItems()}
|
||||
<ModuleWrapper mt="xl" module={DownloadsModule} />
|
||||
<ModuleWrapper mt="xl" module={TorrentsModule} />
|
||||
<ModuleWrapper mt="xl" module={UsenetModule} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import {
|
||||
Text,
|
||||
Card,
|
||||
Anchor,
|
||||
AspectRatio,
|
||||
Card,
|
||||
Center,
|
||||
createStyles,
|
||||
useMantineColorScheme,
|
||||
Image,
|
||||
Text,
|
||||
useMantineColorScheme,
|
||||
} from '@mantine/core';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useState } from 'react';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { serviceItem } from '../../tools/types';
|
||||
import PingComponent from '../../modules/ping/PingModule';
|
||||
import AppShelfMenu from './AppShelfMenu';
|
||||
import { useConfig } from '../../tools/state';
|
||||
import { serviceItem } from '../../tools/types';
|
||||
import AppShelfMenu from './AppShelfMenu';
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
item: {
|
||||
@@ -134,7 +134,7 @@ export function AppShelfItem(props: any) {
|
||||
</Anchor>
|
||||
</motion.i>
|
||||
</AspectRatio>
|
||||
<PingComponent url={service.url} status={service.status} />
|
||||
{service.ping !== false && <PingComponent url={service.url} status={service.status} />}
|
||||
</Card.Section>
|
||||
</Center>
|
||||
</Card>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Anchor, Avatar, Group, Text } from '@mantine/core';
|
||||
import { Avatar, Group, Text } from '@mantine/core';
|
||||
|
||||
interface smallServiceItem {
|
||||
label: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TextInput, Button, Stack } from '@mantine/core';
|
||||
import { TextInput, Button, Stack, Textarea } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useConfig } from '../../tools/state';
|
||||
@@ -17,6 +17,7 @@ export default function TitleChanger() {
|
||||
logo: config.settings.logo,
|
||||
favicon: config.settings.favicon,
|
||||
background: config.settings.background,
|
||||
customCSS: config.settings.customCSS,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -25,6 +26,7 @@ export default function TitleChanger() {
|
||||
logo?: string;
|
||||
favicon?: string;
|
||||
background?: string;
|
||||
customCSS?: string;
|
||||
}) => {
|
||||
setConfig({
|
||||
...config,
|
||||
@@ -34,6 +36,7 @@ export default function TitleChanger() {
|
||||
logo: values.logo,
|
||||
favicon: values.favicon,
|
||||
background: values.background,
|
||||
customCSS: values.customCSS,
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -62,6 +65,12 @@ export default function TitleChanger() {
|
||||
placeholder={t('background.placeholder')}
|
||||
{...form.getInputProps('background')}
|
||||
/>
|
||||
<Textarea
|
||||
minRows={5}
|
||||
label={t('customCSS.label')}
|
||||
placeholder={t('customCSS.placeholder')}
|
||||
{...form.getInputProps('customCSS')}
|
||||
/>
|
||||
<Button type="submit">{t('buttons.submit')}</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function LanguageSwitch() {
|
||||
const { t, i18n } = useTranslation('settings/general/internationalization');
|
||||
const { changeLanguage } = i18n;
|
||||
const configLocale = getCookie('config-locale');
|
||||
const { locale, locales } = useRouter();
|
||||
const { locale, locales, push } = useRouter();
|
||||
const [selectedLanguage, setSelectedLanguage] = useState<string>(
|
||||
(configLocale as string) ?? locale ?? 'en'
|
||||
);
|
||||
@@ -37,6 +37,8 @@ export default function LanguageSwitch() {
|
||||
sameSite: 'strict',
|
||||
});
|
||||
|
||||
push('/', '/', { locale: value });
|
||||
|
||||
showNotification({
|
||||
title: 'Language changed',
|
||||
message: `You changed the language to '${newLanguage.originalName}'`,
|
||||
@@ -65,6 +67,14 @@ export default function LanguageSwitch() {
|
||||
onChange={onChangeSelect}
|
||||
value={selectedLanguage}
|
||||
defaultValue={locale}
|
||||
searchable
|
||||
filter={(value, item) => {
|
||||
const selectItems = item as unknown as { value: string, language: Language };
|
||||
return (
|
||||
selectItems.language.originalName.trim().includes(value.trim()) ||
|
||||
selectItems.language.translatedName.trim().includes(value.trim())
|
||||
);
|
||||
}}
|
||||
styles={{
|
||||
icon: {
|
||||
width: 42,
|
||||
|
||||
@@ -34,6 +34,9 @@ export default function Layout({ children, style }: any) {
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
<style>
|
||||
{cx(config.settings.customCSS)}
|
||||
</style>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user