components/topic/TopicTable.tsx (234 lines of code) (raw):

/* * 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 { HStack, Input, Table, Thead, Tbody, Tr, Th, Td, TableContainer, useToast, Box, Button, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useDisclosure, } from '@chakra-ui/react'; import axios from 'axios'; import { useContext, useEffect, useState } from 'react'; import { AppContext } from '../../context/context'; interface Topic { name: string, messageCount: number, } interface TopicProps { name: string, messageCount: number, } interface CreateTopicRequest { name: string, } interface RemoveTopicRequest { name: string, } const CreateTopicModal = () => { const { state } = useContext(AppContext); const { isOpen, onOpen, onClose } = useDisclosure(); const [topicName, setTopicName] = useState(''); const handleTopicNameChange = (event: React.FormEvent<HTMLInputElement>) => { setTopicName(event.currentTarget.value); }; const toast = useToast(); const [loading, setLoading] = useState(false); const onCreateClick = async () => { try { setLoading(true); await axios.post<CreateTopicRequest>(`${state.endpoint}/topic`, { name: topicName, }); onClose(); } catch (error) { if (axios.isAxiosError(error)) { toast({ title: 'Failed to create the topic', description: error.message, status: 'error', duration: 3000, isClosable: true, }); } } finally { setLoading(false); } }; return ( <> <Button colorScheme="blue" onClick={onOpen} > Create Topic </Button> <Modal isOpen={isOpen} onClose={onClose}> <ModalOverlay /> <ModalContent> <ModalHeader>Create Topic</ModalHeader> <ModalCloseButton /> <ModalBody> <Input placeholder="Topic Name" value={topicName} onChange={handleTopicNameChange} /> </ModalBody> <ModalFooter> <Button mr={2} onClick={onClose} > Close </Button> <Button colorScheme="blue" onClick={onCreateClick} isLoading={loading} isDisabled={topicName.length === 0} > Create </Button> </ModalFooter> </ModalContent> </Modal> </> ); }; const TopicRow = ({ name, messageCount, }: TopicProps) => { const { state } = useContext(AppContext); const toast = useToast(); const [loading, setLoading] = useState(false); const onRemoveClick = async () => { try { setLoading(true); await axios.delete<RemoveTopicRequest>(`${state.endpoint}/topic`, { data: { name, }, }); setLoading(false); } catch (error) { if (axios.isAxiosError(error)) { toast({ title: 'Failed to remove the topic', description: error.message, status: 'error', duration: 3000, isClosable: true, }); } } }; return ( <Tr> <Td>{name}</Td> <Td>{messageCount}</Td> <Td> <HStack> <Button colorScheme="red" isLoading={loading} onClick={onRemoveClick} > Remove </Button> </HStack> </Td> </Tr> ); }; const TopicTable = () => { const { state } = useContext(AppContext); const [searchInput, setSearchInput] = useState<string>(''); const handleSearchInputChange = (event: React.FormEvent<HTMLInputElement>) => { setSearchInput(event.currentTarget.value); }; const [topicList, setTopicList] = useState<Topic[]>([]); const toast = useToast(); useEffect(() => { const fetch = async () => { try { const { data } = await axios.get<Topic[]>(`${state.endpoint}/topic`); setTopicList(data); } catch (error) { if (axios.isAxiosError(error)) { toast({ title: 'Failed to fetch the list of topics', description: 'Unable to connect to the EventMesh daemon', status: 'error', duration: 3000, isClosable: true, }); setTopicList([]); } } }; fetch(); }, []); return ( <Box maxW="full" bg="white" borderWidth="1px" borderRadius="md" overflow="hidden" p="4" > <HStack spacing="2" > <Input w="100%" placeholder="Search" value={searchInput} onChange={handleSearchInputChange} /> <CreateTopicModal /> </HStack> <TableContainer> <Table variant="simple"> <Thead> <Tr> <Th>Topic Name</Th> <Th>Message Count</Th> <Th>Action</Th> </Tr> </Thead> <Tbody> {topicList.filter(({ name, }) => { if (searchInput && !name.includes(searchInput)) { return false; } return true; }).map(({ name, messageCount, }) => ( <TopicRow name={name} messageCount={messageCount} /> ))} </Tbody> </Table> </TableContainer> </Box> ); }; export default TopicTable;