blob: 29fd982ef827ce4763e7aadfeded18e5f3eef577 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {
useCallback, useEffect, useState, useRef,
} from 'react';
import Head from 'next/head';
import type { NextPage } from 'next';
import moment from 'moment';
import {
ChevronLeftIcon,
ChevronRightIcon,
WarningTwoIcon,
} from '@chakra-ui/icons';
import {
Divider,
Button,
Flex,
Input,
Stack,
// Select,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
Text,
AlertDialog,
AlertDialogOverlay,
AlertDialogContent,
AlertDialogHeader,
AlertDialogBody,
AlertDialogFooter,
useToast,
Box,
Spinner,
} from '@chakra-ui/react';
import axios from 'axios';
import Details from '../components/workflow/Details';
import Create from '../components/workflow/Create';
import { WorkflowType } from '../components/workflow/types';
// import { WorkflowStatusMap } from '../components/workflow/constant';
const ApiRoot = process.env.NEXT_PUBLIC_WORKFLOW_API_ROOT;
const Workflows: NextPage = () => {
const toast = useToast();
const [isLoading, setIsLoading] = useState(true);
const [workflows, setWorkflows] = useState<WorkflowType[]>([]);
const [total, setTotal] = useState(0);
const [keywordFilter, setKeywordFilter] = useState('');
const [statusFilter, setStatusFilter] = useState('any');
const [pageIndex, setPageIndex] = useState(1);
const pageSize = 10;
const [refreshFlag, setRefreshFlag] = useState<number>(+new Date());
const [isShowCreate, setIsShowCreate] = useState(false);
const [isShowDetails, setIsShowDetails] = useState(false);
const [isShowCancelConfirm, setIsShowCancelComfirm] = useState(false);
const cancelRef = useRef(null);
const [selectedWorkflow, setSelectedWorkflow] = useState<WorkflowType | null>(
null,
);
const onDelete = () => {
axios
.delete(`${ApiRoot}/workflow/${selectedWorkflow?.workflow_id}`)
.then(() => {
toast({
title: 'Workflow has been deleted',
description: (
<Box>
<Text>{`Workflow ID: ${selectedWorkflow?.workflow_id}`}</Text>
<Text>{`Workflow Name: ${selectedWorkflow?.workflow_name}`}</Text>
</Box>
),
status: 'success',
position: 'top-right',
});
setIsShowCancelComfirm(false);
setSelectedWorkflow(null);
setRefreshFlag(+new Date());
})
.catch((error) => {
setIsShowCancelComfirm(false);
toast({
title: 'Failed to delete',
description: error.response.data,
status: 'error',
position: 'top-right',
});
});
};
const getWorkflows = useCallback(async () => {
setIsLoading(true);
try {
const reqParams: {
page: number;
size: number;
workflow_id?: string;
status?: string;
} = {
page: pageIndex,
size: pageSize,
};
// if (statusFilter) {
// reqParams.status = statusFilter;
// }
if (keywordFilter) {
reqParams.workflow_id = keywordFilter;
}
const { data } = await axios.get<{
total: number;
workflows: WorkflowType[];
}>(`${ApiRoot}/workflow`, {
params: reqParams,
});
setWorkflows(data.workflows);
setTotal(data.total);
setIsLoading(false);
} catch (error) {
setIsLoading(false);
}
}, [pageIndex, pageSize, keywordFilter, statusFilter, refreshFlag]);
useEffect(() => {
const controller = new AbortController();
getWorkflows();
return () => {
controller.abort();
};
}, [pageIndex, pageSize, keywordFilter, statusFilter, refreshFlag]);
return (
<>
<Head>
<title>Workflows | Apache EventMesh Dashboard</title>
</Head>
<Flex
w="full"
h="full"
bg="white"
flexDirection="column"
borderWidth="1px"
borderRadius="md"
overflow="hidden"
p="6"
>
<Flex w="full" justifyContent="space-between" mt="2" mb="2">
<Button
size="md"
backgroundColor="#2a62ad"
color="white"
_hover={{ bg: '#dce5fe', color: '#2a62ad' }}
onClick={() => setIsShowCreate(true)}
>
Create Workflow
</Button>
<Stack direction="row" spacing="2">
<Input
size="md"
placeholder="Workflow ID"
value={keywordFilter}
onChange={(evt) => setKeywordFilter(evt.target.value)}
/>
{/* <Select
size="md"
placeholder="Status"
value={statusFilter}
onChange={(event) => setStatusFilter(event.target.value)}
>
<option value="1">Running</option>
<option value="-1">Deleted</option>
</Select> */}
<Box>
<Button
colorScheme="blue"
variant="ghost"
onClick={() => setRefreshFlag(+new Date())}
>
Refresh
</Button>
</Box>
</Stack>
</Flex>
<Divider mt="15" mb="15" orientation="horizontal" />
<TableContainer>
<Table variant="simple">
<Thead>
<Tr>
<Th>Workflow ID</Th>
<Th>Workflow Name</Th>
{/* <Th>Status</Th> */}
<Th isNumeric>Total Instance</Th>
<Th isNumeric>Running</Th>
<Th isNumeric>Failed</Th>
<Th>Updated at</Th>
<Th>Created At</Th>
<Th>Actions</Th>
</Tr>
</Thead>
<Tbody>
{workflows.map((workflow) => (
<Tr key={workflow.workflow_id}>
<Td>
<Button
size="sm"
colorScheme="blue"
variant="ghost"
onClick={() => {
setIsShowDetails(true);
setSelectedWorkflow(workflow);
}}
>
{workflow.workflow_id}
</Button>
</Td>
<Td>{workflow.workflow_name}</Td>
{/* <Td>{WorkflowStatusMap.get(workflow.status)}</Td> */}
<Td isNumeric>{workflow.total_instances}</Td>
<Td isNumeric>{workflow.total_running_instances}</Td>
<Td isNumeric>{workflow.total_failed_instances}</Td>
<Td>
{moment(workflow.update_time).format('YYYY-MM-DD HH:mm:ss')}
</Td>
<Td>
{moment(workflow.create_time).format('YYYY-MM-DD HH:mm:ss')}
</Td>
<Td>
<Button
size="sm"
colorScheme="blue"
variant="ghost"
onClick={() => {
setSelectedWorkflow(workflow);
setIsShowCancelComfirm(true);
}}
>
Delete
</Button>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Flex mt={4} alignItems="center">
{isLoading ? (
<Spinner colorScheme="blue" size="sm" />
) : (
<Text fontSize="sm" color="#909090">
{total}
{` workflow${total > 1 ? 's' : ''} in total, `}
{`page ${pageIndex} of ${Math.ceil(total / pageSize)}`}
</Text>
)}
<Flex flex={1} justifyContent="flex-end" align="center">
<Button
mr={2}
size="sm"
leftIcon={<ChevronLeftIcon />}
colorScheme="blue"
variant="outline"
disabled={pageIndex < 2}
onClick={() => setPageIndex(pageIndex - 1)}
>
Prev
</Button>
<Button
size="sm"
rightIcon={<ChevronRightIcon />}
colorScheme="blue"
variant="outline"
disabled={pageIndex >= Math.ceil(total / pageSize)}
onClick={() => setPageIndex(pageIndex + 1)}
>
Next
</Button>
</Flex>
</Flex>
</Flex>
<AlertDialog
leastDestructiveRef={cancelRef}
isOpen={isShowCancelConfirm}
onClose={() => setIsShowCancelComfirm(false)}
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
<Flex alignItems="center">
<WarningTwoIcon mr={2} boxSize={6} color="orange" />
<Text fontSize="xl" as="b">
Confirm
</Text>
</Flex>
</AlertDialogHeader>
<AlertDialogBody>
Are you sure to delete
{' '}
<Text fontSize="sm" as="b">
{selectedWorkflow?.workflow_name}
</Text>
?
<Box />
</AlertDialogBody>
<AlertDialogFooter>
<Button
ref={cancelRef}
onClick={() => {
setIsShowCancelComfirm(false);
setSelectedWorkflow(null);
}}
>
No
</Button>
<Button colorScheme="blue" onClick={() => onDelete()} ml={3}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
<Details
visible={isShowDetails}
data={selectedWorkflow}
onSaved={() => {
setIsShowDetails(false);
setRefreshFlag(+new Date());
}}
onClose={() => {
setIsShowDetails(false);
setSelectedWorkflow(null);
}}
/>
<Create
visible={isShowCreate}
onSucceed={() => {
setIsShowCreate(false);
setPageIndex(1);
setRefreshFlag(+new Date());
}}
onClose={() => setIsShowCreate(false)}
/>
</>
);
};
export default Workflows;