mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 15:35:55 +01:00
Merge branch 'gridstack' of https://github.com/manuel-rw/homarr into gridstack
This commit is contained in:
@@ -164,28 +164,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"widgets": [{
|
"widgets": [
|
||||||
"id": "date",
|
{
|
||||||
"properties": {
|
"id": "date",
|
||||||
"display24HourFormat": true
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "wrapper",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33e"
|
"display24HourFormat": true
|
||||||
}
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"location": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 3
|
|
||||||
},
|
},
|
||||||
"size": {
|
"area": {
|
||||||
"width": 4,
|
"type": "wrapper",
|
||||||
"height": 2
|
"properties": {
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33e"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"location": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 3
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 4,
|
||||||
|
"height": 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"common": {
|
"common": {
|
||||||
"searchEngine": {
|
"searchEngine": {
|
||||||
@@ -214,4 +216,4 @@
|
|||||||
"appOpacity": 100
|
"appOpacity": 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
const { env } = require('process');
|
|
||||||
|
|
||||||
const { i18n } = require('./next-i18next.config');
|
const { i18n } = require('./next-i18next.config');
|
||||||
|
|
||||||
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
const withBundleAnalyzer = require('@next/bundle-analyzer')({
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"close": "Close",
|
||||||
|
"delete": "Delete",
|
||||||
"ok": "Okay",
|
"ok": "Okay",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"changePosition": "Change position",
|
"changePosition": "Change position",
|
||||||
@@ -11,10 +13,15 @@
|
|||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"dangerZone": "Danger zone"
|
"dangerZone": "Danger zone"
|
||||||
},
|
},
|
||||||
|
"secrets": {
|
||||||
|
"apiKey": "Api key",
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password"
|
||||||
|
},
|
||||||
"tip": "Tip: ",
|
"tip": "Tip: ",
|
||||||
"time": {
|
"time": {
|
||||||
"seconds": "seconds",
|
"seconds": "seconds",
|
||||||
"minutes": "minutes",
|
"minutes": "minutes",
|
||||||
"hours": "hours"
|
"hours": "hours"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
{
|
|
||||||
"actionIcon": {
|
|
||||||
"tooltip": "Add a service"
|
|
||||||
},
|
|
||||||
"modal": {
|
|
||||||
"title": "Add service",
|
|
||||||
"form": {
|
|
||||||
"validation": {
|
|
||||||
"invalidUrl": "Please enter a valid URL",
|
|
||||||
"noStatusCodeSelected": "Please select a status code"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tabs": {
|
|
||||||
"options": {
|
|
||||||
"title": "Options",
|
|
||||||
"form": {
|
|
||||||
"serviceName": {
|
|
||||||
"label": "Service name",
|
|
||||||
"placeholder": "Plex"
|
|
||||||
},
|
|
||||||
"iconUrl": {
|
|
||||||
"label": "Icon URL"
|
|
||||||
},
|
|
||||||
"serviceUrl": {
|
|
||||||
"label": "Service URL"
|
|
||||||
},
|
|
||||||
"onClickUrl": {
|
|
||||||
"label": "On Click URL"
|
|
||||||
},
|
|
||||||
"serviceType": {
|
|
||||||
"label": "Service type",
|
|
||||||
"defaultValue": "Other",
|
|
||||||
"placeholder": "Pick one"
|
|
||||||
},
|
|
||||||
"category": {
|
|
||||||
"label": "Category",
|
|
||||||
"placeholder": "Select a category or create a new one",
|
|
||||||
"nothingFound": "Nothing found",
|
|
||||||
"createLabel": "+ Create {{query}}"
|
|
||||||
},
|
|
||||||
"integrations": {
|
|
||||||
"apiKey": {
|
|
||||||
"label": "API key",
|
|
||||||
"placeholder": "Your API key",
|
|
||||||
"validation": {
|
|
||||||
"noKey": "Invalid Key"
|
|
||||||
},
|
|
||||||
"tip": {
|
|
||||||
"text": "Get your API key",
|
|
||||||
"link": "here."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"qBittorrent": {
|
|
||||||
"username": {
|
|
||||||
"label": "Username",
|
|
||||||
"placeholder": "admin",
|
|
||||||
"validation": {
|
|
||||||
"invalidUsername": "Invalid username"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"password": {
|
|
||||||
"label": "Password",
|
|
||||||
"placeholder": "adminadmin",
|
|
||||||
"validation": {
|
|
||||||
"invalidPassword": "Invalid password"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"deluge": {
|
|
||||||
"password": {
|
|
||||||
"label": "Password",
|
|
||||||
"placeholder": "password",
|
|
||||||
"validation": {
|
|
||||||
"invalidPassword": "Invalid password"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"transmission": {
|
|
||||||
"username": {
|
|
||||||
"label": "Username",
|
|
||||||
"placeholder": "admin",
|
|
||||||
"validation": {
|
|
||||||
"invalidUsername": "Invalid username"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"password": {
|
|
||||||
"label": "Password",
|
|
||||||
"placeholder": "adminadmin",
|
|
||||||
"validation": {
|
|
||||||
"invalidPassword": "Invalid password"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nzbget": {
|
|
||||||
"username": {
|
|
||||||
"label": "Username",
|
|
||||||
"placeholder": "admin",
|
|
||||||
"validation": {
|
|
||||||
"invalidUsername": "Invalid username"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"password": {
|
|
||||||
"label": "Password",
|
|
||||||
"placeholder": "password",
|
|
||||||
"validation": {
|
|
||||||
"invalidPassword": "Invalid password"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"advancedOptions": {
|
|
||||||
"title": "Advanced options",
|
|
||||||
"form": {
|
|
||||||
"openServiceInNewTab": {
|
|
||||||
"label": "Open service in new tab"
|
|
||||||
},
|
|
||||||
"buttons": {
|
|
||||||
"submit": {
|
|
||||||
"content": "Add service"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"modal": {
|
|
||||||
"title": "Modify a service",
|
|
||||||
"buttons": {
|
|
||||||
"save": "Save service"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"menu": {
|
|
||||||
"labels": {
|
|
||||||
"settings": "Settings",
|
|
||||||
"dangerZone": "Danger zone"
|
|
||||||
},
|
|
||||||
"actions": {
|
|
||||||
"edit": "Edit",
|
|
||||||
"delete": "Delete"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"accordions": {
|
|
||||||
"downloads": {
|
|
||||||
"text": "Your downloads",
|
|
||||||
"torrents": "Your Torrent downloads",
|
|
||||||
"usenet": "Your Usenet downloads"
|
|
||||||
},
|
|
||||||
"others": {
|
|
||||||
"text": "Others"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"modal": {
|
"modal": {
|
||||||
"title": "Add a new tile to your Dashboard",
|
"title": "Add a new tile omegalaul",
|
||||||
"text": "You can use tiles, to make Homarr's dashboard yours. You may choose your desired tiles from the collection below and drag them anywhere on the dashboard where you like."
|
"text": "Tiles are the main element of homarr. They allow you to configure the dashboard and display the information you want."
|
||||||
},
|
},
|
||||||
"actionIcon": {
|
"actionIcon": {
|
||||||
"tooltip": "Add a tile"
|
"tooltip": "Add a tile"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
59
public/locales/en/layout/modals/add-app.json
Normal file
59
public/locales/en/layout/modals/add-app.json
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"tabs": {
|
||||||
|
"general": "General",
|
||||||
|
"behaviour": "Behaviour",
|
||||||
|
"network": "Network",
|
||||||
|
"appearance": "Appearance",
|
||||||
|
"integration": "Integration"
|
||||||
|
},
|
||||||
|
"general": {
|
||||||
|
"appname": {
|
||||||
|
"label": "App name",
|
||||||
|
"description": "Used for displaying the app on the dashboard"
|
||||||
|
},
|
||||||
|
"internalAddress": {
|
||||||
|
"label": "Internal address",
|
||||||
|
"description": "Internal IP of the app"
|
||||||
|
},
|
||||||
|
"externalAddress": {
|
||||||
|
"label": "External address",
|
||||||
|
"description": "Url that will be opened in the browser when clicking on the app"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"behaviour": {
|
||||||
|
"isOpeningNewTab": {
|
||||||
|
"label": "Open in new tab",
|
||||||
|
"description": "Open the link in a new tab"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"statusChecker": {
|
||||||
|
"label": "Status checker",
|
||||||
|
"description": "Sends a simple HTTP / HTTPS request to check if your app is online"
|
||||||
|
},
|
||||||
|
"statusCodes": {
|
||||||
|
"label": "HTTP status codes",
|
||||||
|
"description": "Determines what response codes are allowed for this app to be 'Online'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"icon": {
|
||||||
|
"label": "App Icon",
|
||||||
|
"description": "Logo of your app displayed in your dashboard. (Must return a body content containg an image)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"integration": {
|
||||||
|
"type": {
|
||||||
|
"label": "Integration configuration",
|
||||||
|
"description": "Treats this app as the selected integration and provides you with per-app configuration",
|
||||||
|
"placeholder": "Select an integration"
|
||||||
|
},
|
||||||
|
"secrets": {
|
||||||
|
"description": "To update a secret, enter a value and click the save button. To remove a secret, use the clear button.",
|
||||||
|
"warning": "Please note that Homarr removes secrets from the configuration for security reasons. Thus, you can only either define or unset any credentials. Your credentials act as the main access for your integrations and you should <strong>never</strong> share them with anybody else. Make sure to <strong>store and manage your secrets safely</strong>.",
|
||||||
|
"clear": "Clear secret",
|
||||||
|
"save": "Save secret",
|
||||||
|
"update": "Update secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,5 +11,19 @@
|
|||||||
"credits": {
|
"credits": {
|
||||||
"madeWithLove": "Made with ❤️ by @"
|
"madeWithLove": "Made with ❤️ by @"
|
||||||
},
|
},
|
||||||
"grow": "Grow grid (take all space)"
|
"grow": "Grow grid (take all space)",
|
||||||
|
"layout": {
|
||||||
|
"title": "Dashboard yayout",
|
||||||
|
"main": "Main",
|
||||||
|
"sidebar": "Sidebar",
|
||||||
|
"cannotturnoff": "Cannot be turned off",
|
||||||
|
"dashboardlayout": "Dashboard layout",
|
||||||
|
"enablersidebar": "Enable right sidebar",
|
||||||
|
"enablelsidebar": "Enable left sidebar",
|
||||||
|
"enablesearchbar": "Enable search bar",
|
||||||
|
"enabledocker": "Enable docker integration",
|
||||||
|
"enableping": "Enable pings",
|
||||||
|
"enablelsidebardescription": "Optional. Can be used for apps and integrations only",
|
||||||
|
"enablersidebardescription": "Optional. Can be used for apps and integrations only"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
|
Anchor,
|
||||||
Badge,
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
createStyles,
|
createStyles,
|
||||||
Group,
|
Group,
|
||||||
|
HoverCard,
|
||||||
Modal,
|
Modal,
|
||||||
Table,
|
Table,
|
||||||
Text,
|
Text,
|
||||||
@@ -17,6 +19,7 @@ import {
|
|||||||
IconVocabulary,
|
IconVocabulary,
|
||||||
IconWorldWww,
|
IconWorldWww,
|
||||||
} from '@tabler/icons';
|
} from '@tabler/icons';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
import { InitOptions } from 'i18next';
|
import { InitOptions } from 'i18next';
|
||||||
import { i18n } from 'next-i18next';
|
import { i18n } from 'next-i18next';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
@@ -27,12 +30,13 @@ import { usePrimaryGradient } from '../layout/useGradient';
|
|||||||
interface AboutModalProps {
|
interface AboutModalProps {
|
||||||
opened: boolean;
|
opened: boolean;
|
||||||
closeModal: () => void;
|
closeModal: () => void;
|
||||||
|
newVersionAvailable?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AboutModal = ({ opened, closeModal }: AboutModalProps) => {
|
export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutModalProps) => {
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
const colorGradiant = usePrimaryGradient();
|
const colorGradiant = usePrimaryGradient();
|
||||||
const informations = useInformationTableItems();
|
const informations = useInformationTableItems(newVersionAvailable);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -50,8 +54,8 @@ export const AboutModal = ({ opened, closeModal }: AboutModalProps) => {
|
|||||||
>
|
>
|
||||||
<Text mb="lg">
|
<Text mb="lg">
|
||||||
Homarr is a simple and modern homepage for your server that helps you access all of your
|
Homarr is a simple and modern homepage for your server that helps you access all of your
|
||||||
apps in one place. It integrates with the apps you use to display useful information
|
apps in one place. It integrates with the apps you use to display useful information or
|
||||||
or control them. It's easy to install and supports many different devices.
|
control them. It's easy to install and supports many different devices.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Title order={6} mb="xs" align="center">
|
<Title order={6} mb="xs" align="center">
|
||||||
@@ -123,7 +127,8 @@ interface ExtendedInitOptions extends InitOptions {
|
|||||||
locales: string[];
|
locales: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const useInformationTableItems = (): InformationTableItem[] => {
|
const useInformationTableItems = (newVersionAvailable?: string): InformationTableItem[] => {
|
||||||
|
// TODO: Fix this to not request. Pass it as a prop.
|
||||||
const colorGradiant = usePrimaryGradient();
|
const colorGradiant = usePrimaryGradient();
|
||||||
|
|
||||||
let items: InformationTableItem[] = [];
|
let items: InformationTableItem[] = [];
|
||||||
@@ -161,9 +166,41 @@ const useInformationTableItems = (): InformationTableItem[] => {
|
|||||||
icon: <IconVersions size={20} />,
|
icon: <IconVersions size={20} />,
|
||||||
label: 'Homarr version',
|
label: 'Homarr version',
|
||||||
content: (
|
content: (
|
||||||
<Badge variant="gradient" gradient={colorGradiant}>
|
<Group position="right">
|
||||||
{CURRENT_VERSION}
|
<Badge variant="gradient" gradient={colorGradiant}>
|
||||||
</Badge>
|
{CURRENT_VERSION}
|
||||||
|
</Badge>
|
||||||
|
{newVersionAvailable && (
|
||||||
|
<HoverCard shadow="md" position="top" withArrow>
|
||||||
|
<HoverCard.Target>
|
||||||
|
<motion.div
|
||||||
|
initial={{ scale: 1.2 }}
|
||||||
|
animate={{
|
||||||
|
scale: [0.8, 1.10, 1],
|
||||||
|
rotate: [0, 10, 0],
|
||||||
|
}}
|
||||||
|
transition={{ duration: 0.8, ease: 'easeInOut' }}
|
||||||
|
>
|
||||||
|
<Badge color="green" variant="filled">
|
||||||
|
new: {newVersionAvailable}
|
||||||
|
</Badge>
|
||||||
|
</motion.div>
|
||||||
|
</HoverCard.Target>
|
||||||
|
<HoverCard.Dropdown>
|
||||||
|
Version{' '}
|
||||||
|
<b>
|
||||||
|
<Anchor
|
||||||
|
target="_blank"
|
||||||
|
href={`https://github.com/ajnart/homarr/releases/tag/${newVersionAvailable}`}
|
||||||
|
>
|
||||||
|
{newVersionAvailable}
|
||||||
|
</Anchor>
|
||||||
|
</b>{' '}
|
||||||
|
is available ! Current version: {CURRENT_VERSION}
|
||||||
|
</HoverCard.Dropdown>
|
||||||
|
</HoverCard>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
|
|
||||||
import { arrayMove, SortableContext } from '@dnd-kit/sortable';
|
import { arrayMove, SortableContext } from '@dnd-kit/sortable';
|
||||||
import { useLocalStorage } from '@mantine/hooks';
|
import { useLocalStorage } from '@mantine/hooks';
|
||||||
import { useTranslation } from 'next-i18next';
|
|
||||||
import * as Modules from '../../modules';
|
import * as Modules from '../../modules';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
|
|
||||||
@@ -38,8 +37,6 @@ const AppShelf = (props: any) => {
|
|||||||
const [activeId, setActiveId] = useState(null);
|
const [activeId, setActiveId] = useState(null);
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
|
||||||
const { t } = useTranslation('layout/app-shelf');
|
|
||||||
|
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
useSensor(TouchSensor, {
|
useSensor(TouchSensor, {
|
||||||
activationConstraint: {
|
activationConstraint: {
|
||||||
|
|||||||
@@ -47,11 +47,9 @@ export default function ConfigChanger() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const useConfigsQuery = () => {
|
const useConfigsQuery = () => useQuery({
|
||||||
return useQuery({
|
|
||||||
queryKey: ['config/get-all'],
|
queryKey: ['config/get-all'],
|
||||||
queryFn: fetchConfigs,
|
queryFn: fetchConfigs,
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const fetchConfigs = async () => (await (await fetch('/api/configs')).json()) as string[];
|
const fetchConfigs = async () => (await (await fetch('/api/configs')).json()) as string[];
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { DashboardDetailView } from './Views/DetailView';
|
|||||||
import { DashboardEditView } from './Views/EditView';
|
import { DashboardEditView } from './Views/EditView';
|
||||||
import { useEditModeStore } from './Views/useEditModeStore';
|
import { useEditModeStore } from './Views/useEditModeStore';
|
||||||
|
|
||||||
interface DashboardProps {}
|
|
||||||
|
|
||||||
export const Dashboard = () => {
|
export const Dashboard = () => {
|
||||||
const isEditMode = useEditModeStore((x) => x.enabled);
|
const isEditMode = useEditModeStore((x) => x.enabled);
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ interface MobileRibbonSidebarDrawerProps {
|
|||||||
export const MobileRibbonSidebarDrawer = ({
|
export const MobileRibbonSidebarDrawer = ({
|
||||||
location,
|
location,
|
||||||
...props
|
...props
|
||||||
}: MobileRibbonSidebarDrawerProps) => {
|
}: MobileRibbonSidebarDrawerProps) => (
|
||||||
return (
|
|
||||||
<Drawer
|
<Drawer
|
||||||
position={location}
|
position={location}
|
||||||
title={<Title order={4}>{location} sidebar</Title>}
|
title={<Title order={4}>{location} sidebar</Title>}
|
||||||
@@ -24,4 +23,3 @@ export const MobileRibbonSidebarDrawer = ({
|
|||||||
<DashboardSidebar location={location} />
|
<DashboardSidebar location={location} />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Button, Flex, Grid, NumberInput, Select, SelectItem } from '@mantine/core';
|
import { Button, Flex, Grid, NumberInput, Select, SelectItem } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfigContext } from '../../../../config/provider';
|
import { useConfigContext } from '../../../../config/provider';
|
||||||
|
|
||||||
interface ChangePositionModalProps {
|
interface ChangePositionModalProps {
|
||||||
@@ -44,6 +45,8 @@ export const ChangePositionModal = ({
|
|||||||
onSubmit(form.values.x, form.values.y, form.values.width, form.values.height);
|
onSubmit(form.values.x, form.values.y, form.values.width, form.values.height);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
<Grid>
|
<Grid>
|
||||||
@@ -94,9 +97,9 @@ export const ChangePositionModal = ({
|
|||||||
|
|
||||||
<Flex justify="end" gap="sm" mt="md">
|
<Flex justify="end" gap="sm" mt="md">
|
||||||
<Button onClick={() => onCancel()} variant="light" color="gray">
|
<Button onClick={() => onCancel()} variant="light" color="gray">
|
||||||
Cancel
|
{t('cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit">Save</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const ChangeWidgetPositionModal = ({
|
|||||||
updateConfig(
|
updateConfig(
|
||||||
configName,
|
configName,
|
||||||
(prev) => {
|
(prev) => {
|
||||||
let currentWidget = prev.widgets.find((x) => x.id === innerProps.widgetId);
|
const currentWidget = prev.widgets.find((x) => x.id === innerProps.widgetId);
|
||||||
currentWidget!.shape = {
|
currentWidget!.shape = {
|
||||||
location: {
|
location: {
|
||||||
x,
|
x,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Alert, Button, createStyles, Group, Stack, Tabs, Text, ThemeIcon } from '@mantine/core';
|
import { Alert, Button, Group, Stack, Tabs, Text, ThemeIcon } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { ContextModalProps } from '@mantine/modals';
|
import { ContextModalProps } from '@mantine/modals';
|
||||||
import {
|
import {
|
||||||
@@ -30,7 +30,7 @@ export const EditAppModal = ({
|
|||||||
id,
|
id,
|
||||||
innerProps,
|
innerProps,
|
||||||
}: ContextModalProps<{ app: AppType; allowAppNamePropagation: boolean }>) => {
|
}: ContextModalProps<{ app: AppType; allowAppNamePropagation: boolean }>) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation(['layout/modals/add-app', 'common']);
|
||||||
const { name: configName, config } = useConfigContext();
|
const { name: configName, config } = useConfigContext();
|
||||||
const updateConfig = useConfigStore((store) => store.updateConfig);
|
const updateConfig = useConfigStore((store) => store.updateConfig);
|
||||||
const [allowAppNamePropagation, setAllowAppNamePropagation] = useState<boolean>(
|
const [allowAppNamePropagation, setAllowAppNamePropagation] = useState<boolean>(
|
||||||
@@ -137,68 +137,76 @@ export const EditAppModal = ({
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||||
<Tabs
|
<Stack
|
||||||
value={activeTab}
|
justify="space-between"
|
||||||
onTabChange={(tab) => setActiveTab(tab as EditAppModalTab)}
|
style={{
|
||||||
defaultValue="general"
|
minHeight: 300,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Tabs.List grow>
|
<Tabs
|
||||||
<Tabs.Tab
|
value={activeTab}
|
||||||
rightSection={<ValidationErrorIndicator keys={['name', 'url']} />}
|
onTabChange={(tab) => setActiveTab(tab as EditAppModalTab)}
|
||||||
icon={<IconAdjustments size={14} />}
|
defaultValue="general"
|
||||||
value="general"
|
radius="md"
|
||||||
>
|
>
|
||||||
General
|
<Tabs.List grow>
|
||||||
</Tabs.Tab>
|
<Tabs.Tab
|
||||||
<Tabs.Tab
|
rightSection={<ValidationErrorIndicator keys={['name', 'url']} />}
|
||||||
rightSection={<ValidationErrorIndicator keys={['behaviour.onClickUrl']} />}
|
icon={<IconAdjustments size={14} />}
|
||||||
icon={<IconClick size={14} />}
|
value="general"
|
||||||
value="behaviour"
|
>
|
||||||
>
|
{t('tabs.general')}
|
||||||
Behaviour
|
</Tabs.Tab>
|
||||||
</Tabs.Tab>
|
<Tabs.Tab
|
||||||
<Tabs.Tab
|
rightSection={<ValidationErrorIndicator keys={['behaviour.onClickUrl']} />}
|
||||||
rightSection={<ValidationErrorIndicator keys={[]} />}
|
icon={<IconClick size={14} />}
|
||||||
icon={<IconAccessPoint size={14} />}
|
value="behaviour"
|
||||||
value="network"
|
>
|
||||||
>
|
{t('tabs.behaviour')}
|
||||||
Network
|
</Tabs.Tab>
|
||||||
</Tabs.Tab>
|
<Tabs.Tab
|
||||||
<Tabs.Tab
|
rightSection={<ValidationErrorIndicator keys={[]} />}
|
||||||
rightSection={<ValidationErrorIndicator keys={['appearance.iconUrl']} />}
|
icon={<IconAccessPoint size={14} />}
|
||||||
icon={<IconBrush size={14} />}
|
value="network"
|
||||||
value="appearance"
|
>
|
||||||
>
|
{t('tabs.network')}
|
||||||
Appearance
|
</Tabs.Tab>
|
||||||
</Tabs.Tab>
|
<Tabs.Tab
|
||||||
<Tabs.Tab
|
rightSection={<ValidationErrorIndicator keys={['appearance.iconUrl']} />}
|
||||||
rightSection={<ValidationErrorIndicator keys={[]} />}
|
icon={<IconBrush size={14} />}
|
||||||
icon={<IconPlug size={14} />}
|
value="appearance"
|
||||||
value="integration"
|
>
|
||||||
>
|
{t('tabs.appearance')}
|
||||||
Integration
|
</Tabs.Tab>
|
||||||
</Tabs.Tab>
|
<Tabs.Tab
|
||||||
</Tabs.List>
|
rightSection={<ValidationErrorIndicator keys={[]} />}
|
||||||
|
icon={<IconPlug size={14} />}
|
||||||
|
value="integration"
|
||||||
|
>
|
||||||
|
{t('tabs.integration')}
|
||||||
|
</Tabs.Tab>
|
||||||
|
</Tabs.List>
|
||||||
|
|
||||||
<GeneralTab form={form} openTab={(targetTab) => setActiveTab(targetTab)} />
|
<GeneralTab form={form} openTab={(targetTab) => setActiveTab(targetTab)} />
|
||||||
<BehaviourTab form={form} />
|
<BehaviourTab form={form} />
|
||||||
<NetworkTab form={form} />
|
<NetworkTab form={form} />
|
||||||
<AppearanceTab
|
<AppearanceTab
|
||||||
form={form}
|
form={form}
|
||||||
disallowAppNameProgagation={() => setAllowAppNamePropagation(false)}
|
disallowAppNameProgagation={() => setAllowAppNamePropagation(false)}
|
||||||
allowAppNamePropagation={allowAppNamePropagation}
|
allowAppNamePropagation={allowAppNamePropagation}
|
||||||
/>
|
/>
|
||||||
<IntegrationTab form={form} />
|
<IntegrationTab form={form} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<Group position="right" mt={100}>
|
<Group position="right" mt="md">
|
||||||
<Button onClick={closeModal} px={50} variant="light" color="gray">
|
<Button onClick={closeModal} px={50} variant="light" color="gray">
|
||||||
Cancel
|
{t('common:actions.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button disabled={!form.isValid()} px={50} type="submit">
|
<Button disabled={!form.isValid()} px={50} type="submit">
|
||||||
Save
|
{t('common:actions.save')}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const AppearanceTab = ({
|
|||||||
disallowAppNameProgagation,
|
disallowAppNameProgagation,
|
||||||
allowAppNamePropagation,
|
allowAppNamePropagation,
|
||||||
}: AppearanceTabProps) => {
|
}: AppearanceTabProps) => {
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation('layout/modals/add-app');
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -26,8 +26,8 @@ export const AppearanceTab = ({
|
|||||||
defaultValue={form.values.appearance.iconUrl}
|
defaultValue={form.values.appearance.iconUrl}
|
||||||
className={classes.textInput}
|
className={classes.textInput}
|
||||||
icon={<DebouncedAppIcon form={form} width={20} height={20} />}
|
icon={<DebouncedAppIcon form={form} width={20} height={20} />}
|
||||||
label="App Icon"
|
label={t('appearance.icon.label')}
|
||||||
description="Logo of your app displayed in your dashboard. Must return a body content containg an image"
|
description={t('appearance.icon.description')}
|
||||||
variant="default"
|
variant="default"
|
||||||
withAsterisk
|
withAsterisk
|
||||||
required
|
required
|
||||||
|
|||||||
@@ -29,11 +29,7 @@ interface IconSelectorProps {
|
|||||||
allowAppNamePropagation: boolean;
|
allowAppNamePropagation: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IconSelector = ({
|
export const IconSelector = ({ onChange, allowAppNamePropagation, form }: IconSelectorProps) => {
|
||||||
onChange,
|
|
||||||
allowAppNamePropagation,
|
|
||||||
form,
|
|
||||||
}: IconSelectorProps) => {
|
|
||||||
const { data, isLoading } = useRepositoryIconsQuery<WalkxcodeRepositoryIcon>({
|
const { data, isLoading } = useRepositoryIconsQuery<WalkxcodeRepositoryIcon>({
|
||||||
url: 'https://api.github.com/repos/walkxcode/Dashboard-Icons/contents/png',
|
url: 'https://api.github.com/repos/walkxcode/Dashboard-Icons/contents/png',
|
||||||
converter: (item) => ({
|
converter: (item) => ({
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Tabs, TextInput, Switch, Text } from '@mantine/core';
|
import { Tabs, Switch } from '@mantine/core';
|
||||||
import { UseFormReturnType } from '@mantine/form';
|
import { UseFormReturnType } from '@mantine/form';
|
||||||
import { IconClick } from '@tabler/icons';
|
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { AppType } from '../../../../../../types/app';
|
import { AppType } from '../../../../../../types/app';
|
||||||
|
|
||||||
@@ -9,21 +8,13 @@ interface BehaviourTabProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const BehaviourTab = ({ form }: BehaviourTabProps) => {
|
export const BehaviourTab = ({ form }: BehaviourTabProps) => {
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation('layout/modals/add-app');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs.Panel value="behaviour" pt="xs">
|
<Tabs.Panel value="behaviour" pt="xs">
|
||||||
<TextInput
|
|
||||||
icon={<IconClick size={16} />}
|
|
||||||
label="On click url"
|
|
||||||
description="Overrides the app URL when clicking on the app"
|
|
||||||
placeholder="URL that should be opened instead when clicking on the app"
|
|
||||||
variant="default"
|
|
||||||
mb="md"
|
|
||||||
{...form.getInputProps('behaviour.onClickUrl')}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
label="Open in new tab"
|
label={t('behaviour.isOpeningNewTab.label')}
|
||||||
|
description={t('behaviour.isOpeningNewTab.description')}
|
||||||
{...form.getInputProps('behaviour.isOpeningNewTab', { type: 'checkbox' })}
|
{...form.getInputProps('behaviour.isOpeningNewTab', { type: 'checkbox' })}
|
||||||
/>
|
/>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Tabs, Text, TextInput } from '@mantine/core';
|
import { Tabs, TextInput } from '@mantine/core';
|
||||||
import { UseFormReturnType } from '@mantine/form';
|
import { UseFormReturnType } from '@mantine/form';
|
||||||
import { IconCursorText, IconLink } from '@tabler/icons';
|
import { IconClick, IconCursorText, IconLink } from '@tabler/icons';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { AppType } from '../../../../../../types/app';
|
import { AppType } from '../../../../../../types/app';
|
||||||
import { EditAppModalTab } from '../type';
|
import { EditAppModalTab } from '../type';
|
||||||
@@ -11,43 +11,42 @@ interface GeneralTabProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const GeneralTab = ({ form, openTab }: GeneralTabProps) => {
|
export const GeneralTab = ({ form, openTab }: GeneralTabProps) => {
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation('layout/modals/add-app');
|
||||||
return (
|
return (
|
||||||
<Tabs.Panel value="general" pt="lg">
|
<Tabs.Panel value="general" pt="sm">
|
||||||
<TextInput
|
<TextInput
|
||||||
icon={<IconCursorText size={16} />}
|
icon={<IconCursorText size={16} />}
|
||||||
label="App name"
|
label={t('general.appname.label')}
|
||||||
description="Used for displaying the app on the dashboard"
|
description={t('general.appname.description')}
|
||||||
placeholder="My example app"
|
placeholder="My example app"
|
||||||
variant="default"
|
variant="default"
|
||||||
mb="md"
|
mb="md"
|
||||||
withAsterisk
|
withAsterisk
|
||||||
|
required
|
||||||
{...form.getInputProps('name')}
|
{...form.getInputProps('name')}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
icon={<IconLink size={16} />}
|
icon={<IconLink size={16} />}
|
||||||
label="App url"
|
label={t('general.internalAddress.label')}
|
||||||
description={
|
description={t('general.internalAddress.description')}
|
||||||
<Text>
|
|
||||||
URL that will be opened when clicking on the app. Can be overwritten using
|
|
||||||
<Text
|
|
||||||
onClick={() => openTab('behaviour')}
|
|
||||||
variant="link"
|
|
||||||
span
|
|
||||||
style={{
|
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{' '}
|
|
||||||
on click URL{' '}
|
|
||||||
</Text>
|
|
||||||
when using external URLs to enhance security.
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
placeholder="https://google.com"
|
placeholder="https://google.com"
|
||||||
variant="default"
|
variant="default"
|
||||||
withAsterisk
|
withAsterisk
|
||||||
|
required
|
||||||
{...form.getInputProps('url')}
|
{...form.getInputProps('url')}
|
||||||
|
onChange={(e) => {
|
||||||
|
form.setFieldValue('behaviour.onClickUrl', e.target.value);
|
||||||
|
form.setFieldValue('url', e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
icon={<IconClick size={16} />}
|
||||||
|
label={t('general.externalAddress.label')}
|
||||||
|
description={t('general.externalAddress.description')}
|
||||||
|
placeholder="https://homarr.mywebsite.com/"
|
||||||
|
variant="default"
|
||||||
|
mb="md"
|
||||||
|
{...form.getInputProps('behaviour.onClickUrl')}
|
||||||
/>
|
/>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { TablerIcon } from '@tabler/icons';
|
import { TablerIcon } from '@tabler/icons';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
interface GenericSecretInputProps {
|
interface GenericSecretInputProps {
|
||||||
@@ -32,6 +33,7 @@ export const GenericSecretInput = ({
|
|||||||
const Icon = setIcon;
|
const Icon = setIcon;
|
||||||
|
|
||||||
const [displayUpdateField, setDisplayUpdateField] = useState<boolean>(false);
|
const [displayUpdateField, setDisplayUpdateField] = useState<boolean>(false);
|
||||||
|
const { t } = useTranslation(['layout/modals/add-app', 'common']);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card withBorder>
|
<Card withBorder>
|
||||||
@@ -43,7 +45,7 @@ export const GenericSecretInput = ({
|
|||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
<Stack spacing={0}>
|
<Stack spacing={0}>
|
||||||
<Title className={classes.subtitle} order={6}>
|
<Title className={classes.subtitle} order={6}>
|
||||||
{label}
|
{t(label)}
|
||||||
</Title>
|
</Title>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -51,13 +53,13 @@ export const GenericSecretInput = ({
|
|||||||
<Grid.Col xs={12} md={6}>
|
<Grid.Col xs={12} md={6}>
|
||||||
<Flex gap={10} justify="end" align="end">
|
<Flex gap={10} justify="end" align="end">
|
||||||
<Button variant="subtle" color="gray" px="xl">
|
<Button variant="subtle" color="gray" px="xl">
|
||||||
Clear Secret
|
{t('integration.secrets.clear')}
|
||||||
</Button>
|
</Button>
|
||||||
{displayUpdateField === true ? (
|
{displayUpdateField === true ? (
|
||||||
<PasswordInput placeholder="new secret" width={200} {...props} />
|
<PasswordInput placeholder="new secret" width={200} {...props} />
|
||||||
) : (
|
) : (
|
||||||
<Button onClick={() => setDisplayUpdateField(true)} variant="light">
|
<Button onClick={() => setDisplayUpdateField(true)} variant="light">
|
||||||
Set Secret
|
{t('integration.secrets.update')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ interface IntegrationSelectorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const IntegrationSelector = ({ form }: IntegrationSelectorProps) => {
|
export const IntegrationSelector = ({ form }: IntegrationSelectorProps) => {
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation('layout/modals/add-app');
|
||||||
|
|
||||||
// TODO: read this out from integrations dynamically.
|
// TODO: read this out from integrations dynamically.
|
||||||
const data: SelectItem[] = [
|
const data: SelectItem[] = [
|
||||||
@@ -76,9 +76,9 @@ export const IntegrationSelector = ({ form }: IntegrationSelectorProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
label="Integration configuration"
|
label={t('integration.type.label')}
|
||||||
description="Treats this app as the selected integration and provides you with per-app configuration"
|
description={t('integration.type.description')}
|
||||||
placeholder="Select your desired configuration"
|
placeholder={t('integration.type.placeholder')}
|
||||||
itemComponent={SelectItemComponent}
|
itemComponent={SelectItemComponent}
|
||||||
data={data}
|
data={data}
|
||||||
maxDropdownHeight={400}
|
maxDropdownHeight={400}
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ export const IntegrationOptionsRenderer = ({ form }: IntegrationOptionsRendererP
|
|||||||
let indexInFormValue =
|
let indexInFormValue =
|
||||||
form.values.integration?.properties.findIndex((p) => p.field === property) ?? -1;
|
form.values.integration?.properties.findIndex((p) => p.field === property) ?? -1;
|
||||||
if (indexInFormValue === -1) {
|
if (indexInFormValue === -1) {
|
||||||
const type = Object.entries(integrationFieldDefinitions).find(
|
const { type } = Object.entries(integrationFieldDefinitions).find(
|
||||||
([k, v]) => k === property
|
([k, v]) => k === property
|
||||||
)![1].type;
|
)![1];
|
||||||
const newProperty: AppIntegrationPropertyType = {
|
const newProperty: AppIntegrationPropertyType = {
|
||||||
type,
|
type,
|
||||||
field: property as IntegrationField,
|
field: property as IntegrationField,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Alert, Divider, Tabs, Text } from '@mantine/core';
|
import { Alert, Divider, Tabs, Text } from '@mantine/core';
|
||||||
import { UseFormReturnType } from '@mantine/form';
|
import { UseFormReturnType } from '@mantine/form';
|
||||||
import { IconAlertTriangle } from '@tabler/icons';
|
import { IconAlertTriangle } from '@tabler/icons';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { Trans, useTranslation } from 'next-i18next';
|
||||||
import { AppType } from '../../../../../../types/app';
|
import { AppType } from '../../../../../../types/app';
|
||||||
import { IntegrationSelector } from './Components/InputElements/IntegrationSelector';
|
import { IntegrationSelector } from './Components/InputElements/IntegrationSelector';
|
||||||
import { IntegrationOptionsRenderer } from './Components/IntegrationOptionsRenderer/IntegrationOptionsRenderer';
|
import { IntegrationOptionsRenderer } from './Components/IntegrationOptionsRenderer/IntegrationOptionsRenderer';
|
||||||
@@ -11,7 +11,7 @@ interface IntegrationTabProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const IntegrationTab = ({ form }: IntegrationTabProps) => {
|
export const IntegrationTab = ({ form }: IntegrationTabProps) => {
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation('layout/modals/add-app');
|
||||||
const hasIntegrationSelected = form.values.integration?.type;
|
const hasIntegrationSelected = form.values.integration?.type;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -20,18 +20,14 @@ export const IntegrationTab = ({ form }: IntegrationTabProps) => {
|
|||||||
|
|
||||||
{hasIntegrationSelected && (
|
{hasIntegrationSelected && (
|
||||||
<>
|
<>
|
||||||
<Divider label="Integration Configuration" labelPosition="center" mt="xl" mb="md" />
|
<Divider label={t('integration.type.label')} labelPosition="center" mt="xl" mb="md" />
|
||||||
<Text size="sm" color="dimmed" mb="lg">
|
<Text size="sm" color="dimmed" mb="lg">
|
||||||
To update a secret, enter a value and click the save button. To remove a secret, use the
|
{t('integration.secrets.description')}
|
||||||
clear button.
|
|
||||||
</Text>
|
</Text>
|
||||||
<IntegrationOptionsRenderer form={form} />
|
<IntegrationOptionsRenderer form={form} />
|
||||||
<Alert icon={<IconAlertTriangle />} color="yellow">
|
<Alert icon={<IconAlertTriangle />} color="yellow">
|
||||||
<Text>
|
<Text>
|
||||||
Please note that Homarr removes secrets from the configuration for security reasons.
|
<Trans i18nKey="integration.secrets.warning" />
|
||||||
Thus, you can only either define or unset any credentials. Your credentials act as the
|
|
||||||
main access for your integrations and you should <b>never</b> share them with anybody
|
|
||||||
else. Make sure to <b>store and manage your secrets safely</b>.
|
|
||||||
</Text>
|
</Text>
|
||||||
</Alert>
|
</Alert>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ interface NetworkTabProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const NetworkTab = ({ form }: NetworkTabProps) => {
|
export const NetworkTab = ({ form }: NetworkTabProps) => {
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation('layout/modals/add-app');
|
||||||
return (
|
return (
|
||||||
<Tabs.Panel value="network" pt="lg">
|
<Tabs.Panel value="network" pt="lg">
|
||||||
<Switch
|
<Switch
|
||||||
label="Enable status checker"
|
label={t('network.statusChecker.label')}
|
||||||
description="Sends a simple HTTP / HTTPS request to check if your app is online"
|
description={t('network.statusChecker.description')}
|
||||||
mb="md"
|
mb="md"
|
||||||
defaultChecked={form.values.network.enabledStatusChecker}
|
defaultChecked={form.values.network.enabledStatusChecker}
|
||||||
{...form.getInputProps('network.enabledStatusChecker')}
|
{...form.getInputProps('network.enabledStatusChecker')}
|
||||||
@@ -22,8 +22,8 @@ export const NetworkTab = ({ form }: NetworkTabProps) => {
|
|||||||
{form.values.network.enabledStatusChecker && (
|
{form.values.network.enabledStatusChecker && (
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
required
|
required
|
||||||
label="HTTP status codes"
|
label={t('network.statusCodes.label')}
|
||||||
description="Determines what response codes are allowed for this app to be 'Online'"
|
description={t('network.statusCodes.description')}
|
||||||
data={StatusCodes}
|
data={StatusCodes}
|
||||||
clearable
|
clearable
|
||||||
searchable
|
searchable
|
||||||
|
|||||||
@@ -1,6 +1 @@
|
|||||||
export type EditAppModalTab =
|
export type EditAppModalTab = 'general' | 'behaviour' | 'network' | 'appereance' | 'integration';
|
||||||
| 'general'
|
|
||||||
| 'behaviour'
|
|
||||||
| 'network'
|
|
||||||
| 'appereance'
|
|
||||||
| 'integration';
|
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ export const WidgetElementType = ({ id, image, disabled, widget }: WidgetElement
|
|||||||
|
|
||||||
if (!configName) return null;
|
if (!configName) return null;
|
||||||
|
|
||||||
const getLowestWrapper = () => {
|
const getLowestWrapper = () => config?.wrappers.sort((a, b) => a.position - b.position)[0];
|
||||||
return config?.wrappers.sort((a, b) => a.position - b.position)[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddition = async () => {
|
const handleAddition = async () => {
|
||||||
updateConfig(
|
updateConfig(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, Center, Text, UnstyledButton } from '@mantine/core';
|
import { Center, Text, UnstyledButton } from '@mantine/core';
|
||||||
import { NextLink } from '@mantine/next';
|
import { NextLink } from '@mantine/next';
|
||||||
import { createStyles } from '@mantine/styles';
|
import { createStyles } from '@mantine/styles';
|
||||||
import { AppType } from '../../../../types/app';
|
import { AppType } from '../../../../types/app';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { HomarrCardWrapper } from './HomarrCardWrapper';
|
import { HomarrCardWrapper } from './HomarrCardWrapper';
|
||||||
import { BaseTileProps } from './type';
|
import { BaseTileProps } from './type';
|
||||||
|
|
||||||
export const EmptyTile = ({ className }: BaseTileProps) => {
|
export const EmptyTile = ({ className }: BaseTileProps) => (
|
||||||
return <HomarrCardWrapper className={className}>Empty</HomarrCardWrapper>;
|
<HomarrCardWrapper className={className}>Empty</HomarrCardWrapper>
|
||||||
};
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Card, CardProps } from '@mantine/core';
|
import { Card, CardProps } from '@mantine/core';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { useCardStyles } from '../../layout/useCardStyles';
|
import { useCardStyles } from '../../layout/useCardStyles';
|
||||||
|
import { useEditModeStore } from '../Views/useEditModeStore';
|
||||||
|
|
||||||
interface HomarrCardWrapperProps extends CardProps {
|
interface HomarrCardWrapperProps extends CardProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -11,11 +12,13 @@ export const HomarrCardWrapper = ({ ...props }: HomarrCardWrapperProps) => {
|
|||||||
cx,
|
cx,
|
||||||
classes: { card: cardClass },
|
classes: { card: cardClass },
|
||||||
} = useCardStyles();
|
} = useCardStyles();
|
||||||
|
const isEditMode = useEditModeStore((x) => x.enabled);
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
{...props}
|
{...props}
|
||||||
className={cx(props.className, cardClass)}
|
className={cx(props.className, cardClass)}
|
||||||
withBorder
|
withBorder
|
||||||
|
style={{ cursor: isEditMode ? 'move' : 'default' }}
|
||||||
radius="lg"
|
radius="lg"
|
||||||
shadow="md"
|
shadow="md"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import Widgets from '../../../../widgets';
|
|
||||||
import { Button, Group, MultiSelect, Stack, Switch, TextInput } from '@mantine/core';
|
import { Button, Group, MultiSelect, Stack, Switch, TextInput } from '@mantine/core';
|
||||||
import { ContextModalProps } from '@mantine/modals';
|
import { ContextModalProps } from '@mantine/modals';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import Widgets from '../../../../widgets';
|
||||||
import { useConfigContext } from '../../../../config/provider';
|
import { useConfigContext } from '../../../../config/provider';
|
||||||
import { useConfigStore } from '../../../../config/store';
|
import { useConfigStore } from '../../../../config/store';
|
||||||
import { IWidget } from '../../../../widgets/widgets';
|
import { IWidget } from '../../../../widgets/widgets';
|
||||||
@@ -48,7 +48,7 @@ export const WidgetsEditModal = ({
|
|||||||
updateConfig(
|
updateConfig(
|
||||||
configName,
|
configName,
|
||||||
(prev) => {
|
(prev) => {
|
||||||
let currentWidget = prev.widgets.find((x) => x.id === innerProps.widgetId);
|
const currentWidget = prev.widgets.find((x) => x.id === innerProps.widgetId);
|
||||||
currentWidget!.properties = moduleProperties;
|
currentWidget!.properties = moduleProperties;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const WidgetsMenu = ({ integration, widget }: WidgetsMenuProps) => {
|
|||||||
title: null,
|
title: null,
|
||||||
innerProps: {
|
innerProps: {
|
||||||
widgetId: integration,
|
widgetId: integration,
|
||||||
widget: widget,
|
widget,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Group, Stack } from '@mantine/core';
|
import { Group, Stack } from '@mantine/core';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useConfigContext } from '../../../config/provider';
|
import { useConfigContext } from '../../../config/provider';
|
||||||
import { useScreenLargerThan } from '../../../tools/hooks/useScreenLargerThan';
|
|
||||||
import { useScreenSmallerThan } from '../../../tools/hooks/useScreenSmallerThan';
|
import { useScreenSmallerThan } from '../../../tools/hooks/useScreenSmallerThan';
|
||||||
import { CategoryType } from '../../../types/category';
|
import { CategoryType } from '../../../types/category';
|
||||||
import { WrapperType } from '../../../types/wrapper';
|
import { WrapperType } from '../../../types/wrapper';
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { DashboardView } from './DashboardView';
|
import { DashboardView } from './DashboardView';
|
||||||
|
|
||||||
export const DashboardDetailView = () => {
|
export const DashboardDetailView = () => <DashboardView />;
|
||||||
return <DashboardView />;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { DashboardView } from './DashboardView';
|
import { DashboardView } from './DashboardView';
|
||||||
|
|
||||||
export const DashboardEditView = () => {
|
export const DashboardEditView = () => <DashboardView />;
|
||||||
return <DashboardView />;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { Card, Group, Title } from '@mantine/core';
|
import { Group, Title } from '@mantine/core';
|
||||||
import { CategoryType } from '../../../../types/category';
|
import { CategoryType } from '../../../../types/category';
|
||||||
import { IWidgetDefinition } from '../../../../widgets/widgets';
|
|
||||||
import { HomarrCardWrapper } from '../../Tiles/HomarrCardWrapper';
|
import { HomarrCardWrapper } from '../../Tiles/HomarrCardWrapper';
|
||||||
import { Tiles } from '../../Tiles/tilesDefinitions';
|
|
||||||
import Widgets from '../../../../widgets';
|
|
||||||
import { GridstackTileWrapper } from '../../Tiles/TileWrapper';
|
|
||||||
import { useEditModeStore } from '../../Views/useEditModeStore';
|
import { useEditModeStore } from '../../Views/useEditModeStore';
|
||||||
import { useGridstack } from '../gridstack/use-gridstack';
|
import { useGridstack } from '../gridstack/use-gridstack';
|
||||||
import { CategoryEditMenu } from './CategoryEditMenu';
|
import { CategoryEditMenu } from './CategoryEditMenu';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Button, Group, TextInput } from '@mantine/core';
|
import { Button, Group, TextInput } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { ContextModalProps } from '@mantine/modals';
|
import { ContextModalProps } from '@mantine/modals';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfigContext } from '../../../../config/provider';
|
import { useConfigContext } from '../../../../config/provider';
|
||||||
import { useConfigStore } from '../../../../config/store';
|
import { useConfigStore } from '../../../../config/store';
|
||||||
import { CategoryType } from '../../../../types/category';
|
import { CategoryType } from '../../../../types/category';
|
||||||
@@ -31,15 +32,17 @@ export const CategoryEditModal = ({
|
|||||||
context.closeModal(id);
|
context.closeModal(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
<TextInput data-autoFocus {...form.getInputProps('name')} label="Name of category" />
|
<TextInput data-autoFocus {...form.getInputProps('name')} label="Name of category" />
|
||||||
|
|
||||||
<Group mt="md" grow>
|
<Group mt="md" grow>
|
||||||
<Button onClick={() => context.closeModal(id)} variant="light" color="gray">
|
<Button onClick={() => context.closeModal(id)} variant="light" color="gray">
|
||||||
Cancel
|
{t('cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit">Save</Button>
|
<Button type="submit">{t('save')}</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { Card } from '@mantine/core';
|
import { Card } from '@mantine/core';
|
||||||
import { RefObject } from 'react';
|
import { RefObject } from 'react';
|
||||||
import { IWidgetDefinition } from '../../../../widgets/widgets';
|
|
||||||
import { Tiles } from '../../Tiles/tilesDefinitions';
|
|
||||||
import Widgets from '../../../../widgets';
|
|
||||||
import { GridstackTileWrapper } from '../../Tiles/TileWrapper';
|
|
||||||
import { useGridstack } from '../gridstack/use-gridstack';
|
import { useGridstack } from '../gridstack/use-gridstack';
|
||||||
import { WrapperContent } from '../WrapperContent';
|
import { WrapperContent } from '../WrapperContent';
|
||||||
|
|
||||||
@@ -38,6 +34,5 @@ export const DashboardSidebar = ({ location }: DashboardSidebarProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useMinRowForFullHeight = (wrapperRef: RefObject<HTMLDivElement>) => {
|
const useMinRowForFullHeight = (wrapperRef: RefObject<HTMLDivElement>) =>
|
||||||
return wrapperRef.current ? Math.floor(wrapperRef.current!.offsetHeight / 64) : 2;
|
wrapperRef.current ? Math.floor(wrapperRef.current!.offsetHeight / 64) : 2;
|
||||||
};
|
|
||||||
|
|||||||
@@ -18,47 +18,45 @@ interface WrapperContentProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WrapperContent = ({ apps, refs, widgets }: WrapperContentProps) => {
|
export const WrapperContent = ({ apps, refs, widgets }: WrapperContentProps) => (
|
||||||
return (
|
<>
|
||||||
<>
|
{apps?.map((app) => {
|
||||||
{apps?.map((app) => {
|
const { component: TileComponent, ...tile } = Tiles.app;
|
||||||
const { component: TileComponent, ...tile } = Tiles['app'];
|
return (
|
||||||
return (
|
<GridstackTileWrapper
|
||||||
<GridstackTileWrapper
|
id={app.id}
|
||||||
id={app.id}
|
type="app"
|
||||||
type="app"
|
key={app.id}
|
||||||
key={app.id}
|
itemRef={refs.items.current[app.id]}
|
||||||
itemRef={refs.items.current[app.id]}
|
{...tile}
|
||||||
{...tile}
|
{...app.shape.location}
|
||||||
{...app.shape.location}
|
{...app.shape.size}
|
||||||
{...app.shape.size}
|
>
|
||||||
>
|
<TileComponent className="grid-stack-item-content" app={app} />
|
||||||
<TileComponent className="grid-stack-item-content" app={app} />
|
</GridstackTileWrapper>
|
||||||
</GridstackTileWrapper>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
{widgets.map((widget) => {
|
||||||
{widgets.map((widget) => {
|
const definition = Widgets[widget.id as keyof typeof Widgets] as
|
||||||
const definition = Widgets[widget.id as keyof typeof Widgets] as
|
| IWidgetDefinition
|
||||||
| IWidgetDefinition
|
| undefined;
|
||||||
| undefined;
|
if (!definition) return null;
|
||||||
if (!definition) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridstackTileWrapper
|
<GridstackTileWrapper
|
||||||
type="widget"
|
type="widget"
|
||||||
key={widget.id}
|
key={widget.id}
|
||||||
itemRef={refs.items.current[widget.id]}
|
itemRef={refs.items.current[widget.id]}
|
||||||
id={definition.id}
|
id={definition.id}
|
||||||
{...definition.gridstack}
|
{...definition.gridstack}
|
||||||
{...widget.shape.location}
|
{...widget.shape.location}
|
||||||
{...widget.shape.size}
|
{...widget.shape.size}
|
||||||
>
|
>
|
||||||
<WidgetWrapper className="grid-stack-item-content" widget={widget} widgetId={widget.id}>
|
<WidgetWrapper className="grid-stack-item-content" widget={widget} widgetId={widget.id}>
|
||||||
<definition.component className="grid-stack-item-content" widget={widget} />
|
<definition.component className="grid-stack-item-content" widget={widget} />
|
||||||
</WidgetWrapper>
|
</WidgetWrapper>
|
||||||
</GridstackTileWrapper>
|
</GridstackTileWrapper>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import ConfigChanger from '../../Config/ConfigChanger';
|
|||||||
import ConfigActions from './Config/ConfigActions';
|
import ConfigActions from './Config/ConfigActions';
|
||||||
import LanguageSelect from './Language/LanguageSelect';
|
import LanguageSelect from './Language/LanguageSelect';
|
||||||
import { SearchEngineSelector } from './SearchEngine/SearchEngineSelector';
|
import { SearchEngineSelector } from './SearchEngine/SearchEngineSelector';
|
||||||
import { SearchNewTabSwitch } from './SearchNewTabSwitch';
|
|
||||||
|
|
||||||
export default function CommonSettings() {
|
export default function CommonSettings() {
|
||||||
const { config } = useConfigContext();
|
const { config } = useConfigContext();
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default function ConfigActions() {
|
|||||||
<Flex gap="xs" justify="stretch">
|
<Flex gap="xs" justify="stretch">
|
||||||
<ActionIcon className={classes.actionIcon} onClick={handleDownload} variant="default">
|
<ActionIcon className={classes.actionIcon} onClick={handleDownload} variant="default">
|
||||||
<IconDownload size={20} />
|
<IconDownload size={20} />
|
||||||
<Text>{t('buttons.download')}</Text>
|
<Text size="sm">{t('buttons.download')}</Text>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
className={classes.actionIcon}
|
className={classes.actionIcon}
|
||||||
@@ -48,11 +48,11 @@ export default function ConfigActions() {
|
|||||||
variant="light"
|
variant="light"
|
||||||
>
|
>
|
||||||
<IconTrash color={colors.red[2]} size={20} />
|
<IconTrash color={colors.red[2]} size={20} />
|
||||||
<Text>{t('buttons.delete.text')}</Text>
|
<Text size="sm">{t('buttons.delete.text')}</Text>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
<ActionIcon className={classes.actionIcon} onClick={createCopyModal.open} variant="default">
|
<ActionIcon className={classes.actionIcon} onClick={createCopyModal.open} variant="default">
|
||||||
<IconCopy size={20} />
|
<IconCopy size={20} />
|
||||||
<Text>{t('buttons.saveCopy')}</Text>
|
<Text size="sm">{t('buttons.saveCopy')}</Text>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Center,
|
|
||||||
Checkbox,
|
Checkbox,
|
||||||
createStyles,
|
createStyles,
|
||||||
Flex,
|
Flex,
|
||||||
@@ -11,6 +10,7 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
useMantineTheme,
|
useMantineTheme,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { ChangeEvent, Dispatch, SetStateAction, useState } from 'react';
|
import { ChangeEvent, Dispatch, SetStateAction, useState } from 'react';
|
||||||
import { useConfigContext } from '../../../../config/provider';
|
import { useConfigContext } from '../../../../config/provider';
|
||||||
import { useConfigStore } from '../../../../config/store';
|
import { useConfigStore } from '../../../../config/store';
|
||||||
@@ -35,6 +35,7 @@ export const LayoutSelector = ({ defaultLayout }: LayoutSelectorProps) => {
|
|||||||
const [searchBar, setSearchBar] = useState(defaultLayout?.enabledSearchbar ?? false);
|
const [searchBar, setSearchBar] = useState(defaultLayout?.enabledSearchbar ?? false);
|
||||||
|
|
||||||
const { colors, colorScheme } = useMantineTheme();
|
const { colors, colorScheme } = useMantineTheme();
|
||||||
|
const { t } = useTranslation('settings/common');
|
||||||
|
|
||||||
if (!configName) return null;
|
if (!configName) return null;
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ export const LayoutSelector = ({ defaultLayout }: LayoutSelectorProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<Title order={6}>Dashboard layout</Title>
|
<Title order={6}>{t('layout.title')}</Title>
|
||||||
|
|
||||||
<Paper px="xs" py={4} withBorder>
|
<Paper px="xs" py={4} withBorder>
|
||||||
<Group position="apart">
|
<Group position="apart">
|
||||||
@@ -94,7 +95,7 @@ export const LayoutSelector = ({ defaultLayout }: LayoutSelectorProps) => {
|
|||||||
{leftSidebar && (
|
{leftSidebar && (
|
||||||
<Paper className={classes.secondaryWrapper} p="xs" withBorder>
|
<Paper className={classes.secondaryWrapper} p="xs" withBorder>
|
||||||
<Flex align="center" justify="center" direction="column">
|
<Flex align="center" justify="center" direction="column">
|
||||||
<Text align="center">Sidebar</Text>
|
<Text align="center">{t('layout.sidebar')}</Text>
|
||||||
<Text color="dimmed" size="xs" align="center">
|
<Text color="dimmed" size="xs" align="center">
|
||||||
Only for
|
Only for
|
||||||
<br />
|
<br />
|
||||||
@@ -106,16 +107,16 @@ export const LayoutSelector = ({ defaultLayout }: LayoutSelectorProps) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Paper className={classes.primaryWrapper} p="xs" withBorder>
|
<Paper className={classes.primaryWrapper} p="xs" withBorder>
|
||||||
<Text align="center">Main</Text>
|
<Text align="center">{t('layout.main')}</Text>
|
||||||
<Text color="dimmed" size="xs" align="center">
|
<Text color="dimmed" size="xs" align="center">
|
||||||
Cannot be turned of.
|
{t('layout.cannotturnoff')}
|
||||||
</Text>
|
</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{rightSidebar && (
|
{rightSidebar && (
|
||||||
<Paper className={classes.secondaryWrapper} p="xs" withBorder>
|
<Paper className={classes.secondaryWrapper} p="xs" withBorder>
|
||||||
<Flex align="center" justify="center" direction="column">
|
<Flex align="center" justify="center" direction="column">
|
||||||
<Text align="center">Sidebar</Text>
|
<Text align="center">{t('layout.sidebar')}</Text>
|
||||||
<Text color="dimmed" size="xs" align="center">
|
<Text color="dimmed" size="xs" align="center">
|
||||||
Only for
|
Only for
|
||||||
<br />
|
<br />
|
||||||
@@ -129,29 +130,29 @@ export const LayoutSelector = ({ defaultLayout }: LayoutSelectorProps) => {
|
|||||||
|
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Enable left sidebar"
|
label={t('layout.enablelsidebar')}
|
||||||
description="Optional. Can be used for apps and integrations only"
|
description={t('layout.enablelsidebardesc')}
|
||||||
checked={leftSidebar}
|
checked={leftSidebar}
|
||||||
onChange={(ev) => handleChange('enabledLeftSidebar', ev, setLeftSidebar)}
|
onChange={(ev) => handleChange('enabledLeftSidebar', ev, setLeftSidebar)}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Enable right sidebar"
|
label={t('layout.enablersidebar')}
|
||||||
description="Optional. Can be used for apps and integrations only"
|
description={t('layout.enablersidebardesc')}
|
||||||
checked={rightSidebar}
|
checked={rightSidebar}
|
||||||
onChange={(ev) => handleChange('enabledRightSidebar', ev, setRightSidebar)}
|
onChange={(ev) => handleChange('enabledRightSidebar', ev, setRightSidebar)}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Enable search bar"
|
label={t('layout.enablesearchbar')}
|
||||||
checked={searchBar}
|
checked={searchBar}
|
||||||
onChange={(ev) => handleChange('enabledSearchbar', ev, setSearchBar)}
|
onChange={(ev) => handleChange('enabledSearchbar', ev, setSearchBar)}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Enable docker"
|
label={t('layout.enabledocker')}
|
||||||
checked={docker}
|
checked={docker}
|
||||||
onChange={(ev) => handleChange('enabledDocker', ev, setDocker)}
|
onChange={(ev) => handleChange('enabledDocker', ev, setDocker)}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Enable pings"
|
label={t('layout.enableping')}
|
||||||
checked={ping}
|
checked={ping}
|
||||||
onChange={(ev) => handleChange('enabledPing', ev, setPing)}
|
onChange={(ev) => handleChange('enabledPing', ev, setPing)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import { ActionIcon, Title, Tooltip, Drawer, Tabs, ScrollArea } from '@mantine/core';
|
import { Title, Drawer, Tabs, ScrollArea } from '@mantine/core';
|
||||||
import { useHotkeys } from '@mantine/hooks';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { IconSettings } from '@tabler/icons';
|
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import CustomizationSettings from './Customization/CustomizationSettings';
|
import CustomizationSettings from './Customization/CustomizationSettings';
|
||||||
import CommonSettings from './Common/CommonSettings';
|
import CommonSettings from './Common/CommonSettings';
|
||||||
import Credits from './Common/Credits';
|
import Credits from './Common/Credits';
|
||||||
|
|
||||||
function SettingsMenu() {
|
function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string }) {
|
||||||
const { t } = useTranslation('settings/common');
|
const { t } = useTranslation('settings/common');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -36,7 +33,11 @@ interface SettingsDrawerProps {
|
|||||||
closeDrawer: () => void;
|
closeDrawer: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsDrawer({ opened, closeDrawer }: SettingsDrawerProps) {
|
export function SettingsDrawer({
|
||||||
|
opened,
|
||||||
|
closeDrawer,
|
||||||
|
newVersionAvailable,
|
||||||
|
}: SettingsDrawerProps & { newVersionAvailable: string }) {
|
||||||
const { t } = useTranslation('settings/common');
|
const { t } = useTranslation('settings/common');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -48,7 +49,7 @@ export function SettingsDrawer({ opened, closeDrawer }: SettingsDrawerProps) {
|
|||||||
opened={opened}
|
opened={opened}
|
||||||
onClose={closeDrawer}
|
onClose={closeDrawer}
|
||||||
>
|
>
|
||||||
<SettingsMenu />
|
<SettingsMenu newVersionAvailable={newVersionAvailable} />
|
||||||
<Credits />
|
<Credits />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
import React, { useEffect } from 'react';
|
|
||||||
import { createStyles, Footer as FooterComponent } from '@mantine/core';
|
|
||||||
import { showNotification } from '@mantine/notifications';
|
|
||||||
import { IconAlertCircle as AlertCircle } from '@tabler/icons';
|
|
||||||
import { CURRENT_VERSION, REPO_URL } from '../../../data/constants';
|
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
|
||||||
footer: {
|
|
||||||
borderTop: `1px solid ${
|
|
||||||
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
|
|
||||||
inner: {
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: `${theme.spacing.md}px ${theme.spacing.md}px`,
|
|
||||||
|
|
||||||
[theme.fn.smallerThan('sm')]: {
|
|
||||||
flexDirection: 'column',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
links: {
|
|
||||||
[theme.fn.smallerThan('sm')]: {
|
|
||||||
marginTop: theme.spacing.lg,
|
|
||||||
marginBottom: theme.spacing.sm,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
interface FooterCenteredProps {
|
|
||||||
links: { link: string; label: string }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Footer({ links }: FooterCenteredProps) {
|
|
||||||
useEffect(() => {
|
|
||||||
// Fetch Data here when component first mounted
|
|
||||||
fetch(`https://api.github.com/repos/${REPO_URL}/releases/latest`).then((res) => {
|
|
||||||
res.json().then((data) => {
|
|
||||||
if (data.tag_name > CURRENT_VERSION) {
|
|
||||||
showNotification({
|
|
||||||
color: 'yellow',
|
|
||||||
autoClose: false,
|
|
||||||
title: 'New version available',
|
|
||||||
icon: <AlertCircle />,
|
|
||||||
message: `Version ${data.tag_name} is available, update now!`,
|
|
||||||
});
|
|
||||||
} else if (data.tag_name < CURRENT_VERSION) {
|
|
||||||
showNotification({
|
|
||||||
color: 'orange',
|
|
||||||
autoClose: 5000,
|
|
||||||
title: 'You are using a development version',
|
|
||||||
icon: <AlertCircle />,
|
|
||||||
message: 'This version of Homarr is still in development! Bugs are expected 🐛',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FooterComponent
|
|
||||||
height="auto"
|
|
||||||
style={{
|
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
|
||||||
clear: 'both',
|
|
||||||
}}
|
|
||||||
children={undefined}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import { AppShell, createStyles } from '@mantine/core';
|
import { AppShell, createStyles } from '@mantine/core';
|
||||||
import { useConfigContext } from '../../config/provider';
|
import { useConfigContext } from '../../config/provider';
|
||||||
import { Background } from './Background';
|
import { Background } from './Background';
|
||||||
import { Footer } from './Footer';
|
import { Header } from './header/Header';
|
||||||
import { Header } from './Header/Header';
|
import { Head } from './header/Meta/Head';
|
||||||
import { Head } from './Header/Meta/Head';
|
|
||||||
|
|
||||||
const useStyles = createStyles(() => ({}));
|
const useStyles = createStyles(() => ({}));
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ export default function Layout({ children }: any) {
|
|||||||
<AppShell
|
<AppShell
|
||||||
fixed={false}
|
fixed={false}
|
||||||
header={<Header />}
|
header={<Header />}
|
||||||
footer={<Footer links={[]} />}
|
|
||||||
styles={{
|
styles={{
|
||||||
main: {
|
main: {
|
||||||
minHeight: 'calc(100vh - var(--mantine-header-height))',
|
minHeight: 'calc(100vh - var(--mantine-header-height))',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { IconApps } from '@tabler/icons';
|
|||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
export const AddElementAction = () => {
|
export const AddElementAction = () => {
|
||||||
const { t } = useTranslation('layout/add-service-app-shelf');
|
const { t } = useTranslation('layout/element-selector/selector');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip withinPortal label={t('actionIcon.tooltip')}>
|
<Tooltip withinPortal label={t('actionIcon.tooltip')}>
|
||||||
@@ -16,7 +16,7 @@ export const AddElementAction = () => {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
openContextModal({
|
openContextModal({
|
||||||
modal: 'selectElement',
|
modal: 'selectElement',
|
||||||
title: 'Add an element to your dashboard',
|
title: t('modal.title'),
|
||||||
size: 'xl',
|
size: 'xl',
|
||||||
innerProps: {},
|
innerProps: {},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { Box, createStyles, Group, Header as MantineHeader } from '@mantine/core';
|
import { Box, createStyles, Group, Header as MantineHeader, Indicator } from '@mantine/core';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { REPO_URL, CURRENT_VERSION } from '../../../../data/constants';
|
||||||
import { useConfigContext } from '../../../config/provider';
|
import { useConfigContext } from '../../../config/provider';
|
||||||
import { Logo } from '../Logo';
|
import { Logo } from '../Logo';
|
||||||
import { useCardStyles } from '../useCardStyles';
|
import { useCardStyles } from '../useCardStyles';
|
||||||
@@ -15,6 +17,18 @@ export function Header(props: any) {
|
|||||||
|
|
||||||
const { config } = useConfigContext();
|
const { config } = useConfigContext();
|
||||||
|
|
||||||
|
const [newVersionAvailable, setNewVersionAvailable] = useState<string>('');
|
||||||
|
useEffect(() => {
|
||||||
|
// Fetch Data here when component first mounted
|
||||||
|
fetch(`https://api.github.com/repos/${REPO_URL}/releases/latest`).then((res) => {
|
||||||
|
res.json().then((data) => {
|
||||||
|
if (data.tag_name > CURRENT_VERSION) {
|
||||||
|
setNewVersionAvailable(data.tag_name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [CURRENT_VERSION]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MantineHeader height={HeaderHeight} className={cardClasses.card}>
|
<MantineHeader height={HeaderHeight} className={cardClasses.card}>
|
||||||
<Group p="xs" noWrap grow>
|
<Group p="xs" noWrap grow>
|
||||||
@@ -25,7 +39,9 @@ export function Header(props: any) {
|
|||||||
<Search />
|
<Search />
|
||||||
<AddElementAction />
|
<AddElementAction />
|
||||||
<ToggleEditModeAction />
|
<ToggleEditModeAction />
|
||||||
<SettingsMenu />
|
<Indicator size={15} color="blue" withBorder processing disabled={!newVersionAvailable}>
|
||||||
|
<SettingsMenu newVersionAvailable={newVersionAvailable} />
|
||||||
|
</Indicator>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</MantineHeader>
|
</MantineHeader>
|
||||||
|
|||||||
@@ -60,8 +60,7 @@ export function Search() {
|
|||||||
// Overseerr is not use anywhere else, so it makes no sense to add a standalone toggle for displaying results
|
// Overseerr is not use anywhere else, so it makes no sense to add a standalone toggle for displaying results
|
||||||
const isOverseerrEnabled = false; //config?.settings.common.enabledModules.overseerr;
|
const isOverseerrEnabled = false; //config?.settings.common.enabledModules.overseerr;
|
||||||
const overseerrApp = config?.apps.find(
|
const overseerrApp = config?.apps.find(
|
||||||
(app) =>
|
(app) => app.integration?.type === 'overseerr' || app.integration?.type === 'jellyseerr'
|
||||||
app.integration?.type === 'overseerr' || app.integration?.type === 'jellyseerr'
|
|
||||||
);
|
);
|
||||||
const searchEngineSettings = config?.settings.common.searchEngine;
|
const searchEngineSettings = config?.settings.common.searchEngine;
|
||||||
const searchEngineUrl = !searchEngineSettings
|
const searchEngineUrl = !searchEngineSettings
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { ActionIcon, Menu, Tooltip } from '@mantine/core';
|
import { ActionIcon, Badge, Menu, Tooltip } from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { IconInfoCircle, IconMenu2, IconSettings } from '@tabler/icons';
|
import { IconInfoCircle, IconMenu2, IconSettings } from '@tabler/icons';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { AboutModal } from '../../About/AboutModal';
|
import { AboutModal } from '../../About/AboutModal';
|
||||||
import { SettingsDrawer } from '../../Settings/SettingsDrawer';
|
import { SettingsDrawer } from '../../Settings/SettingsDrawer';
|
||||||
import { ColorSchemeSwitch } from './SettingsMenu/ColorSchemeSwitch';
|
import { ColorSchemeSwitch } from './SettingsMenu/ColorSchemeSwitch';
|
||||||
|
|
||||||
export const SettingsMenu = () => {
|
export function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string }) {
|
||||||
const [drawerOpened, drawer] = useDisclosure(false);
|
const [drawerOpened, drawer] = useDisclosure(false);
|
||||||
|
const { t } = useTranslation('common');
|
||||||
const [aboutModalOpened, aboutModal] = useDisclosure(false);
|
const [aboutModalOpened, aboutModal] = useDisclosure(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -22,19 +24,34 @@ export const SettingsMenu = () => {
|
|||||||
<ColorSchemeSwitch />
|
<ColorSchemeSwitch />
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
<Menu.Item icon={<IconSettings strokeWidth={1.2} size={18} />} onClick={drawer.open}>
|
<Menu.Item icon={<IconSettings strokeWidth={1.2} size={18} />} onClick={drawer.open}>
|
||||||
Homarr Settings
|
{t('sections.settings')}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
icon={<IconInfoCircle strokeWidth={1.2} size={18} />}
|
icon={<IconInfoCircle strokeWidth={1.2} size={18} />}
|
||||||
onClick={aboutModal.open}
|
rightSection={
|
||||||
|
newVersionAvailable && (
|
||||||
|
<Badge variant="light" color="blue">
|
||||||
|
New
|
||||||
|
</Badge>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={() => aboutModal.open()}
|
||||||
>
|
>
|
||||||
About
|
About
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu.Dropdown>
|
</Menu.Dropdown>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<SettingsDrawer opened={drawerOpened} closeDrawer={drawer.close} />
|
<SettingsDrawer
|
||||||
<AboutModal opened={aboutModalOpened} closeModal={aboutModal.close} />
|
opened={drawerOpened}
|
||||||
|
closeDrawer={drawer.close}
|
||||||
|
newVersionAvailable={newVersionAvailable}
|
||||||
|
/>
|
||||||
|
<AboutModal
|
||||||
|
opened={aboutModalOpened}
|
||||||
|
closeModal={aboutModal.close}
|
||||||
|
newVersionAvailable={newVersionAvailable}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Menu, useMantineColorScheme, useMantineTheme } from '@mantine/core';
|
import { Menu, useMantineColorScheme } from '@mantine/core';
|
||||||
import { IconMoonStars, IconSun } from '@tabler/icons';
|
import { IconMoonStars, IconSun } from '@tabler/icons';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
'lidarr',
|
'lidarr',
|
||||||
];
|
];
|
||||||
const mediaApps = config.apps.filter(
|
const mediaApps = config.apps.filter(
|
||||||
(app) =>
|
(app) => app.integration && mediaAppIntegrationTypes.includes(app.integration.type)
|
||||||
app.integration && mediaAppIntegrationTypes.includes(app.integration.type)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const medias = await Promise.all(
|
const medias = await Promise.all(
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { getCookie } from 'cookies-next';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Consola from 'consola';
|
import Consola from 'consola';
|
||||||
import { getConfig } from '../../../../tools/config/getConfig';
|
import { getConfig } from '../../../../tools/config/getConfig';
|
||||||
import { Config } from '../../../../tools/types';
|
|
||||||
import { MediaType } from '../../../../modules/overseerr/SearchResult';
|
import { MediaType } from '../../../../modules/overseerr/SearchResult';
|
||||||
|
|
||||||
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import axios from 'axios';
|
|||||||
import { getCookie } from 'cookies-next';
|
import { getCookie } from 'cookies-next';
|
||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { getConfig } from '../../../../tools/config/getConfig';
|
import { getConfig } from '../../../../tools/config/getConfig';
|
||||||
import { Config } from '../../../../tools/types';
|
|
||||||
|
|
||||||
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const configName = getCookie('config-name', { req });
|
const configName = getCookie('config-name', { req });
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
import ping from 'ping';
|
import axios from 'axios';
|
||||||
|
import https from 'https';
|
||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
|
||||||
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
||||||
// Parse req.body as a AppItem
|
// Parse req.body as a AppItem
|
||||||
const { url } = req.query;
|
const { url } = req.query;
|
||||||
// Parse url as URL object
|
const agent = new https.Agent({ rejectUnauthorized: false });
|
||||||
const parsedUrl = new URL(url as string);
|
await axios
|
||||||
// Ping the URL
|
.get(url as string, { httpsAgent: agent, timeout: 2000 })
|
||||||
const response = await ping.promise.probe(parsedUrl.hostname, {
|
.then((response) => {
|
||||||
timeout: 1,
|
res.status(response.status).json(response.statusText);
|
||||||
});
|
})
|
||||||
|
.catch((error) => {
|
||||||
// Return 200 if the alive property is true
|
if (error.response) {
|
||||||
if (response.alive) {
|
res.status(error.response.status).json(error.response.statusText);
|
||||||
return res.status(200).end();
|
} else if (error.code === 'ECONNABORTED') {
|
||||||
}
|
res.status(408).json('Request Timeout');
|
||||||
// Return 404 if the alive property is false
|
} else {
|
||||||
return res.status(404).end();
|
res.status(500).json('Server Error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// // Make a request to the URL
|
||||||
|
// const response = await axios.get(url);
|
||||||
|
// // Return the response
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
|||||||
@@ -39,10 +39,8 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const options = {
|
const options = {
|
||||||
host: url.hostname,
|
host: url.hostname,
|
||||||
port: url.port,
|
port: url.port,
|
||||||
login:
|
login: app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
|
||||||
app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
|
hash: app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
|
||||||
hash:
|
|
||||||
app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const nzbGet = NzbgetClient(options);
|
const nzbGet = NzbgetClient(options);
|
||||||
|
|||||||
@@ -31,10 +31,8 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const options = {
|
const options = {
|
||||||
host: url.hostname,
|
host: url.hostname,
|
||||||
port: url.port,
|
port: url.port,
|
||||||
login:
|
login: app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
|
||||||
app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
|
hash: app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
|
||||||
hash:
|
|
||||||
app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const nzbGet = NzbgetClient(options);
|
const nzbGet = NzbgetClient(options);
|
||||||
|
|||||||
@@ -32,10 +32,8 @@ async function Post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const options = {
|
const options = {
|
||||||
host: url.hostname,
|
host: url.hostname,
|
||||||
port: url.port,
|
port: url.port,
|
||||||
login:
|
login: app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
|
||||||
app.integration.properties.find((x) => x.field === 'username')?.value ?? undefined,
|
hash: app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
|
||||||
hash:
|
|
||||||
app.integration.properties.find((x) => x.field === 'password')?.value ?? undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const nzbGet = NzbgetClient(options);
|
const nzbGet = NzbgetClient(options);
|
||||||
|
|||||||
@@ -10,5 +10,4 @@ export const useGetServiceByType = (...serviceTypes: ServiceType[]) => {
|
|||||||
export const getServiceByType = (config: Config, ...serviceTypes: ServiceType[]) =>
|
export const getServiceByType = (config: Config, ...serviceTypes: ServiceType[]) =>
|
||||||
config.apps.filter((s) => serviceTypes.includes(s.type));
|
config.apps.filter((s) => serviceTypes.includes(s.type));
|
||||||
|
|
||||||
export const getServiceById = (config: Config, id: string) =>
|
export const getServiceById = (config: Config, id: string) => config.apps.find((s) => s.id === id);
|
||||||
config.apps.find((s) => s.id === id);
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
export const dashboardNamespaces = [
|
export const dashboardNamespaces = [
|
||||||
'common',
|
'common',
|
||||||
'layout/app-shelf',
|
|
||||||
'layout/add-service-app-shelf',
|
|
||||||
'layout/app-shelf-menu',
|
|
||||||
'layout/tools',
|
'layout/tools',
|
||||||
'layout/element-selector/selector',
|
'layout/element-selector/selector',
|
||||||
|
'layout/modals/add-app',
|
||||||
'layout/header/actions/toggle-edit-mode',
|
'layout/header/actions/toggle-edit-mode',
|
||||||
'settings/common',
|
'settings/common',
|
||||||
'settings/general/theme-selector',
|
'settings/general/theme-selector',
|
||||||
|
|||||||
@@ -90,16 +90,16 @@ export const integrationFieldDefinitions: {
|
|||||||
apiKey: {
|
apiKey: {
|
||||||
type: 'private',
|
type: 'private',
|
||||||
icon: IconKey,
|
icon: IconKey,
|
||||||
label: 'API Key',
|
label: 'common:secrets.apiKey',
|
||||||
},
|
},
|
||||||
username: {
|
username: {
|
||||||
type: 'public',
|
type: 'public',
|
||||||
icon: IconUser,
|
icon: IconUser,
|
||||||
label: 'Username',
|
label: 'common:secrets.username',
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
type: 'private',
|
type: 'private',
|
||||||
icon: IconPassword,
|
icon: IconPassword,
|
||||||
label: 'Password',
|
label: 'common:secrets.password',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4536,7 +4536,7 @@ __metadata:
|
|||||||
|
|
||||||
"fsevents@patch:fsevents@^2.3.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@~2.3.2#~builtin<compat/fsevents>":
|
"fsevents@patch:fsevents@^2.3.2#~builtin<compat/fsevents>, fsevents@patch:fsevents@~2.3.2#~builtin<compat/fsevents>":
|
||||||
version: 2.3.2
|
version: 2.3.2
|
||||||
resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=df0bf1"
|
resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin<compat/fsevents>::version=2.3.2&hash=18f3a7"
|
||||||
dependencies:
|
dependencies:
|
||||||
node-gyp: latest
|
node-gyp: latest
|
||||||
conditions: os=darwin
|
conditions: os=darwin
|
||||||
@@ -7489,7 +7489,7 @@ __metadata:
|
|||||||
|
|
||||||
"resolve@patch:resolve@^1.19.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.20.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.0#~builtin<compat/resolve>":
|
"resolve@patch:resolve@^1.19.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.20.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.0#~builtin<compat/resolve>":
|
||||||
version: 1.22.1
|
version: 1.22.1
|
||||||
resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin<compat/resolve>::version=1.22.1&hash=c3c19d"
|
resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin<compat/resolve>::version=1.22.1&hash=07638b"
|
||||||
dependencies:
|
dependencies:
|
||||||
is-core-module: ^2.9.0
|
is-core-module: ^2.9.0
|
||||||
path-parse: ^1.0.7
|
path-parse: ^1.0.7
|
||||||
@@ -7502,7 +7502,7 @@ __metadata:
|
|||||||
|
|
||||||
"resolve@patch:resolve@^2.0.0-next.3#~builtin<compat/resolve>":
|
"resolve@patch:resolve@^2.0.0-next.3#~builtin<compat/resolve>":
|
||||||
version: 2.0.0-next.4
|
version: 2.0.0-next.4
|
||||||
resolution: "resolve@patch:resolve@npm%3A2.0.0-next.4#~builtin<compat/resolve>::version=2.0.0-next.4&hash=c3c19d"
|
resolution: "resolve@patch:resolve@npm%3A2.0.0-next.4#~builtin<compat/resolve>::version=2.0.0-next.4&hash=07638b"
|
||||||
dependencies:
|
dependencies:
|
||||||
is-core-module: ^2.9.0
|
is-core-module: ^2.9.0
|
||||||
path-parse: ^1.0.7
|
path-parse: ^1.0.7
|
||||||
@@ -8338,7 +8338,7 @@ __metadata:
|
|||||||
|
|
||||||
"typescript@patch:typescript@^4.7.4#~builtin<compat/typescript>":
|
"typescript@patch:typescript@^4.7.4#~builtin<compat/typescript>":
|
||||||
version: 4.9.4
|
version: 4.9.4
|
||||||
resolution: "typescript@patch:typescript@npm%3A4.9.4#~builtin<compat/typescript>::version=4.9.4&hash=d73830"
|
resolution: "typescript@patch:typescript@npm%3A4.9.4#~builtin<compat/typescript>::version=4.9.4&hash=7ad353"
|
||||||
bin:
|
bin:
|
||||||
tsc: bin/tsc
|
tsc: bin/tsc
|
||||||
tsserver: bin/tsserver
|
tsserver: bin/tsserver
|
||||||
|
|||||||
Reference in New Issue
Block a user