From 0b7f407b8c30cbded65f7a165385db5d793ea0b9 Mon Sep 17 00:00:00 2001 From: ajnart Date: Thu, 2 Mar 2023 16:40:50 +0900 Subject: [PATCH 01/12] =?UTF-8?q?=E2=9C=A8=20Add=20feature=20for=20edit=20?= =?UTF-8?q?mode=20password?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 + .../Modals/AboutModal/AboutModal.tsx | 37 +++++---- src/components/layout/header/SettingsMenu.tsx | 2 + .../header/SettingsMenu/EditModeToggle.tsx | 78 +++++++++++++++++++ src/modules/Docker/DockerModule.tsx | 2 +- src/pages/api/configs/tryToggleEdit.tsx | 34 ++++++++ 6 files changed, 138 insertions(+), 17 deletions(-) create mode 100644 .env create mode 100644 src/components/layout/header/SettingsMenu/EditModeToggle.tsx create mode 100644 src/pages/api/configs/tryToggleEdit.tsx 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', + }); +}; From 8850e3a0278738ab09ae1fff27c5804547fc02c1 Mon Sep 17 00:00:00 2001 From: ajnart Date: Thu, 2 Mar 2023 16:41:27 +0900 Subject: [PATCH 02/12] =?UTF-8?q?=F0=9F=94=A5=20remove=20.env=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 611ce2a59..000000000 --- a/.env +++ /dev/null @@ -1,2 +0,0 @@ -DISABLE_EDIT_MODE=TRUE -EDIT_MODE_PASSWORD='edit' \ No newline at end of file From e734af0109303f6b55885f4cf7d7b1264665c689 Mon Sep 17 00:00:00 2001 From: ajnart Date: Thu, 2 Mar 2023 16:43:54 +0900 Subject: [PATCH 03/12] =?UTF-8?q?=F0=9F=94=A5=20Remove=20logs=20and=20reph?= =?UTF-8?q?rase=20modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../layout/header/SettingsMenu/EditModeToggle.tsx | 7 +++---- src/pages/api/configs/tryToggleEdit.tsx | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/layout/header/SettingsMenu/EditModeToggle.tsx b/src/components/layout/header/SettingsMenu/EditModeToggle.tsx index 083e889c9..5d4fda871 100644 --- a/src/components/layout/header/SettingsMenu/EditModeToggle.tsx +++ b/src/components/layout/header/SettingsMenu/EditModeToggle.tsx @@ -38,7 +38,8 @@ function ModalContent() { In order to toggle edit mode, you need to enter the password you entered in the - environment variable named EDIT_MODE_PASSWORD + environment variable named EDIT_MODE_PASSWORD . If it is not set, you are not + able to toggle edit mode on and off. - + ); diff --git a/src/pages/api/configs/tryToggleEdit.tsx b/src/pages/api/configs/tryToggleEdit.tsx index 406babdce..45f22e8f2 100644 --- a/src/pages/api/configs/tryToggleEdit.tsx +++ b/src/pages/api/configs/tryToggleEdit.tsx @@ -4,7 +4,6 @@ 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({ From f906214fa93c1489ae4c2ceb0eb50ddaeff331f5 Mon Sep 17 00:00:00 2001 From: ajnart Date: Thu, 2 Mar 2023 16:53:52 +0900 Subject: [PATCH 04/12] Try adding a vercel config file --- vercel.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 vercel.json diff --git a/vercel.json b/vercel.json new file mode 100644 index 000000000..77cfe5754 --- /dev/null +++ b/vercel.json @@ -0,0 +1,6 @@ +{ + "env": { + "EDIT_MODE_PASSWORD": "edit", + "DISABLE_EDIT_MODE": "TRUE" + } +} \ No newline at end of file From 46938db8f3f36ac00a205699821b94e0d4f70618 Mon Sep 17 00:00:00 2001 From: ajnart Date: Thu, 2 Mar 2023 20:04:25 +0900 Subject: [PATCH 05/12] Show the toggle conditionally The toggle button should only show if the environment variable is defined --- .env | 1 + src/components/layout/header/SettingsMenu.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 000000000..c22045117 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DISABLE_EDIT_MODE=TRUE diff --git a/src/components/layout/header/SettingsMenu.tsx b/src/components/layout/header/SettingsMenu.tsx index 91d04a2fd..8e44d0e2e 100644 --- a/src/components/layout/header/SettingsMenu.tsx +++ b/src/components/layout/header/SettingsMenu.tsx @@ -26,7 +26,7 @@ export function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: str - + {process.env.EDIT_MODE_PASSWORD !== undefined && } {!editModeEnabled && ( } onClick={drawer.open}> From 402f05f265a0df1d45c08a4dc3978ce69f9f04a5 Mon Sep 17 00:00:00 2001 From: ajnart Date: Thu, 2 Mar 2023 20:04:58 +0900 Subject: [PATCH 06/12] =?UTF-8?q?=F0=9F=94=A5=20Delete=20.env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index c22045117..000000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -DISABLE_EDIT_MODE=TRUE From ab212e36d0b7d73990f7905a512ccf72c9d1e169 Mon Sep 17 00:00:00 2001 From: ajnart Date: Thu, 2 Mar 2023 20:05:27 +0900 Subject: [PATCH 07/12] =?UTF-8?q?=F0=9F=99=88=20add=20.env=20file=20to=20g?= =?UTF-8?q?itignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 92b9ea013..1b7d1e587 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,7 @@ data/configs !.yarn/plugins !.yarn/releases !.yarn/sdks -!.yarn/versions \ No newline at end of file +!.yarn/versions + +#envfiles +.env \ No newline at end of file From caa625c3ec82765b6666197a47c98238d31ed5b2 Mon Sep 17 00:00:00 2001 From: ajnart Date: Thu, 2 Mar 2023 20:44:52 +0900 Subject: [PATCH 08/12] Disable show the toggle conditionally --- src/components/layout/header/SettingsMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/layout/header/SettingsMenu.tsx b/src/components/layout/header/SettingsMenu.tsx index 8e44d0e2e..91d04a2fd 100644 --- a/src/components/layout/header/SettingsMenu.tsx +++ b/src/components/layout/header/SettingsMenu.tsx @@ -26,7 +26,7 @@ export function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: str - {process.env.EDIT_MODE_PASSWORD !== undefined && } + {!editModeEnabled && ( } onClick={drawer.open}> From 13670c56267599ecb0e798bc62f03a8fdf733a9d Mon Sep 17 00:00:00 2001 From: ajnart Date: Tue, 21 Mar 2023 11:35:07 +0800 Subject: [PATCH 09/12] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Addre?= =?UTF-8?q?ss=20PR=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/api/configs/tryPassword.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/pages/api/configs/tryPassword.tsx b/src/pages/api/configs/tryPassword.tsx index 381845abf..1632985ab 100644 --- a/src/pages/api/configs/tryPassword.tsx +++ b/src/pages/api/configs/tryPassword.tsx @@ -2,26 +2,31 @@ 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 PASSWORD env variable - if (tried === process.env.PASSWORD) { + const { tried, type = 'password' } = req.body; + // If the type of password is "edit", we run this branch to check the edit password + if (type === 'edit') { + 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, + }); + } + } else if (tried === process.env.PASSWORD) { 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 password attempt, from ${ req.headers['x-forwarded-for'] }` ); - return res.status(200).json({ + return res.status(401).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); } From b923f8261bed913645219a1da11dc72522efc02e Mon Sep 17 00:00:00 2001 From: ajnart Date: Tue, 21 Mar 2023 11:36:34 +0800 Subject: [PATCH 10/12] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Addre?= =?UTF-8?q?ss=20PR=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../header/SettingsMenu/EditModeToggle.tsx | 38 ++++++++++--------- src/pages/api/configs/tryToggleEdit.tsx | 33 ---------------- 2 files changed, 20 insertions(+), 51 deletions(-) delete mode 100644 src/pages/api/configs/tryToggleEdit.tsx diff --git a/src/components/layout/header/SettingsMenu/EditModeToggle.tsx b/src/components/layout/header/SettingsMenu/EditModeToggle.tsx index 5d4fda871..e27c48139 100644 --- a/src/components/layout/header/SettingsMenu/EditModeToggle.tsx +++ b/src/components/layout/header/SettingsMenu/EditModeToggle.tsx @@ -15,24 +15,26 @@ function ModalContent() { 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', - }); - } - }); + axios + .post('/api/configs/tryPassword', { tried: values.triedPassword, type: 'edit' }) + .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', + }); + } + }); })} > diff --git a/src/pages/api/configs/tryToggleEdit.tsx b/src/pages/api/configs/tryToggleEdit.tsx deleted file mode 100644 index 45f22e8f2..000000000 --- a/src/pages/api/configs/tryToggleEdit.tsx +++ /dev/null @@ -1,33 +0,0 @@ -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 - 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', - }); -}; From c507a8892ff66412f95ba3ceeafbea0b7c659e89 Mon Sep 17 00:00:00 2001 From: ajnart Date: Tue, 21 Mar 2023 11:39:34 +0800 Subject: [PATCH 11/12] =?UTF-8?q?=F0=9F=90=9B=20Fix=20bug=20with=20notific?= =?UTF-8?q?ation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../header/SettingsMenu/EditModeToggle.tsx | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/components/layout/header/SettingsMenu/EditModeToggle.tsx b/src/components/layout/header/SettingsMenu/EditModeToggle.tsx index e27c48139..fed323e8c 100644 --- a/src/components/layout/header/SettingsMenu/EditModeToggle.tsx +++ b/src/components/layout/header/SettingsMenu/EditModeToggle.tsx @@ -18,22 +18,21 @@ function ModalContent() { axios .post('/api/configs/tryPassword', { tried: values.triedPassword, type: 'edit' }) .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', - }); - } + showNotification({ + title: 'Success', + message: 'Successfully toggled edit mode, reloading the page...', + color: 'green', + }); + setTimeout(() => { + window.location.reload(); + }, 500); + }) + .catch((_) => { + showNotification({ + title: 'Error', + message: 'Failed to toggle edit mode, please try again.', + color: 'red', + }); }); })} > From 94f13b805ce5b73a0fbc0dfbe5e458d11d4245f9 Mon Sep 17 00:00:00 2001 From: ajnart Date: Tue, 21 Mar 2023 11:40:33 +0800 Subject: [PATCH 12/12] =?UTF-8?q?=F0=9F=90=9B=20Hide=20docker=20when=20edi?= =?UTF-8?q?t=20mode=20is=20not=20enabled=20#745?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/layout/header/Header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/layout/header/Header.tsx b/src/components/layout/header/Header.tsx index 44bb12b51..5eeadc149 100644 --- a/src/components/layout/header/Header.tsx +++ b/src/components/layout/header/Header.tsx @@ -42,7 +42,7 @@ export function Header(props: any) { > {!editModeEnabled && } - + {!editModeEnabled && }