🔀 Merge branch 'dev' into tests/add-tests

This commit is contained in:
Manuel
2023-03-29 12:17:20 +02:00
50 changed files with 1035 additions and 910 deletions

View File

@@ -57,7 +57,12 @@ export default function ConfigChanger() {
size="lg"
radius="md"
>
<Notification loading title={t('configSelect.loadingNew')} radius="md" disallowClose>
<Notification
loading
title={t('configSelect.loadingNew')}
radius="md"
withCloseButton={false}
>
{t('configSelect.pleaseWait')}
</Notification>
</Dialog>

View File

@@ -1,18 +1,19 @@
import {
Accordion,
ActionIcon,
Anchor,
Badge,
Button,
createStyles,
Divider,
Grid,
Group,
HoverCard,
Kbd,
Modal,
Stack,
Table,
Text,
Title,
Tooltip,
} from '@mantine/core';
import {
IconAnchor,
@@ -35,6 +36,8 @@ 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 Tip from '../../../layout/Tip';
import { usePrimaryGradient } from '../../../layout/useGradient';
import Credits from '../../../Settings/Common/Credits';
@@ -50,6 +53,23 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod
const informations = useInformationTableItems(newVersionAvailable);
const { t } = useTranslation(['common', 'layout/modals/about']);
const keybinds = [
{ key: 'Mod + J', shortcut: 'Toggle light/dark mode' },
{ key: 'Mod + K', shortcut: 'Focus on search bar' },
{ key: 'Mod + B', shortcut: 'Open docker widget' },
{ key: 'Mod + E', shortcut: 'Toggle Edit mode' },
];
const rows = keybinds.map((element) => (
<tr key={element.key}>
<td>
<Kbd>{element.key}</Kbd>
</td>
<td>
<Text>{element.shortcut}</Text>
</td>
</tr>
));
return (
<Modal
onClose={() => closeModal()}
@@ -76,7 +96,7 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod
<Trans i18nKey="layout/modals/about:description" />
</Text>
<Table mb="lg" striped highlightOnHover withBorder>
<Table mb="lg" highlightOnHover withBorder>
<tbody>
{informations.map((item, index) => (
<tr key={index}>
@@ -100,8 +120,26 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod
))}
</tbody>
</Table>
<Accordion mb={5} variant="contained" radius="md">
<Accordion.Item value="keybinds">
<Accordion.Control icon={<IconKey size={20} />}>
{t('layout/modals/about:keybinds')}
</Accordion.Control>
<Accordion.Panel>
<Table mb={5}>
<thead>
<tr>
<th>{t('layout/modals/about:key')}</th>
<th>{t('layout/modals/about:action')}</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</Table>
<Tip>{t('layout/modals/about:tip')}</Tip>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
<Divider variant="dashed" mb="md" />
<Title order={6} mb="xs" align="center">
{t('layout/modals/about:contact')}
</Title>
@@ -161,9 +199,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 +215,19 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
icon: <IconKey size={20} />,
label: 'experimental_disableEditMode',
content: (
<Stack>
<Tooltip
color="red"
withinPortal
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
on the API. This will be removed in future versions, as Homarr will receive a proper
authentication system, which will make this obsolete."
>
<Badge color="red">WARNING</Badge>
<Text color="red" size="xs">
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.
</Text>
</Stack>
</Tooltip>
),
},
];
@@ -201,7 +243,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
icon: <IconLanguage size={20} />,
label: 'i18n',
content: (
<Badge variant="gradient" gradient={colorGradiant}>
<Badge variant="light" color={primaryColor}>
{usedI18nNamespaces.length}
</Badge>
),
@@ -210,7 +252,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
icon: <IconVocabulary size={20} />,
label: 'locales',
content: (
<Badge variant="gradient" gradient={colorGradiant}>
<Badge variant="light" color={primaryColor}>
{initOptions.locales.length}
</Badge>
),
@@ -223,7 +265,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
icon: <IconSchema size={20} />,
label: 'configurationSchemaVersion',
content: (
<Badge variant="gradient" gradient={colorGradiant}>
<Badge variant="light" color={primaryColor}>
{configVersion}
</Badge>
),
@@ -232,7 +274,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
icon: <IconFile size={20} />,
label: 'configurationsCount',
content: (
<Badge variant="gradient" gradient={colorGradiant}>
<Badge variant="light" color={primaryColor}>
{configs.length}
</Badge>
),
@@ -242,7 +284,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
label: 'version',
content: (
<Group position="right">
<Badge variant="gradient" gradient={colorGradiant}>
<Badge variant="light" color={primaryColor}>
{attributes.packageVersion ?? 'Unknown'}
</Badge>
{newVersionAvailable && (
@@ -282,7 +324,7 @@ const useInformationTableItems = (newVersionAvailable?: string): InformationTabl
icon: <IconAnchor size={20} />,
label: 'nodeEnvironment',
content: (
<Badge variant="gradient" gradient={colorGradiant}>
<Badge variant="light" color={primaryColor}>
{attributes.environment}
</Badge>
),

View File

@@ -6,7 +6,6 @@ import {
Grid,
Group,
PasswordInput,
Stack,
ThemeIcon,
Title,
Text,
@@ -40,7 +39,7 @@ export const GenericSecretInput = ({
const Icon = setIcon;
const [displayUpdateField, setDisplayUpdateField] = useState<boolean>(false);
const [displayUpdateField, setDisplayUpdateField] = useState<boolean>(!secretIsPresent);
const { t } = useTranslation(['layout/modals/add-app', 'common']);
return (
@@ -51,26 +50,26 @@ export const GenericSecretInput = ({
<ThemeIcon color={secretIsPresent ? 'green' : 'red'} variant="light" size="lg">
<Icon size={18} />
</ThemeIcon>
<Stack spacing={0}>
<Flex justify="start" align="start" direction="column">
<Group spacing="xs">
<Title className={classes.subtitle} order={6}>
{t(label)}
</Title>
<Group spacing="xs">
{secretIsPresent ? (
<Badge className={classes.textTransformUnset} color="green" variant="dot">
{t('integration.type.defined')}
</Badge>
) : (
<Badge className={classes.textTransformUnset} color="red" variant="dot">
{t('integration.type.undefined')}
</Badge>
)}
<Badge
className={classes.textTransformUnset}
color={secretIsPresent ? 'green' : 'red'}
variant="dot"
>
{secretIsPresent
? t('integration.type.defined')
: t('integration.type.undefined')}
</Badge>
{type === 'private' ? (
<Tooltip
label={t('integration.type.explanationPrivate')}
width={200}
width={400}
multiline
withinPortal
withArrow
@@ -82,7 +81,7 @@ export const GenericSecretInput = ({
) : (
<Tooltip
label={t('integration.type.explanationPublic')}
width={200}
width={400}
multiline
withinPortal
withArrow
@@ -94,29 +93,20 @@ export const GenericSecretInput = ({
)}
</Group>
</Group>
<Text size="xs" color="dimmed">
<Text size="xs" color="dimmed" w={400}>
{type === 'private'
? 'Private: Once saved, you cannot read out this value again'
: 'Public: Can be read out repeatedly'}
</Text>
</Stack>
</Flex>
</Group>
</Grid.Col>
<Grid.Col xs={12} md={6}>
<Flex gap={10} justify="end" align="end">
<Button
onClick={() => {
setDisplayUpdateField(false);
onClickUpdateButton(undefined);
}}
variant="subtle"
color="gray"
px="xl"
>
{t('integration.secrets.clear')}
</Button>
{displayUpdateField === true ? (
<PasswordInput
required
defaultValue={value}
placeholder="new secret"
styles={{ root: { width: 200 } }}
{...props}

View File

@@ -29,7 +29,7 @@ export const GenericAvailableElementType = ({
: image;
return (
<Grid.Col span={3}>
<Grid.Col span="auto">
<Card style={{ height: '100%' }}>
<Stack justify="space-between" style={{ height: '100%' }}>
<Stack spacing="xs">

View File

@@ -19,7 +19,7 @@ export const AvailableStaticTypes = ({ onClickBack }: AvailableStaticTypesProps)
they don&apos;t integrate with any apps and their content never changes.
</Text>
<Grid>
<Grid grow>
<GenericAvailableElementType
name="Static Text"
description="Display a fixed string on your dashboard"

View File

@@ -93,12 +93,10 @@ export const AppTile = ({ className, app }: AppTileProps) => {
const useStyles = createStyles((theme, _params, getRef) => ({
image: {
ref: getRef('image'),
maxHeight: '90%',
maxWidth: '90%',
},
appName: {
ref: getRef('appName'),
wordBreak: 'break-word',
},
button: {

View File

@@ -18,12 +18,11 @@ interface GridstackStoreType {
export const useNamedWrapperColumnCount = (): 'small' | 'medium' | 'large' | null => {
const mainAreaWidth = useGridstackStore((x) => x.mainAreaWidth);
const { sm, xl } = useMantineTheme().breakpoints;
if (!mainAreaWidth) return null;
if (mainAreaWidth >= xl) return 'large';
if (mainAreaWidth >= 1400) return 'large';
if (mainAreaWidth >= sm) return 'medium';
if (mainAreaWidth >= 800) return 'medium';
return 'small';
};

View File

@@ -18,7 +18,7 @@ export default function CommonSettings() {
);
}
return (
<ScrollArea style={{ height: height - 100 }} offsetScrollbars>
<ScrollArea style={{ height: height - 100 }} scrollbarSize={5}>
<Stack>
<SearchEngineSelector searchEngine={config.settings.common.searchEngine} />
<Space />

View File

@@ -60,6 +60,7 @@ export const SearchEngineSelector = ({ searchEngine }: Props) => {
<Space mb="md" />
<TextInput
label={t('customEngine.label')}
name={t('configurationName')}
description={t('tips.placeholderTip')}
placeholder={t('customEngine.placeholder')}
value={searchUrl}

View File

@@ -8,7 +8,7 @@ export default function CustomizationSettings() {
const { t } = useTranslation('settings/customization/general');
return (
<ScrollArea style={{ height: height - 100 }} offsetScrollbars>
<ScrollArea style={{ height: height - 100 }} scrollbarSize={5}>
<Stack mt="xs" mb="md" spacing="xs">
<Text color="dimmed">{t('text')}</Text>
<CustomizationSettingsAccordeon />

View File

@@ -41,7 +41,7 @@ export function SettingsDrawer({
return (
<Drawer
size="xl"
size="lg"
padding="lg"
position="right"
title={<Title order={5}>{t('title')}</Title>}

View File

@@ -29,7 +29,7 @@ export const ToggleEditModeAction = () => {
const { config } = useConfigContext();
const { classes } = useCardStyles(true);
useHotkeys([['ctrl+E', toggleEditMode]]);
useHotkeys([['mod+E', toggleEditMode]]);
useWindowEvent('beforeunload', (event: BeforeUnloadEvent) => {
if (enabled) {

View File

@@ -42,7 +42,7 @@ export function Header(props: any) {
>
<Search />
{!editModeEnabled && <ToggleEditModeAction />}
<DockerMenuButton />
{!editModeEnabled && <DockerMenuButton />}
<Indicator
size={15}
color="blue"

View File

@@ -181,7 +181,6 @@ export function Search() {
shadow="md"
radius="md"
zIndex={100}
transition="pop-top-right"
>
<Popover.Target>
<Autocomplete
@@ -297,7 +296,7 @@ export function Search() {
setSearchEngine(item);
showNotification({
radius: 'lg',
disallowClose: true,
withCloseButton: false,
id: 'spotlight',
autoClose: 1000,
icon: <ActionIcon size="sm">{item.icon}</ActionIcon>,

View File

@@ -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
</Menu.Target>
<Menu.Dropdown>
<ColorSchemeSwitch />
<EditModeToggle />
<Menu.Divider />
{!editModeEnabled && (
<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/tryPassword', { tried: values.triedPassword, type: 'edit' })
.then((res) => {
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',
});
});
})}
>
<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> . If it is not set, you are not
able to toggle edit mode on and off.
</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>
);
}