2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-14 19:15:41 +00:00

refactor(frontend): seperate Alerts into own component (#9442)

* refactor(frontend): seperate Alerts into own component

* refactor debug mode info

* add error code for debug mode and render help links in alerts

* add error message for background worker

* add error code for server restart required

* Add error code for missing email settings

* fix various typings

* add error code for missing migrations
This commit is contained in:
Matthias Mair
2025-04-03 09:03:07 +02:00
committed by GitHub
parent c4f98cd6a1
commit b48ceb00f2
10 changed files with 185 additions and 102 deletions

View File

@ -30,15 +30,15 @@ def is_email_configured():
# Display warning unless in test mode
if not testing: # pragma: no cover
logger.debug('EMAIL_HOST is not configured')
logger.debug('INVE-W7: EMAIL_HOST is not configured')
# Display warning unless in test mode
if not settings.EMAIL_HOST_USER and not testing: # pragma: no cover
logger.debug('EMAIL_HOST_USER is not configured')
logger.debug('INVE-W7: EMAIL_HOST_USER is not configured')
# Display warning unless in test mode
if not settings.EMAIL_HOST_PASSWORD and testing: # pragma: no cover
logger.debug('EMAIL_HOST_PASSWORD is not configured')
logger.debug('INVE-W7: EMAIL_HOST_PASSWORD is not configured')
# Email sender must be configured
if not settings.DEFAULT_FROM_EMAIL:
@ -64,6 +64,7 @@ def send_email(subject, body, recipients, from_email=None, html_message=None):
if not is_email_configured() and not settings.TESTING:
# Email is not configured / enabled
logger.info('INVE-W7: Email will not be send, no mail server configured')
return
# If a *from_email* is not specified, ensure that the default is set

View File

@ -67,7 +67,7 @@ def check_system_health(**kwargs):
if not InvenTree.helpers_email.is_email_configured(): # pragma: no cover
result = False
if not settings.DEBUG:
logger.warning('Email backend not configured')
logger.warning('INVE-W7: Email backend not configured')
if not result: # pragma: no cover
if not settings.DEBUG:

View File

@ -87,7 +87,7 @@ class InvenTreeTaskTests(TestCase):
):
InvenTree.tasks.offload_task('InvenTree.test_tasks.doesnotexist')
def test_task_hearbeat(self):
def test_task_heartbeat(self):
"""Test the task heartbeat."""
InvenTree.tasks.offload_task(InvenTree.tasks.heartbeat)

View File

@ -52,6 +52,7 @@ export function ServerInfoModal({
<Trans>Debug Mode</Trans>
</Table.Td>
<Table.Td>
<Badge color='red'>INVE-W4</Badge>
<Trans>Server is running in debug mode</Trans>
</Table.Td>
</Table.Tr>
@ -102,21 +103,19 @@ export function ServerInfoModal({
<Trans>Background Worker</Trans>
</Table.Td>
<Table.Td>
<Badge color='red'>
<Trans>Background worker not running</Trans>
</Badge>
<Badge color='red'>INVE-W5</Badge>
<Trans>The Background worker process is not running.</Trans>
</Table.Td>
</Table.Tr>
)}
{server?.email_configured == false && (
{!server?.email_configured && (
<Table.Tr>
<Table.Td>
<Trans>Email Settings</Trans>
</Table.Td>
<Table.Td>
<Badge color='red'>
<Trans>Email settings not configured</Trans>
</Badge>
<Badge color='red'>INVE-W7</Badge>
<Trans>Email settings not configured.</Trans>
</Table.Td>
</Table.Tr>
)}

View File

@ -0,0 +1,130 @@
import { ActionIcon, Alert, Menu, Tooltip } from '@mantine/core';
import { IconExclamationCircle } from '@tabler/icons-react';
import { useMemo, useState } from 'react';
import { t } from '@lingui/core/macro';
import { docLinks } from '../../defaults/links';
import { useServerApiState } from '../../states/ApiState';
import { useGlobalSettingsState } from '../../states/SettingsState';
import { useUserState } from '../../states/UserState';
interface AlertInfo {
key: string;
title: string;
code?: string;
message: string;
}
/**
* The `Alerts` component displays a menu of alerts for staff users based on the server state
* and global settings. Alerts are shown as a dropdown menu with actionable items that can be dismissed.
*
* Dismissed alerts are filtered out and will not reappear in the current session.
*
* @returns A dropdown menu of alerts for staff users or `null` if there are no alerts or the user is not a staff member.
*/
export function Alerts() {
const user = useUserState();
const [server] = useServerApiState((state) => [state.server]);
const globalSettings = useGlobalSettingsState();
const [dismissed, setDismissed] = useState<string[]>([]);
const alerts: AlertInfo[] = useMemo(() => {
const _alerts: AlertInfo[] = [];
if (server?.debug_mode) {
_alerts.push({
key: 'debug',
title: t`Debug Mode`,
code: 'INVE-W4',
message: t`The server is running in debug mode.`
});
}
if (!server?.worker_running) {
_alerts.push({
key: 'worker',
title: t`Background Worker`,
code: 'INVE-W5',
message: t`The background worker process is not running.`
});
}
if (!server?.email_configured) {
_alerts.push({
key: 'email',
title: t`Email settings`,
code: 'INVE-W7',
message: t`Email settings not configured.`
});
}
if (globalSettings.isSet('SERVER_RESTART_REQUIRED')) {
_alerts.push({
key: 'restart',
title: t`Server Restart`,
code: 'INVE-W6',
message: t`The server requires a restart to apply changes.`
});
}
const n_migrations =
Number.parseInt(globalSettings.getSetting('_PENDING_MIGRATIONS')) ?? 0;
if (n_migrations > 0) {
_alerts.push({
key: 'migrations',
title: t`Database Migrations`,
code: 'INVE-W8',
message: t`There are pending database migrations.`
});
}
return _alerts.filter((alert) => !dismissed.includes(alert.key));
}, [server, dismissed, globalSettings]);
if (user.isStaff() && alerts.length > 0)
return (
<Menu withinPortal={true} position='bottom-end'>
<Menu.Target>
<Tooltip position='bottom-end' label={t`Alerts`}>
<ActionIcon
variant='transparent'
aria-label='open-alerts'
color='red'
>
<IconExclamationCircle />
</ActionIcon>
</Tooltip>
</Menu.Target>
<Menu.Dropdown>
{alerts.map((alert) => (
<Menu.Item key={`alert-item-${alert.key}`}>
<Alert
withCloseButton
color='red'
title={alert.title}
onClose={() => setDismissed([...dismissed, alert.key])}
>
{alert.message}
{alert.code && errorCodeLink(alert.code)}
</Alert>
</Menu.Item>
))}
</Menu.Dropdown>
</Menu>
);
return null;
}
export function errorCodeLink(code: string) {
return (
<a
href={`${docLinks.errorcodes}#${code.toLowerCase()}`}
target='_blank'
rel='noreferrer'
>
{t`Learn more about ${code}`}
</a>
);
}

View File

@ -1,20 +1,14 @@
import {
ActionIcon,
Alert,
Container,
Group,
Indicator,
Menu,
Tabs,
Text,
Tooltip
} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import {
IconBell,
IconExclamationCircle,
IconSearch
} from '@tabler/icons-react';
import { IconBell, IconSearch } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useEffect, useMemo, useState } from 'react';
import { useMatch, useNavigate } from 'react-router-dom';
@ -34,21 +28,14 @@ import {
import { useUserState } from '../../states/UserState';
import { ScanButton } from '../buttons/ScanButton';
import { SpotlightButton } from '../buttons/SpotlightButton';
import { Alerts } from './Alerts';
import { MainMenu } from './MainMenu';
import { NavHoverMenu } from './NavHoverMenu';
import { NavigationDrawer } from './NavigationDrawer';
import { NotificationDrawer } from './NotificationDrawer';
import { SearchDrawer } from './SearchDrawer';
interface AlertInfo {
key: string;
title: string;
message: string;
}
export function Header() {
const user = useUserState();
const [setNavigationOpen, navigationOpen] = useLocalState((state) => [
state.setNavigationOpen,
state.navigationOpen
@ -74,49 +61,6 @@ export function Header() {
return server.customize?.navbar_message;
}, [server.customize]);
const [dismissed, setDismissed] = useState<string[]>([]);
const alerts: AlertInfo[] = useMemo(() => {
const _alerts: AlertInfo[] = [];
if (server?.debug_mode) {
_alerts.push({
key: 'debug',
title: t`Debug Mode`,
message: t`The server is running in debug mode.`
});
}
if (server?.worker_running == false) {
_alerts.push({
key: 'worker',
title: t`Background Worker`,
message: t`The background worker process is not running.`
});
}
if (globalSettings.isSet('SERVER_RESTART_REQUIRED')) {
_alerts.push({
key: 'restart',
title: t`Server Restart`,
message: t`The server requires a restart to apply changes.`
});
}
const n_migrations =
Number.parseInt(globalSettings.getSetting('_PENDING_MIGRATIONS')) ?? 0;
if (n_migrations > 0) {
_alerts.push({
key: 'migrations',
title: t`Database Migrations`,
message: t`There are pending database migrations.`
});
}
return _alerts.filter((alert) => !dismissed.includes(alert.key));
}, [server, dismissed, globalSettings]);
// Fetch number of notifications for the current user
const notifications = useQuery({
queryKey: ['notification-count'],
@ -213,35 +157,7 @@ export function Header() {
</ActionIcon>
</Tooltip>
</Indicator>
{user.isStaff() && alerts.length > 0 && (
<Menu withinPortal={true} position='bottom-end'>
<Menu.Target>
<Tooltip position='bottom-end' label={t`Alerts`}>
<ActionIcon
variant='transparent'
aria-label='open-alerts'
color='red'
>
<IconExclamationCircle />
</ActionIcon>
</Tooltip>
</Menu.Target>
<Menu.Dropdown>
{alerts.map((alert) => (
<Menu.Item key={`alert-item-${alert.key}`}>
<Alert
withCloseButton
color='red'
title={alert.title}
onClose={() => setDismissed([...dismissed, alert.key])}
>
{alert.message}
</Alert>
</Menu.Item>
))}
</Menu.Dropdown>
</Menu>
)}
<Alerts />
<MainMenu />
</Group>
</Group>

View File

@ -75,7 +75,8 @@ export const docLinks = {
api: 'https://docs.inventree.org/en/latest/api/api/',
developer: 'https://docs.inventree.org/en/latest/develop/contributing/',
faq: 'https://docs.inventree.org/en/latest/faq/',
github: 'https://github.com/inventree/inventree'
github: 'https://github.com/inventree/inventree',
errorcodes: 'https://docs.inventree.org/en/latest/settings/error_codes/'
};
export function DocumentationLinks(): MenuLinkItem[] {

View File

@ -3,6 +3,7 @@ import { Accordion, Alert, Divider, Stack, Text } from '@mantine/core';
import { lazy } from 'react';
import { StylishText } from '../../../../components/items/StylishText';
import { errorCodeLink } from '../../../../components/nav/Alerts';
import { FactCollection } from '../../../../components/settings/FactCollection';
import { ApiEndpoints } from '../../../../enums/ApiEndpoints';
import { Loadable } from '../../../../functions/loading';
@ -28,6 +29,7 @@ export default function TaskManagementPanel() {
{taskInfo?.is_running == false && (
<Alert title={t`Background worker not running`} color='red'>
<Text>{t`The background task manager service is not running. Contact your system administrator.`}</Text>
{errorCodeLink('INVE-W5')}
</Alert>
)}
<Stack gap='xs'>