| /* |
| * 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; |