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 (
+
+ );
+}
+
+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',
+ });
+};