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) {