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:
@ -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'
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -80,7 +80,6 @@ export default function ErrorReportTable() {
|
||||
enableSelection: true,
|
||||
rowActions: rowActions,
|
||||
onRowClick: (row) => {
|
||||
console.log(row);
|
||||
setError(row.data);
|
||||
open();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
@ -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
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
@ -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={{}}
|
||||
/>
|
||||
);
|
||||
}
|
@ -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',
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
@ -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:
|
||||
|
Reference in New Issue
Block a user