2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

[PUI] Machines table refactor (#8352)

* Reorganize MachineManagementPanel

- Use Accordion
- More consistent with PluginManagementPanel

* Code cleanup

* Refactor machine drawer display

* Refactor MachineTypeDetailDrawer
This commit is contained in:
Oliver 2024-10-24 15:24:52 +11:00 committed by GitHub
parent 9f9afa1593
commit 328472701d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 357 additions and 293 deletions

View File

@ -1,18 +1,20 @@
import { Trans } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import {
Accordion,
ActionIcon, ActionIcon,
Alert,
Code, Code,
Group, Group,
List, List,
Space,
Stack, Stack,
Text, Text
Title
} from '@mantine/core'; } from '@mantine/core';
import { IconRefresh } from '@tabler/icons-react'; import { IconInfoCircle, IconRefresh } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useMemo } from 'react';
import { api } from '../../../../App'; import { api } from '../../../../App';
import { StylishText } from '../../../../components/items/StylishText';
import { ApiEndpoints } from '../../../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../../../enums/ApiEndpoints';
import { apiUrl } from '../../../../states/ApiState'; import { apiUrl } from '../../../../states/ApiState';
import { MachineListTable } from '../../../../tables/machine/MachineListTable'; import { MachineListTable } from '../../../../tables/machine/MachineListTable';
@ -32,45 +34,77 @@ export default function MachineManagementPanel() {
staleTime: 10 * 1000 staleTime: 10 * 1000
}); });
const hasErrors = useMemo(() => {
return (
registryStatus?.registry_errors &&
registryStatus.registry_errors.length > 0
);
}, [registryStatus]);
return ( return (
<Stack> <Accordion multiple defaultValue={['machinelist', 'machinetypes']}>
<MachineListTable props={{}} /> <Accordion.Item value="machinelist">
<Accordion.Control>
<Space h="10px" /> <StylishText size="lg">{t`Machines`}</StylishText>
</Accordion.Control>
<Stack gap={'xs'}> <Accordion.Panel>
<Title order={5}> <MachineListTable props={{}} />
<Trans>Machine types</Trans> </Accordion.Panel>
</Title> </Accordion.Item>
<MachineTypeListTable props={{}} /> <Accordion.Item value="machinetypes">
</Stack> <Accordion.Control>
<StylishText size="lg">{t`Machine Types`}</StylishText>
<Space h="10px" /> </Accordion.Control>
<Accordion.Panel>
<Stack gap={'xs'}> <MachineTypeListTable props={{}} />
<Group> </Accordion.Panel>
<Title order={5}> </Accordion.Item>
<Trans>Machine Error Stack</Trans> <Accordion.Item value="machineerrors">
</Title> <Accordion.Control>
<ActionIcon variant="outline" onClick={() => refetch()}> <StylishText size="lg">{t`Machine Errors`}</StylishText>
<IconRefresh /> </Accordion.Control>
</ActionIcon> <Accordion.Panel>
</Group> <Stack gap="xs">
{registryStatus?.registry_errors && <Group
registryStatus.registry_errors.length === 0 ? ( justify="space-beteen"
<Text style={{ fontStyle: 'italic' }}> wrap="nowrap"
<Trans>There are no machine registry errors.</Trans> style={{ width: '100%' }}
</Text> >
) : ( {hasErrors ? (
<List> <Alert
{registryStatus?.registry_errors?.map((error, i) => ( flex={10}
<List.Item key={i}> color="red"
<Code>{error.message}</Code> title={t`Registry Registry Errors`}
</List.Item> icon={<IconInfoCircle />}
))} >
</List> <Text>{t`There are machine registry errors`}</Text>
)} </Alert>
</Stack> ) : (
</Stack> <Alert
flex={10}
color="green"
title={t`Machine Registry Errors`}
icon={<IconInfoCircle />}
>
<Text>{t`There are no machine registry errors`}</Text>
</Alert>
)}
<ActionIcon variant="outline" onClick={() => refetch()}>
<IconRefresh />
</ActionIcon>
</Group>
{hasErrors && (
<List>
{registryStatus?.registry_errors?.map((error, i) => (
<List.Item key={i}>
<Code>{error.message}</Code>
</List.Item>
))}
</List>
)}
</Stack>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
); );
} }

View File

@ -1,6 +1,6 @@
import { Trans, t } from '@lingui/macro'; import { Trans, t } from '@lingui/macro';
import { import {
ActionIcon, Accordion,
Badge, Badge,
Box, Box,
Card, Card,
@ -10,7 +10,6 @@ import {
Indicator, Indicator,
List, List,
LoadingOverlay, LoadingOverlay,
Space,
Stack, Stack,
Text, Text,
Title Title
@ -30,6 +29,7 @@ import {
OptionsActionDropdown OptionsActionDropdown
} from '../../components/items/ActionDropdown'; } from '../../components/items/ActionDropdown';
import { InfoItem } from '../../components/items/InfoItem'; import { InfoItem } from '../../components/items/InfoItem';
import { StylishText } from '../../components/items/StylishText';
import { UnavailableIndicator } from '../../components/items/UnavailableIndicator'; import { UnavailableIndicator } from '../../components/items/UnavailableIndicator';
import { import {
DetailDrawer, DetailDrawer,
@ -237,165 +237,176 @@ function MachineDrawer({
}); });
return ( return (
<Stack gap="xs"> <>
{machineEditModal.modal} <Stack gap="xs">
{machineDeleteModal.modal} {machineEditModal.modal}
{machineDeleteModal.modal}
<Group justify="space-between"> <Group justify="space-between">
<Box></Box> <Group>
{machine && <MachineStatusIndicator machine={machine} />}
<Group> <Title order={4}>{machine?.name}</Title>
{machine && <MachineStatusIndicator machine={machine} />}
<Title order={4}>{machine?.name}</Title>
</Group>
<Group>
{machine?.restart_required && (
<Badge color="red">
<Trans>Restart required</Trans>
</Badge>
)}
<OptionsActionDropdown
tooltip={t`Machine Actions`}
actions={[
EditItemAction({
tooltip: t`Edit machine`,
onClick: machineEditModal.open
}),
DeleteItemAction({
tooltip: t`Delete machine`,
onClick: machineDeleteModal.open
}),
{
icon: <IconRefresh />,
name: t`Restart`,
tooltip:
t`Restart machine` +
(machine?.restart_required
? ' (' + t`manual restart required` + ')'
: ''),
indicator: machine?.restart_required
? { color: 'red' }
: undefined,
onClick: () => machine && restartMachine(machine?.pk)
}
]}
/>
</Group>
</Group>
<Card withBorder>
<Stack gap="md">
<Group justify="space-between">
<Title order={4}>
<Trans>Machine information</Trans>
</Title>
<ActionIcon variant="outline" onClick={() => refetch()}>
<IconRefresh />
</ActionIcon>
</Group> </Group>
<Stack pos="relative" gap="xs">
<LoadingOverlay
visible={isFetching}
overlayProps={{ opacity: 0 }}
/>
<InfoItem name={t`Machine Type`}>
<Group gap="xs">
{machineType ? (
<DetailDrawerLink
to={`../type-${machine?.machine_type}`}
text={machineType.name}
/>
) : (
<Text>{machine?.machine_type}</Text>
)}
{machine && !machineType && <UnavailableIndicator />}
</Group>
</InfoItem>
<InfoItem name={t`Machine Driver`}>
<Group gap="xs">
{machineDriver ? (
<DetailDrawerLink
to={`../driver-${machine?.driver}`}
text={machineDriver.name}
/>
) : (
<Text>{machine?.driver}</Text>
)}
{!machine?.is_driver_available && <UnavailableIndicator />}
</Group>
</InfoItem>
<InfoItem name={t`Initialized`}>
<YesNoButton value={machine?.initialized || false} />
</InfoItem>
<InfoItem name={t`Active`}>
<YesNoButton value={machine?.active || false} />
</InfoItem>
<InfoItem name={t`Status`}>
<Flex direction="column">
{machine?.status === -1 ? (
<Text fz="xs">No status</Text>
) : (
StatusRenderer({
status: `${machine?.status || -1}`,
type: `MachineStatus__${machine?.status_model}` as any
})
)}
<Text fz="sm">{machine?.status_text}</Text>
</Flex>
</InfoItem>
<Group justify="space-between" gap="xs">
<Text fz="sm" fw={700}>
<Trans>Errors</Trans>:
</Text>
{machine && machine?.machine_errors.length > 0 ? (
<Badge color="red" style={{ marginLeft: '10px' }}>
{machine?.machine_errors.length}
</Badge>
) : (
<Text fz="xs">
<Trans>No errors reported</Trans>
</Text>
)}
<List w="100%">
{machine?.machine_errors.map((error, i) => (
<List.Item key={i}>
<Code>{error}</Code>
</List.Item>
))}
</List>
</Group>
</Stack>
</Stack>
</Card>
<Space h="10px" />
{machine?.is_driver_available && ( <Group>
<> {machine?.restart_required && (
<Card withBorder> <Badge color="red">
<Title order={5} pb={4}> <Trans>Restart required</Trans>
<Trans>Machine Settings</Trans> </Badge>
</Title> )}
<MachineSettingList <OptionsActionDropdown
machinePk={machinePk} tooltip={t`Machine Actions`}
configType="M" actions={[
onChange={refreshAll} EditItemAction({
tooltip: t`Edit machine`,
onClick: machineEditModal.open
}),
DeleteItemAction({
tooltip: t`Delete machine`,
onClick: machineDeleteModal.open
}),
{
icon: <IconRefresh />,
name: t`Restart`,
tooltip:
t`Restart machine` +
(machine?.restart_required
? ' (' + t`manual restart required` + ')'
: ''),
indicator: machine?.restart_required
? { color: 'red' }
: undefined,
onClick: () => machine && restartMachine(machine?.pk)
}
]}
/> />
</Card> </Group>
</Group>
<Card withBorder> <Accordion
<Title order={5} pb={4}> multiple
<Trans>Driver Settings</Trans> defaultValue={['machine-info', 'machine-settings', 'driver-settings']}
</Title> >
<MachineSettingList <Accordion.Item value="machine-info">
machinePk={machinePk} <Accordion.Control>
configType="D" <StylishText size="lg">{t`Machine Information`}</StylishText>
onChange={refreshAll} </Accordion.Control>
/> <Accordion.Panel>
</Card> <Card withBorder>
</> <Stack gap="md">
)} <Stack pos="relative" gap="xs">
</Stack> <LoadingOverlay
visible={isFetching}
overlayProps={{ opacity: 0 }}
/>
<InfoItem name={t`Machine Type`}>
<Group gap="xs">
{machineType ? (
<DetailDrawerLink
to={`../type-${machine?.machine_type}`}
text={machineType.name}
/>
) : (
<Text>{machine?.machine_type}</Text>
)}
{machine && !machineType && <UnavailableIndicator />}
</Group>
</InfoItem>
<InfoItem name={t`Machine Driver`}>
<Group gap="xs">
{machineDriver ? (
<DetailDrawerLink
to={`../driver-${machine?.driver}`}
text={machineDriver.name}
/>
) : (
<Text>{machine?.driver}</Text>
)}
{!machine?.is_driver_available && (
<UnavailableIndicator />
)}
</Group>
</InfoItem>
<InfoItem name={t`Initialized`}>
<YesNoButton value={machine?.initialized || false} />
</InfoItem>
<InfoItem name={t`Active`}>
<YesNoButton value={machine?.active || false} />
</InfoItem>
<InfoItem name={t`Status`}>
<Flex direction="column">
{machine?.status === -1 ? (
<Text fz="xs">No status</Text>
) : (
StatusRenderer({
status: `${machine?.status || -1}`,
type: `MachineStatus__${machine?.status_model}` as any
})
)}
<Text fz="sm">{machine?.status_text}</Text>
</Flex>
</InfoItem>
<Group justify="space-between" gap="xs">
<Text fz="sm" fw={700}>
<Trans>Errors</Trans>:
</Text>
{machine && machine?.machine_errors.length > 0 ? (
<Badge color="red" style={{ marginLeft: '10px' }}>
{machine?.machine_errors.length}
</Badge>
) : (
<Text fz="xs">
<Trans>No errors reported</Trans>
</Text>
)}
<List w="100%">
{machine?.machine_errors.map((error, i) => (
<List.Item key={i}>
<Code>{error}</Code>
</List.Item>
))}
</List>
</Group>
</Stack>
</Stack>
</Card>
</Accordion.Panel>
</Accordion.Item>
{machine?.is_driver_available && (
<Accordion.Item value="machine-settings">
<Accordion.Control>
<StylishText size="lg">{t`Machine Settings`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<Card withBorder>
<MachineSettingList
machinePk={machinePk}
configType="M"
onChange={refreshAll}
/>
</Card>
</Accordion.Panel>
</Accordion.Item>
)}
{machine?.is_driver_available && (
<Accordion.Item value="driver-settings">
<Accordion.Control>
<StylishText size="lg">{t`Driver Settings`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<Card withBorder>
<MachineSettingList
machinePk={machinePk}
configType="D"
onChange={refreshAll}
/>
</Card>
</Accordion.Panel>
</Accordion.Item>
)}
</Accordion>
</Stack>
</>
); );
} }
@ -543,8 +554,8 @@ export function MachineListTable({
const tableActions = useMemo(() => { const tableActions = useMemo(() => {
return [ return [
<AddItemButton <AddItemButton
key="outline" key="add-machine"
variant="outline" tooltip={t`Add machine`}
onClick={() => { onClick={() => {
setCreateFormMachineType(null); setCreateFormMachineType(null);
createMachineForm.open(); createMachineForm.open();
@ -558,8 +569,8 @@ export function MachineListTable({
{createMachineForm.modal} {createMachineForm.modal}
{renderMachineDrawer && ( {renderMachineDrawer && (
<DetailDrawer <DetailDrawer
title={t`Machine detail`} title={t`Machine Detail`}
size={'lg'} size={'xl'}
renderContent={(id) => { renderContent={(id) => {
if (!id || !id.startsWith('machine-')) return false; if (!id || !id.startsWith('machine-')) return false;
return ( return (

View File

@ -1,6 +1,8 @@
import { Trans, t } from '@lingui/macro'; import { Trans, t } from '@lingui/macro';
import { import {
Accordion,
ActionIcon, ActionIcon,
Alert,
Badge, Badge,
Card, Card,
Code, Code,
@ -11,11 +13,12 @@ import {
Text, Text,
Title Title
} from '@mantine/core'; } from '@mantine/core';
import { IconRefresh } from '@tabler/icons-react'; import { IconExclamationCircle, IconRefresh } from '@tabler/icons-react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { InfoItem } from '../../components/items/InfoItem'; import { InfoItem } from '../../components/items/InfoItem';
import { StylishText } from '../../components/items/StylishText';
import { DetailDrawer } from '../../components/nav/DetailDrawer'; import { DetailDrawer } from '../../components/nav/DetailDrawer';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { useTable } from '../../hooks/UseTable'; import { useTable } from '../../hooks/UseTable';
@ -79,93 +82,109 @@ function MachineTypeDrawer({
); );
return ( return (
<Stack> <>
<Group justify="center"> <Stack>
<Title order={4}> <Group wrap="nowrap">
{machineType ? machineType.name : machineTypeSlug}
</Title>
</Group>
{!machineType && (
<Text style={{ fontStyle: 'italic' }}>
<Trans>Machine type not found.</Trans>
</Text>
)}
<Card withBorder>
<Stack gap="md">
<Group justify="space-between">
<Title order={4}>
<Trans>Machine type information</Trans>
</Title>
<ActionIcon variant="outline" onClick={() => refresh()}>
<IconRefresh />
</ActionIcon>
</Group>
<Stack pos="relative" gap="xs">
<LoadingOverlay
visible={isFetching}
overlayProps={{ opacity: 0 }}
/>
<InfoItem name={t`Name`} value={machineType?.name} type="text" />
<InfoItem name={t`Slug`} value={machineType?.slug} type="text" />
<InfoItem
name={t`Description`}
value={machineType?.description}
type="text"
/>
{!machineType?.is_builtin && (
<InfoItem
name={t`Provider plugin`}
value={machineType?.provider_plugin?.name}
type="text"
link={
machineType?.provider_plugin?.pk !== null
? `../../plugin/${machineType?.provider_plugin?.pk}/`
: undefined
}
detailDrawerLink
/>
)}
<InfoItem
name={t`Provider file`}
value={machineType?.provider_file}
type="code"
/>
<InfoItem
name={t`Builtin`}
value={machineType?.is_builtin}
type="boolean"
/>
</Stack>
</Stack>
</Card>
<Card withBorder>
<Stack gap="md">
<Title order={4}> <Title order={4}>
<Trans>Available drivers</Trans> {machineType ? machineType.name : machineTypeSlug}
</Title> </Title>
</Group>
<InvenTreeTable {!machineType && (
url={apiUrl(ApiEndpoints.machine_driver_list)} <Alert
tableState={table} color="red"
columns={machineDriverTableColumns} title={t`Not Found`}
props={{ icon={<IconExclamationCircle />}
dataFormatter: (data: any) => { >
return data.filter( <Text>{t`Machine type not found.`}</Text>
(d: any) => d.machine_type === machineTypeSlug </Alert>
); )}
},
enableDownload: false, <Accordion
enableSearch: false, multiple
onRowClick: (machine) => navigate(`../driver-${machine.slug}/`) defaultValue={['machine-type-info', 'machine-drivers']}
}} >
/> <Accordion.Item value="machine-type-info">
</Stack> <Accordion.Control>
</Card> <StylishText size="lg">{t`Machine Type Information`}</StylishText>
</Stack> </Accordion.Control>
<Accordion.Panel>
<Card withBorder>
<Stack pos="relative" gap="xs">
<LoadingOverlay
visible={isFetching}
overlayProps={{ opacity: 0 }}
/>
<InfoItem
name={t`Name`}
value={machineType?.name}
type="text"
/>
<InfoItem
name={t`Slug`}
value={machineType?.slug}
type="text"
/>
<InfoItem
name={t`Description`}
value={machineType?.description}
type="text"
/>
{!machineType?.is_builtin && (
<InfoItem
name={t`Provider plugin`}
value={machineType?.provider_plugin?.name}
type="text"
link={
machineType?.provider_plugin?.pk !== null
? `../../plugin/${machineType?.provider_plugin?.pk}/`
: undefined
}
detailDrawerLink
/>
)}
<InfoItem
name={t`Provider file`}
value={machineType?.provider_file}
type="code"
/>
<InfoItem
name={t`Builtin`}
value={machineType?.is_builtin}
type="boolean"
/>
</Stack>
</Card>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value="machine-drivers">
<Accordion.Control>
<StylishText size="lg">{t`Available Drivers`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<Card withBorder>
<InvenTreeTable
url={apiUrl(ApiEndpoints.machine_driver_list)}
tableState={table}
columns={machineDriverTableColumns}
props={{
dataFormatter: (data: any) => {
return data.filter(
(d: any) => d.machine_type === machineTypeSlug
);
},
enableDownload: false,
enableSearch: false,
onRowClick: (machine) =>
navigate(`../driver-${machine.slug}/`)
}}
/>
</Card>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
</Stack>
</>
); );
} }
@ -335,8 +354,8 @@ export function MachineTypeListTable({
return ( return (
<> <>
<DetailDrawer <DetailDrawer
title={t`Machine type detail`} title={t`Machine Type Detail`}
size={'lg'} size={'xl'}
renderContent={(id) => { renderContent={(id) => {
if (!id || !id.startsWith('type-')) return false; if (!id || !id.startsWith('type-')) return false;
return ( return (
@ -345,8 +364,8 @@ export function MachineTypeListTable({
}} }}
/> />
<DetailDrawer <DetailDrawer
title={t`Machine driver detail`} title={t`Machine Driver Detail`}
size={'lg'} size={'xl'}
renderContent={(id) => { renderContent={(id) => {
if (!id || !id.startsWith('driver-')) return false; if (!id || !id.startsWith('driver-')) return false;
return ( return (