2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-12 18:15:40 +00:00

Tasks API Endpoint (#6230)

* Add API endpoint for background task overview

* Cleanup other pending heartbeat tasks

* Adds API endpoint for queued tasks

* Adds API endpoint for scheduled tasks

* Add API endpoint for failed tasks

* Update API version info

* Add table for displaying pending tasks

* Add failed tasks table

* Use accordion

* Annotate extra data to scheduled tasks serializer

* Extend API functionality

* Update tasks.py

- Allow skipping of static file step in "invoke update"
- Allows for quicker updates in dev mode

* Display task result error for failed tasks

* Allow delete of failed tasks

* Remove old debug message

* Adds ability to delete pending tasks

* Update table columns

* Fix unused imports

* Prevent multiple heartbeat functions from being added to the queue at startup

* Add unit tests for API
This commit is contained in:
Oliver
2024-01-17 07:10:42 +11:00
committed by GitHub
parent 5c7d3af150
commit 75f75ed820
18 changed files with 569 additions and 15 deletions

View File

@ -388,7 +388,9 @@ export function InvenTreeTable<T = any>({
},
onConfirm: () => {
// Delete the selected records
let selection = tableState.selectedRecords.map((record) => record.pk);
let selection = tableState.selectedRecords.map(
(record) => record.pk ?? record.id
);
api
.delete(url, {
@ -409,6 +411,12 @@ export function InvenTreeTable<T = any>({
})
.catch((_error) => {
console.warn(`Bulk delete operation failed at ${url}`);
showNotification({
title: t`Error`,
message: t`Failed to delete records`,
color: 'red'
});
});
}
});

View File

@ -80,7 +80,6 @@ export default function ErrorReportTable() {
enableSelection: true,
rowActions: rowActions,
onRowClick: (row) => {
console.log(row);
setError(row.data);
open();
}

View File

@ -0,0 +1,79 @@
import { t } from '@lingui/macro';
import { Drawer, Text } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { useMemo, useState } from 'react';
import { ApiPaths } from '../../../enums/ApiEndpoints';
import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState';
import { StylishText } from '../../items/StylishText';
import { TableColumn } from '../Column';
import { InvenTreeTable } from '../InvenTreeTable';
export default function FailedTasksTable() {
const table = useTable('tasks-failed');
const [error, setError] = useState<string>('');
const [opened, { open, close }] = useDisclosure(false);
const columns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'func',
title: t`Task`,
sortable: true,
switchable: false
},
{
accessor: 'pk',
title: t`Task ID`
},
{
accessor: 'started',
title: t`Started`,
sortable: true,
switchable: false
},
{
accessor: 'stopped',
title: t`Stopped`,
sortable: true,
switchable: false
},
{
accessor: 'attempt_count',
title: t`Attempts`
}
];
}, []);
return (
<>
<Drawer
opened={opened}
size="xl"
position="right"
title={<StylishText>{t`Error Details`}</StylishText>}
onClose={close}
>
{error.split('\n').map((line: string) => {
return <Text size="sm">{line}</Text>;
})}
</Drawer>
<InvenTreeTable
url={apiUrl(ApiPaths.task_failed_list)}
tableState={table}
columns={columns}
props={{
enableBulkDelete: true,
enableSelection: true,
onRowClick: (row: any) => {
setError(row.result);
open();
}
}}
/>
</>
);
}

View File

@ -0,0 +1,56 @@
import { t } from '@lingui/macro';
import { useMemo } from 'react';
import { ApiPaths } from '../../../enums/ApiEndpoints';
import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState';
import { TableColumn } from '../Column';
import { InvenTreeTable } from '../InvenTreeTable';
export default function PendingTasksTable() {
const table = useTable('tasks-pending');
const columns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'func',
title: t`Task`,
switchable: false
},
{
accessor: 'task_id',
title: t`Task ID`
},
{
accessor: 'name',
title: t`Name`
},
{
accessor: 'lock',
title: t`Created`,
sortable: true,
switchable: false
},
{
accessor: 'args',
title: t`Arguments`
},
{
accessor: 'kwargs',
title: t`Keywords`
}
];
}, []);
return (
<InvenTreeTable
url={apiUrl(ApiPaths.task_pending_list)}
tableState={table}
columns={columns}
props={{
enableBulkDelete: true,
enableSelection: true
}}
/>
);
}

View File

@ -0,0 +1,62 @@
import { t } from '@lingui/macro';
import { Group, Text } from '@mantine/core';
import { IconCircleCheck, IconCircleX } from '@tabler/icons-react';
import { useMemo } from 'react';
import { ApiPaths } from '../../../enums/ApiEndpoints';
import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState';
import { TableColumn } from '../Column';
import { InvenTreeTable } from '../InvenTreeTable';
export default function ScheduledTasksTable() {
const table = useTable('tasks-scheduled');
const columns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'func',
title: t`Task`,
sortable: true,
switchable: false
},
{
accessor: 'last_run',
title: t`Last Run`,
sortable: true,
switchable: false,
render: (record: any) => {
if (!record.last_run) {
return '-';
}
return (
<Group position="apart">
<Text>{record.last_run}</Text>
{record.success ? (
<IconCircleCheck color="green" />
) : (
<IconCircleX color="red" />
)}
</Group>
);
}
},
{
accessor: 'next_run',
title: t`Next Run`,
sortable: true,
switchable: false
}
];
}, []);
return (
<InvenTreeTable
url={apiUrl(ApiPaths.task_scheduled_list)}
tableState={table}
columns={columns}
props={{}}
/>
);
}

View File

@ -24,6 +24,11 @@ export enum ApiPaths {
group_list = 'api-group-list',
owner_list = 'api-owner-list',
task_overview = 'api-task-overview',
task_pending_list = 'api-task-pending-list',
task_scheduled_list = 'api-task-scheduled-list',
task_failed_list = 'api-task-failed-list',
settings_global_list = 'api-settings-global-list',
settings_user_list = 'api-settings-user-list',
notifications_list = 'api-notifications-list',

View File

@ -1,6 +1,7 @@
import { Trans, t } from '@lingui/macro';
import { Divider, Paper, SimpleGrid, Stack, Text, Title } from '@mantine/core';
import {
IconCpu,
IconExclamationCircle,
IconList,
IconListDetails,
@ -20,6 +21,10 @@ const UserManagementPanel = Loadable(
lazy(() => import('./UserManagementPanel'))
);
const TaskManagementPanel = Loadable(
lazy(() => import('./TaskManagementPanel'))
);
const PluginManagementPanel = Loadable(
lazy(() => import('./PluginManagementPanel'))
);
@ -52,6 +57,12 @@ export default function AdminCenter() {
icon: <IconUsersGroup />,
content: <UserManagementPanel />
},
{
name: 'background',
label: t`Background Tasks`,
icon: <IconCpu />,
content: <TaskManagementPanel />
},
{
name: 'errors',
label: t`Error Reports`,
@ -126,7 +137,7 @@ export default function AdminCenter() {
<PanelGroup
pageKey="admin-center"
panels={adminCenterPanels}
collapsible={false}
collapsible={true}
/>
</Stack>
);

View File

@ -0,0 +1,51 @@
import { t } from '@lingui/macro';
import { Accordion } from '@mantine/core';
import { lazy } from 'react';
import { StylishText } from '../../../../components/items/StylishText';
import { Loadable } from '../../../../functions/loading';
const PendingTasksTable = Loadable(
lazy(() => import('../../../../components/tables/settings/PendingTasksTable'))
);
const ScheduledTasksTable = Loadable(
lazy(
() => import('../../../../components/tables/settings/ScheduledTasksTable')
)
);
const FailedTasksTable = Loadable(
lazy(() => import('../../../../components/tables/settings/FailedTasksTable'))
);
export default function TaskManagementPanel() {
return (
<Accordion defaultValue="pending">
<Accordion.Item value="pending">
<Accordion.Control>
<StylishText size="lg">{t`Pending Tasks`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<PendingTasksTable />
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value="scheduled">
<Accordion.Control>
<StylishText size="lg">{t`Scheduled Tasks`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<ScheduledTasksTable />
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item value="failed">
<Accordion.Control>
<StylishText size="lg">{t`Failed Tasks`}</StylishText>
</Accordion.Control>
<Accordion.Panel>
<FailedTasksTable />
</Accordion.Panel>
</Accordion.Item>
</Accordion>
);
}

View File

@ -99,6 +99,14 @@ export function apiEndpoint(path: ApiPaths): string {
return 'currency/exchange/';
case ApiPaths.currency_refresh:
return 'currency/refresh/';
case ApiPaths.task_overview:
return 'background-task/';
case ApiPaths.task_pending_list:
return 'background-task/pending/';
case ApiPaths.task_scheduled_list:
return 'background-task/scheduled/';
case ApiPaths.task_failed_list:
return 'background-task/failed/';
case ApiPaths.api_search:
return 'search/';
case ApiPaths.settings_global_list: