2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-27 19:16:44 +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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 185 additions and 102 deletions

View File

@ -52,6 +52,40 @@ See [INVE-W1](#inve-w1)
See [INVE-W1](#inve-w1) See [INVE-W1](#inve-w1)
#### INVE-W4
**Server is running in debug mode - Backend**
InvenTree is running in debug mode. This is **not** recommended for production use, as it exposes sensitive information and makes the server more vulnerable to attacks. Debug mode is not intended for production/exposed instances, **even for short duration**.
It is recommended to run InvenTree in production mode for better security and performance. See [Debug Mode Information](../start/intro.md#debug-mode).
#### INVE-W5
**Background worker process not running - Backend**
The background worker seems to not be running. This is detected by a heartbeat that runs all 5 minutes - this error triggers after not being run in the last 10 minutes.
Check if the process for background workers is running and reaching the database. Steps vary between deployment methods.
See [Background Worker Information](../start/processes.md#background-worker).
#### INVE-W6
**Server restart required - Backend**
The server needs a restart due to changes in settings. Steps very between deployment methods.
#### INVE-W7
**Email settings not configured - Backend**
Not all required settings for sending emails are configured. Not having an email provider configured might lead to degraded processes as password reset, update notifications and user notifications can not work. Setting up email is recommended.
See [Email information](../start/config.md#email-settings).
#### INVE-W8
**Database Migrations required - Backend**
There are database migrations waiting to be applied. This might lead to integrity and availability issues. Applying migrations as soon as possible is recommended.
Some deployment methods support [auto applying of updates](../start/config.md#auto-update). See also [Perform Database Migrations](../start/install.md#perform-database-migrations).
Steps very between deployment methods.
### INVE-I (InvenTree Information) ### INVE-I (InvenTree Information)
Information — These are not errors but information messages. They might point out potential issues or just provide information. Information — These are not errors but information messages. They might point out potential issues or just provide information.

View File

@ -125,9 +125,9 @@ Running in DEBUG mode provides many handy development features, however it is st
So, for a production setup, you should set `INVENTREE_DEBUG=false` in the [configuration options](./config.md). So, for a production setup, you should set `INVENTREE_DEBUG=false` in the [configuration options](./config.md).
### Potential Issues ### Turning Debug Mode off
Turning off DEBUG mode creates further work for the system administrator. In particular, when running in DEBUG mode, the InvenTree web server natively manages *static* and *media* files, which means that when DEBUG mode is *disabled*, the InvenTree server can no longer run as a monolithic process. When running in DEBUG mode, the InvenTree web server natively manages *static* and *media* files, which means that when DEBUG mode is *disabled*, the proxy setup has to be configured to handle this.
!!! info "Read More" !!! info "Read More"
Refer to the [proxy server documentation](./processes.md#proxy-server) for more details Refer to the [proxy server documentation](./processes.md#proxy-server) for more details

View File

@ -30,15 +30,15 @@ def is_email_configured():
# Display warning unless in test mode # Display warning unless in test mode
if not testing: # pragma: no cover 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 # Display warning unless in test mode
if not settings.EMAIL_HOST_USER and not testing: # pragma: no cover 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 # Display warning unless in test mode
if not settings.EMAIL_HOST_PASSWORD and testing: # pragma: no cover 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 # Email sender must be configured
if not settings.DEFAULT_FROM_EMAIL: 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: if not is_email_configured() and not settings.TESTING:
# Email is not configured / enabled # Email is not configured / enabled
logger.info('INVE-W7: Email will not be send, no mail server configured')
return return
# If a *from_email* is not specified, ensure that the default is set # 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 if not InvenTree.helpers_email.is_email_configured(): # pragma: no cover
result = False result = False
if not settings.DEBUG: 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 result: # pragma: no cover
if not settings.DEBUG: if not settings.DEBUG:

View File

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

View File

@ -52,6 +52,7 @@ export function ServerInfoModal({
<Trans>Debug Mode</Trans> <Trans>Debug Mode</Trans>
</Table.Td> </Table.Td>
<Table.Td> <Table.Td>
<Badge color='red'>INVE-W4</Badge>
<Trans>Server is running in debug mode</Trans> <Trans>Server is running in debug mode</Trans>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
@ -102,21 +103,19 @@ export function ServerInfoModal({
<Trans>Background Worker</Trans> <Trans>Background Worker</Trans>
</Table.Td> </Table.Td>
<Table.Td> <Table.Td>
<Badge color='red'> <Badge color='red'>INVE-W5</Badge>
<Trans>Background worker not running</Trans> <Trans>The Background worker process is not running.</Trans>
</Badge>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
)} )}
{server?.email_configured == false && ( {!server?.email_configured && (
<Table.Tr> <Table.Tr>
<Table.Td> <Table.Td>
<Trans>Email Settings</Trans> <Trans>Email Settings</Trans>
</Table.Td> </Table.Td>
<Table.Td> <Table.Td>
<Badge color='red'> <Badge color='red'>INVE-W7</Badge>
<Trans>Email settings not configured</Trans> <Trans>Email settings not configured.</Trans>
</Badge>
</Table.Td> </Table.Td>
</Table.Tr> </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 { import {
ActionIcon, ActionIcon,
Alert,
Container, Container,
Group, Group,
Indicator, Indicator,
Menu,
Tabs, Tabs,
Text, Text,
Tooltip Tooltip
} from '@mantine/core'; } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks'; import { useDisclosure } from '@mantine/hooks';
import { import { IconBell, IconSearch } from '@tabler/icons-react';
IconBell,
IconExclamationCircle,
IconSearch
} from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { type ReactNode, useEffect, useMemo, useState } from 'react'; import { type ReactNode, useEffect, useMemo, useState } from 'react';
import { useMatch, useNavigate } from 'react-router-dom'; import { useMatch, useNavigate } from 'react-router-dom';
@ -34,21 +28,14 @@ import {
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import { ScanButton } from '../buttons/ScanButton'; import { ScanButton } from '../buttons/ScanButton';
import { SpotlightButton } from '../buttons/SpotlightButton'; import { SpotlightButton } from '../buttons/SpotlightButton';
import { Alerts } from './Alerts';
import { MainMenu } from './MainMenu'; import { MainMenu } from './MainMenu';
import { NavHoverMenu } from './NavHoverMenu'; import { NavHoverMenu } from './NavHoverMenu';
import { NavigationDrawer } from './NavigationDrawer'; import { NavigationDrawer } from './NavigationDrawer';
import { NotificationDrawer } from './NotificationDrawer'; import { NotificationDrawer } from './NotificationDrawer';
import { SearchDrawer } from './SearchDrawer'; import { SearchDrawer } from './SearchDrawer';
interface AlertInfo {
key: string;
title: string;
message: string;
}
export function Header() { export function Header() {
const user = useUserState();
const [setNavigationOpen, navigationOpen] = useLocalState((state) => [ const [setNavigationOpen, navigationOpen] = useLocalState((state) => [
state.setNavigationOpen, state.setNavigationOpen,
state.navigationOpen state.navigationOpen
@ -74,49 +61,6 @@ export function Header() {
return server.customize?.navbar_message; return server.customize?.navbar_message;
}, [server.customize]); }, [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 // Fetch number of notifications for the current user
const notifications = useQuery({ const notifications = useQuery({
queryKey: ['notification-count'], queryKey: ['notification-count'],
@ -213,35 +157,7 @@ export function Header() {
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
</Indicator> </Indicator>
{user.isStaff() && alerts.length > 0 && ( <Alerts />
<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>
)}
<MainMenu /> <MainMenu />
</Group> </Group>
</Group> </Group>

View File

@ -75,7 +75,8 @@ export const docLinks = {
api: 'https://docs.inventree.org/en/latest/api/api/', api: 'https://docs.inventree.org/en/latest/api/api/',
developer: 'https://docs.inventree.org/en/latest/develop/contributing/', developer: 'https://docs.inventree.org/en/latest/develop/contributing/',
faq: 'https://docs.inventree.org/en/latest/faq/', 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[] { export function DocumentationLinks(): MenuLinkItem[] {

View File

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