Add feature for edit mode password

This commit is contained in:
ajnart
2023-03-02 16:40:50 +09:00
parent 05423440f3
commit 0b7f407b8c
6 changed files with 138 additions and 17 deletions

2
.env Normal file
View File

@@ -0,0 +1,2 @@
DISABLE_EDIT_MODE=TRUE
EDIT_MODE_PASSWORD='edit'

View File

@@ -9,10 +9,10 @@ import {
Group, Group,
HoverCard, HoverCard,
Modal, Modal,
Stack,
Table, Table,
Text, Text,
Title, Title,
Tooltip,
} from '@mantine/core'; } from '@mantine/core';
import { import {
IconAnchor, IconAnchor,
@@ -35,6 +35,7 @@ import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../../config/store'; import { useConfigStore } from '../../../../config/store';
import { useEditModeInformationStore } from '../../../../hooks/useEditModeInformation'; import { useEditModeInformationStore } from '../../../../hooks/useEditModeInformation';
import { usePackageAttributesStore } from '../../../../tools/client/zustands/usePackageAttributesStore'; import { usePackageAttributesStore } from '../../../../tools/client/zustands/usePackageAttributesStore';
import { useColorTheme } from '../../../../tools/color';
import { usePrimaryGradient } from '../../../layout/useGradient'; import { usePrimaryGradient } from '../../../layout/useGradient';
import Credits from '../../../Settings/Common/Credits'; import Credits from '../../../Settings/Common/Credits';
@@ -161,9 +162,9 @@ interface ExtendedInitOptions extends InitOptions {
} }
const useInformationTableItems = (newVersionAvailable?: string): InformationTableItem[] => { const useInformationTableItems = (newVersionAvailable?: string): InformationTableItem[] => {
const colorGradiant = usePrimaryGradient();
const { attributes } = usePackageAttributesStore(); const { attributes } = usePackageAttributesStore();
const { editModeEnabled } = useEditModeInformationStore(); const { editModeEnabled } = useEditModeInformationStore();
const { primaryColor } = useColorTheme();
const { configVersion } = useConfigContext(); const { configVersion } = useConfigContext();
const { configs } = useConfigStore(); const { configs } = useConfigStore();
@@ -177,15 +178,19 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
icon: <IconKey size={20} />, icon: <IconKey size={20} />,
label: 'experimental_disableEditMode', label: 'experimental_disableEditMode',
content: ( content: (
<Stack> <Tooltip
<Badge color="red">WARNING</Badge> color="red"
<Text color="red" size="xs"> withinPortal
This is an experimental feature, where the edit mode is disabled entirely - no config width={300}
multiline
withArrow
label="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 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 on the API. This will be removed in future versions, as Homarr will receive a proper
authentication system, which will make this obsolete. authentication system, which will make this obsolete."
</Text> >
</Stack> <Badge color="red">WARNING</Badge>
</Tooltip>
), ),
}, },
]; ];
@@ -201,7 +206,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
icon: <IconLanguage size={20} />, icon: <IconLanguage size={20} />,
label: 'i18n', label: 'i18n',
content: ( content: (
<Badge variant="gradient" gradient={colorGradiant}> <Badge variant="light" color={primaryColor}>
{usedI18nNamespaces.length} {usedI18nNamespaces.length}
</Badge> </Badge>
), ),
@@ -210,7 +215,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
icon: <IconVocabulary size={20} />, icon: <IconVocabulary size={20} />,
label: 'locales', label: 'locales',
content: ( content: (
<Badge variant="gradient" gradient={colorGradiant}> <Badge variant="light" color={primaryColor}>
{initOptions.locales.length} {initOptions.locales.length}
</Badge> </Badge>
), ),
@@ -223,7 +228,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
icon: <IconSchema size={20} />, icon: <IconSchema size={20} />,
label: 'configurationSchemaVersion', label: 'configurationSchemaVersion',
content: ( content: (
<Badge variant="gradient" gradient={colorGradiant}> <Badge variant="light" color={primaryColor}>
{configVersion} {configVersion}
</Badge> </Badge>
), ),
@@ -232,7 +237,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
icon: <IconFile size={20} />, icon: <IconFile size={20} />,
label: 'configurationsCount', label: 'configurationsCount',
content: ( content: (
<Badge variant="gradient" gradient={colorGradiant}> <Badge variant="light" color={primaryColor}>
{configs.length} {configs.length}
</Badge> </Badge>
), ),
@@ -242,7 +247,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
label: 'version', label: 'version',
content: ( content: (
<Group position="right"> <Group position="right">
<Badge variant="gradient" gradient={colorGradiant}> <Badge variant="light" color={primaryColor}>
{attributes.packageVersion ?? 'Unknown'} {attributes.packageVersion ?? 'Unknown'}
</Badge> </Badge>
{newVersionAvailable && ( {newVersionAvailable && (
@@ -282,7 +287,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
icon: <IconAnchor size={20} />, icon: <IconAnchor size={20} />,
label: 'nodeEnvironment', label: 'nodeEnvironment',
content: ( content: (
<Badge variant="gradient" gradient={colorGradiant}> <Badge variant="light" color={primaryColor}>
{attributes.environment} {attributes.environment}
</Badge> </Badge>
), ),

View File

@@ -7,6 +7,7 @@ import { AboutModal } from '../../Dashboard/Modals/AboutModal/AboutModal';
import { SettingsDrawer } from '../../Settings/SettingsDrawer'; import { SettingsDrawer } from '../../Settings/SettingsDrawer';
import { useCardStyles } from '../useCardStyles'; import { useCardStyles } from '../useCardStyles';
import { ColorSchemeSwitch } from './SettingsMenu/ColorSchemeSwitch'; import { ColorSchemeSwitch } from './SettingsMenu/ColorSchemeSwitch';
import { EditModeToggle } from './SettingsMenu/EditModeToggle';
export function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string }) { export function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string }) {
const [drawerOpened, drawer] = useDisclosure(false); const [drawerOpened, drawer] = useDisclosure(false);
@@ -25,6 +26,7 @@ export function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: str
</Menu.Target> </Menu.Target>
<Menu.Dropdown> <Menu.Dropdown>
<ColorSchemeSwitch /> <ColorSchemeSwitch />
<EditModeToggle />
<Menu.Divider /> <Menu.Divider />
{!editModeEnabled && ( {!editModeEnabled && (
<Menu.Item icon={<IconSettings strokeWidth={1.2} size={18} />} onClick={drawer.open}> <Menu.Item icon={<IconSettings strokeWidth={1.2} size={18} />} onClick={drawer.open}>

View File

@@ -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 (
<form
onSubmit={form.onSubmit((values) => {
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',
});
}
});
})}
>
<Stack>
<Text size="sm">
In order to toggle edit mode, you need to enter the password you entered in the
environment variable named <Code>EDIT_MODE_PASSWORD</Code>
</Text>
<PasswordInput
id="triedPassword"
label="Edit password"
autoFocus
required
{...form.getInputProps('triedPassword')}
/>
<Button type="submit">
Submit
</Button>
</Stack>
</form>
);
}
export function EditModeToggle() {
const { editModeEnabled } = useEditModeInformationStore();
const Icon = editModeEnabled ? IconEdit : IconEditOff;
return (
<Menu.Item
closeMenuOnClick={false}
icon={<Icon strokeWidth={1.2} size={18} />}
onClick={() =>
openModal({
title: 'Toggle edit mode',
centered: true,
size: 'lg',
children: <ModalContent />,
})
}
>
{editModeEnabled ? 'Enable edit mode' : 'Disable edit mode'}
</Menu.Item>
);
}

View File

@@ -54,7 +54,7 @@ export default function DockerMenuButton(props: any) {
}, 300); }, 300);
} }
if (!dockerEnabled) { if (!dockerEnabled || process.env.DISABLE_EDIT_MODE === 'true') {
return null; return null;
} }

View File

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