diff --git a/public/locales/en/layout/modals/about.json b/public/locales/en/layout/modals/about.json index 2263dc21f..aa99318db 100644 --- a/public/locales/en/layout/modals/about.json +++ b/public/locales/en/layout/modals/about.json @@ -8,6 +8,7 @@ "version": "Version", "nodeEnvironment": "Node environment", "i18n": "Loaded I18n translation namespaces", - "locales": "Configured I18n locales" + "locales": "Configured I18n locales", + "experimental_disableEditMode": "EXPERIMENTAL: Disable edit mode" } } \ 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 3d36ca722..8f366fd9f 100644 --- a/src/components/Dashboard/Modals/AboutModal/AboutModal.tsx +++ b/src/components/Dashboard/Modals/AboutModal/AboutModal.tsx @@ -9,6 +9,7 @@ import { Group, HoverCard, Modal, + Stack, Table, Text, Title, @@ -18,6 +19,7 @@ import { IconBrandDiscord, IconBrandGithub, IconFile, + IconKey, IconLanguage, IconSchema, IconVersions, @@ -31,6 +33,7 @@ import Image from 'next/image'; import { ReactNode } from 'react'; import { useConfigContext } from '../../../../config/provider'; import { useConfigStore } from '../../../../config/store'; +import { useEditModeInformationStore } from '../../../../hooks/useEditModeInformation'; import { usePackageAttributesStore } from '../../../../tools/client/zustands/usePackageAttributesStore'; import { usePrimaryGradient } from '../../../layout/useGradient'; import Credits from '../../../Settings/Common/Credits'; @@ -82,10 +85,17 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod {item.icon} - {t(`layout/modals/about:metrics.${item.label}`)} + + }} + /> + - {item.content} + + {item.content} + ))} @@ -153,12 +163,34 @@ interface ExtendedInitOptions extends InitOptions { const useInformationTableItems = (newVersionAvailable?: string): InformationTableItem[] => { const colorGradiant = usePrimaryGradient(); const { attributes } = usePackageAttributesStore(); + const { editModeEnabled } = useEditModeInformationStore(); const { configVersion } = useConfigContext(); const { configs } = useConfigStore(); let items: InformationTableItem[] = []; + if (editModeEnabled) { + items = [ + ...items, + { + 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. + + + ), + }, + ]; + } + if (i18n !== null) { const usedI18nNamespaces = i18n.reportNamespaces.getUsedNamespaces(); const initOptions = i18n.options as ExtendedInitOptions; diff --git a/src/components/layout/header/Header.tsx b/src/components/layout/header/Header.tsx index 52c47bc17..44bb12b51 100644 --- a/src/components/layout/header/Header.tsx +++ b/src/components/layout/header/Header.tsx @@ -1,6 +1,7 @@ import { Box, createStyles, Group, Header as MantineHeader, Indicator } from '@mantine/core'; import { useQuery } from '@tanstack/react-query'; import { REPO_URL } from '../../../../data/constants'; +import { useEditModeInformationStore } from '../../../hooks/useEditModeInformation'; import DockerMenuButton from '../../../modules/Docker/DockerModule'; import { usePackageAttributesStore } from '../../../tools/client/zustands/usePackageAttributesStore'; import { Logo } from '../Logo'; @@ -15,8 +16,9 @@ export function Header(props: any) { const { classes } = useStyles(); const { classes: cardClasses, cx } = useCardStyles(false); const { attributes } = usePackageAttributesStore(); + const { editModeEnabled } = useEditModeInformationStore(); - const { isLoading, error, data } = useQuery({ + const { data } = useQuery({ queryKey: ['github/latest'], cacheTime: 1000 * 60 * 60 * 24, staleTime: 1000 * 60 * 60 * 5, @@ -32,9 +34,14 @@ export function Header(props: any) { - + - + {!editModeEnabled && } @@ -24,9 +26,11 @@ export function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: str - } onClick={drawer.open}> - {t('sections.settings')} - + {!editModeEnabled && ( + } onClick={drawer.open}> + {t('sections.settings')} + + )} } rightSection={ diff --git a/src/hooks/useEditModeInformation.ts b/src/hooks/useEditModeInformation.ts new file mode 100644 index 000000000..c59edc86c --- /dev/null +++ b/src/hooks/useEditModeInformation.ts @@ -0,0 +1,11 @@ +import { create } from 'zustand'; + +interface EditModeInformationStore { + editModeEnabled: boolean; + setDisabled: () => void; +} + +export const useEditModeInformationStore = create((set) => ({ + editModeEnabled: false, + setDisabled: () => set(() => ({ editModeEnabled: true })), +})); diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 81107b5f4..e55dd6bef 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,15 +1,17 @@ import { ColorScheme, ColorSchemeProvider, MantineProvider, MantineTheme } from '@mantine/core'; import { useColorScheme, useHotkeys, useLocalStorage } from '@mantine/hooks'; import { ModalsProvider } from '@mantine/modals'; +import Consola from 'consola'; import { NotificationsProvider } from '@mantine/notifications'; import { QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { getCookie } from 'cookies-next'; import { GetServerSidePropsContext } from 'next'; import { appWithTranslation } from 'next-i18next'; import { AppProps } from 'next/app'; import Head from 'next/head'; import { useEffect, useState } from 'react'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import 'video.js/dist/video-js.css'; import { ChangeAppPositionModal } from '../components/Dashboard/Modals/ChangePosition/ChangeAppPositionModal'; import { ChangeWidgetPositionModal } from '../components/Dashboard/Modals/ChangePosition/ChangeWidgetPositionModal'; import { EditAppModal } from '../components/Dashboard/Modals/EditAppModal/EditAppModal'; @@ -18,21 +20,25 @@ import { WidgetsEditModal } from '../components/Dashboard/Tiles/Widgets/WidgetsE import { WidgetsRemoveModal } from '../components/Dashboard/Tiles/Widgets/WidgetsRemoveModal'; import { CategoryEditModal } from '../components/Dashboard/Wrappers/Category/CategoryEditModal'; import { ConfigProvider } from '../config/provider'; +import { usePackageAttributesStore } from '../tools/client/zustands/usePackageAttributesStore'; import { ColorTheme } from '../tools/color'; import { queryClient } from '../tools/queryClient'; -import { theme } from '../tools/theme'; import { getServiceSidePackageAttributes, ServerSidePackageAttributesType, } from '../tools/server/getPackageVersion'; -import { usePackageAttributesStore } from '../tools/client/zustands/usePackageAttributesStore'; -import 'video.js/dist/video-js.css'; +import { theme } from '../tools/theme'; +import { useEditModeInformationStore } from '../hooks/useEditModeInformation'; import '../styles/global.scss'; function App( this: any, - props: AppProps & { colorScheme: ColorScheme; packageAttributes: ServerSidePackageAttributesType } + props: AppProps & { + colorScheme: ColorScheme; + packageAttributes: ServerSidePackageAttributesType; + editModeEnabled: boolean; + } ) { const { Component, pageProps } = props; const [primaryColor, setPrimaryColor] = useState('red'); @@ -57,9 +63,14 @@ function App( }); const { setInitialPackageAttributes } = usePackageAttributesStore(); + const { setDisabled } = useEditModeInformationStore(); useEffect(() => { setInitialPackageAttributes(props.packageAttributes); + + if (!props.editModeEnabled) { + setDisabled(); + } }, []); const toggleColorScheme = (value?: ColorScheme) => @@ -125,9 +136,19 @@ function App( ); } -App.getInitialProps = ({ ctx }: { ctx: GetServerSidePropsContext }) => ({ - colorScheme: getCookie('color-scheme', ctx) || 'light', - packageAttributes: getServiceSidePackageAttributes(), -}); +App.getInitialProps = ({ ctx }: { ctx: GetServerSidePropsContext }) => { + const disableEditMode = + process.env.DISABLE_EDIT_MODE && process.env.DISABLE_EDIT_MODE.toLowerCase() === 'true'; + if (disableEditMode) { + Consola.warn( + 'EXPERIMENTAL: You have disabled the edit mode. Modifications are no longer possible and any requests on the API will be dropped. If you want to disable this, unset the DISABLE_EDIT_MODE environment variable. This behaviour may be removed in future versions of Homarr' + ); + } + return { + colorScheme: getCookie('color-scheme', ctx) || 'light', + packageAttributes: getServiceSidePackageAttributes(), + editModeEnabled: !disableEditMode, + }; +}; export default appWithTranslation(App); diff --git a/src/pages/api/configs/[slug].ts b/src/pages/api/configs/[slug].ts index 414c74c5d..7050413f6 100644 --- a/src/pages/api/configs/[slug].ts +++ b/src/pages/api/configs/[slug].ts @@ -1,13 +1,22 @@ import fs from 'fs'; + import path from 'path'; + import Consola from 'consola'; + import { NextApiRequest, NextApiResponse } from 'next'; + import { BackendConfigType, ConfigType } from '../../../types/config'; import { getConfig } from '../../../tools/config/getConfig'; function Put(req: NextApiRequest, res: NextApiResponse) { + if (process.env.DISABLE_EDIT_MODE === 'true') { + return res.status(409).json({ error: 'Edit mode has been disabled by the administrator' }); + } + // Get the slug of the request const { slug } = req.query as { slug: string }; + // Get the body of the request const { body: config }: { body: ConfigType } = req; if (!slug || !config) {