diff --git a/.env b/.env new file mode 100644 index 000000000..611ce2a59 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +DISABLE_EDIT_MODE=TRUE +EDIT_MODE_PASSWORD='edit' \ No newline at end of file diff --git a/src/components/Dashboard/Modals/AboutModal/AboutModal.tsx b/src/components/Dashboard/Modals/AboutModal/AboutModal.tsx index 8f366fd9f..4346f0124 100644 --- a/src/components/Dashboard/Modals/AboutModal/AboutModal.tsx +++ b/src/components/Dashboard/Modals/AboutModal/AboutModal.tsx @@ -9,10 +9,10 @@ import { Group, HoverCard, Modal, - Stack, Table, Text, Title, + Tooltip, } from '@mantine/core'; import { IconAnchor, @@ -35,6 +35,7 @@ import { useConfigContext } from '../../../../config/provider'; import { useConfigStore } from '../../../../config/store'; import { useEditModeInformationStore } from '../../../../hooks/useEditModeInformation'; import { usePackageAttributesStore } from '../../../../tools/client/zustands/usePackageAttributesStore'; +import { useColorTheme } from '../../../../tools/color'; import { usePrimaryGradient } from '../../../layout/useGradient'; import Credits from '../../../Settings/Common/Credits'; @@ -161,9 +162,9 @@ interface ExtendedInitOptions extends InitOptions { } const useInformationTableItems = (newVersionAvailable?: string): InformationTableItem[] => { - const colorGradiant = usePrimaryGradient(); const { attributes } = usePackageAttributesStore(); const { editModeEnabled } = useEditModeInformationStore(); + const { primaryColor } = useColorTheme(); const { configVersion } = useConfigContext(); const { configs } = useConfigStore(); @@ -177,15 +178,19 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl icon: , label: 'experimental_disableEditMode', content: ( - + WARNING - - This is an experimental feature, where the edit mode is disabled entirely - no config - modifications are possbile anymore. All update requests for the config will be dropped - on the API. This will be removed in future versions, as Homarr will receive a proper - authentication system, which will make this obsolete. - - + ), }, ]; @@ -201,7 +206,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl icon: , label: 'i18n', content: ( - + {usedI18nNamespaces.length} ), @@ -210,7 +215,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl icon: , label: 'locales', content: ( - + {initOptions.locales.length} ), @@ -223,7 +228,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl icon: , label: 'configurationSchemaVersion', content: ( - + {configVersion} ), @@ -232,7 +237,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl icon: , label: 'configurationsCount', content: ( - + {configs.length} ), @@ -242,7 +247,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl label: 'version', content: ( - + {attributes.packageVersion ?? 'Unknown'} {newVersionAvailable && ( @@ -282,7 +287,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl icon: , label: 'nodeEnvironment', content: ( - + {attributes.environment} ), diff --git a/src/components/layout/header/SettingsMenu.tsx b/src/components/layout/header/SettingsMenu.tsx index 970762e68..91d04a2fd 100644 --- a/src/components/layout/header/SettingsMenu.tsx +++ b/src/components/layout/header/SettingsMenu.tsx @@ -7,6 +7,7 @@ import { AboutModal } from '../../Dashboard/Modals/AboutModal/AboutModal'; import { SettingsDrawer } from '../../Settings/SettingsDrawer'; import { useCardStyles } from '../useCardStyles'; import { ColorSchemeSwitch } from './SettingsMenu/ColorSchemeSwitch'; +import { EditModeToggle } from './SettingsMenu/EditModeToggle'; export function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string }) { const [drawerOpened, drawer] = useDisclosure(false); @@ -25,6 +26,7 @@ export function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: str + {!editModeEnabled && ( } onClick={drawer.open}> diff --git a/src/components/layout/header/SettingsMenu/EditModeToggle.tsx b/src/components/layout/header/SettingsMenu/EditModeToggle.tsx new file mode 100644 index 000000000..083e889c9 --- /dev/null +++ b/src/components/layout/header/SettingsMenu/EditModeToggle.tsx @@ -0,0 +1,78 @@ +import { Button, Code, Menu, PasswordInput, Stack, Text } from '@mantine/core'; +import { useForm } from '@mantine/form'; +import { openModal } from '@mantine/modals'; +import { showNotification } from '@mantine/notifications'; +import { IconEdit, IconEditOff } from '@tabler/icons'; +import axios from 'axios'; +import { useEditModeInformationStore } from '../../../../hooks/useEditModeInformation'; + +function ModalContent() { + const form = useForm({ + initialValues: { + triedPassword: '', + }, + }); + return ( +
{ + axios.post('/api/configs/tryToggleEdit', { tried: values.triedPassword }).then((res) => { + if (res.data.success) { + showNotification({ + title: 'Success', + message: 'Successfully toggled edit mode, reloading the page...', + color: 'green', + }); + setTimeout(() => { + window.location.reload(); + }, 500); + } else { + showNotification({ + title: 'Wrong password', + message: 'The password you entered is wrong.', + color: 'red', + }); + } + }); + })} + > + + + In order to toggle edit mode, you need to enter the password you entered in the + environment variable named EDIT_MODE_PASSWORD + + + + +
+ ); +} + +export function EditModeToggle() { + const { editModeEnabled } = useEditModeInformationStore(); + const Icon = editModeEnabled ? IconEdit : IconEditOff; + + return ( + } + onClick={() => + openModal({ + title: 'Toggle edit mode', + centered: true, + size: 'lg', + children: , + }) + } + > + {editModeEnabled ? 'Enable edit mode' : 'Disable edit mode'} + + ); +} diff --git a/src/modules/Docker/DockerModule.tsx b/src/modules/Docker/DockerModule.tsx index 91de2247b..1948c60a3 100644 --- a/src/modules/Docker/DockerModule.tsx +++ b/src/modules/Docker/DockerModule.tsx @@ -54,7 +54,7 @@ export default function DockerMenuButton(props: any) { }, 300); } - if (!dockerEnabled) { + if (!dockerEnabled || process.env.DISABLE_EDIT_MODE === 'true') { return null; } diff --git a/src/pages/api/configs/tryToggleEdit.tsx b/src/pages/api/configs/tryToggleEdit.tsx new file mode 100644 index 000000000..406babdce --- /dev/null +++ b/src/pages/api/configs/tryToggleEdit.tsx @@ -0,0 +1,34 @@ +import Consola from 'consola'; +import { NextApiRequest, NextApiResponse } from 'next'; + +function Post(req: NextApiRequest, res: NextApiResponse) { + const { tried } = req.body; + // Try to match the password with the EDIT_PASSWORD env variable + Consola.log(req.body, process.env.EDIT_MODE_PASSWORD); + if (tried === process.env.EDIT_MODE_PASSWORD) { + process.env.DISABLE_EDIT_MODE = process.env.DISABLE_EDIT_MODE === 'true' ? 'false' : 'true'; + return res.status(200).json({ + success: true, + }); + } + // Warn that there was a wrong password attempt (date : wrong password, person's IP) + Consola.warn( + `${new Date().toLocaleString()} : Wrong edit password attempt, from ${ + req.headers['x-forwarded-for'] + }` + ); + return res.status(200).json({ + success: false, + }); +} + +export default async (req: NextApiRequest, res: NextApiResponse) => { + // Filter out if the request is a POST or a GET + if (req.method === 'POST') { + return Post(req, res); + } + return res.status(405).json({ + statusCode: 405, + message: 'Method not allowed', + }); +};