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:
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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>
|
||||
)}
|
||||
|
130
src/frontend/src/components/nav/Alerts.tsx
Normal file
130
src/frontend/src/components/nav/Alerts.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
|
@ -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[] {
|
||||
|
@ -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'>
|
||||
|
Reference in New Issue
Block a user