transfer dashboard from parent
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c87e042
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
+
+[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
+
+The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
diff --git a/components/client/GrpcClientTable.tsx b/components/client/GrpcClientTable.tsx
new file mode 100644
index 0000000..1ffdfea
--- /dev/null
+++ b/components/client/GrpcClientTable.tsx
@@ -0,0 +1,217 @@
+/*
+ * 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,
+  Select,
+  Input,
+  Table,
+  Thead,
+  Tbody,
+  Tr,
+  Th,
+  Td,
+  TableContainer,
+  useToast,
+  Box,
+  Button,
+} from '@chakra-ui/react';
+import axios from 'axios';
+import { useContext, useEffect, useState } from 'react';
+import { AppContext } from '../../context/context';
+
+interface GrpcClient {
+  env: string,
+  subsystem: string,
+  url: string,
+  pid: number,
+  host: string,
+  port: number,
+  version: string,
+  idc: string,
+  group: string,
+  purpose: string,
+  protocol: string,
+}
+
+interface GrpcClientProps {
+  url: string,
+  group: string,
+}
+
+interface RemoveGrpcClientRequest {
+  url: string,
+}
+
+const GrpcClientRow = ({
+  url, group,
+}: GrpcClientProps) => {
+  const { state } = useContext(AppContext);
+
+  const toast = useToast();
+  const [loading, setLoading] = useState(false);
+  const onRemoveClick = async () => {
+    try {
+      setLoading(true);
+      await axios.delete<RemoveGrpcClientRequest>(`${state.endpoint}/client/grpc`, {
+        data: {
+          url,
+        },
+      });
+      setLoading(false);
+    } catch (error) {
+      if (axios.isAxiosError(error)) {
+        toast({
+          title: 'Failed to remove the gRPC Client',
+          description: error.message,
+          status: 'error',
+          duration: 3000,
+          isClosable: true,
+        });
+      }
+    }
+  };
+
+  return (
+    <Tr>
+      <Td>{url}</Td>
+      <Td>{group}</Td>
+      <Td>
+        <HStack>
+          <Button
+            colorScheme="red"
+            isLoading={loading}
+            onClick={onRemoveClick}
+          >
+            Remove
+          </Button>
+        </HStack>
+      </Td>
+    </Tr>
+  );
+};
+
+const GrpcClientTable = () => {
+  const { state } = useContext(AppContext);
+
+  const [searchInput, setSearchInput] = useState<string>('');
+  const handleSearchInputChange = (event: React.FormEvent<HTMLInputElement>) => {
+    setSearchInput(event.currentTarget.value);
+  };
+
+  const [groupSet, setGroupSet] = useState<Set<string>>(new Set());
+  const [groupFilter, setGroupFilter] = useState<string>('');
+  const handleGroupSelectChange = (event: React.FormEvent<HTMLSelectElement>) => {
+    setGroupFilter(event.currentTarget.value);
+  };
+
+  const [GrpcClientList, setGrpcClientList] = useState<GrpcClient[]>([]);
+  const toast = useToast();
+  useEffect(() => {
+    const fetch = async () => {
+      try {
+        const { data } = await axios.get<GrpcClient[]>(`${state.endpoint}/client/grpc`);
+        setGrpcClientList(data);
+
+        const nextGroupSet = new Set<string>();
+        data.forEach(({ group }) => {
+          nextGroupSet.add(group);
+        });
+        setGroupSet(nextGroupSet);
+      } catch (error) {
+        if (axios.isAxiosError(error)) {
+          toast({
+            title: 'Failed to fetch the list of gRPC Clients',
+            description: 'Unable to connect to the EventMesh daemon',
+            status: 'error',
+            duration: 3000,
+            isClosable: true,
+          });
+          setGrpcClientList([]);
+        }
+      }
+    };
+
+    fetch();
+  }, []);
+
+  return (
+    <Box
+      maxW="full"
+      bg="white"
+      borderWidth="1px"
+      borderRadius="md"
+      overflow="hidden"
+      p="4"
+    >
+      <HStack
+        spacing="2"
+      >
+        <Input
+          w="200%"
+          placeholder="Search"
+          value={searchInput}
+          onChange={handleSearchInputChange}
+        />
+        <Select
+          placeholder="Select Group"
+          onChange={handleGroupSelectChange}
+        >
+          {Array.from(groupSet).map((group) => (
+            <option value={group} key={group}>{group}</option>
+          ))}
+        </Select>
+      </HStack>
+
+      <TableContainer>
+        <Table variant="simple">
+          <Thead>
+            <Tr>
+              <Th>URL</Th>
+              <Th>Group</Th>
+              <Th>Action</Th>
+            </Tr>
+          </Thead>
+          <Tbody>
+            {GrpcClientList && GrpcClientList.filter(({
+              url, group,
+            }) => {
+              if (searchInput && !url.includes(searchInput)) {
+                return false;
+              }
+              if (groupFilter && groupFilter !== group) {
+                return false;
+              }
+              return true;
+            }).map(({
+              url, group,
+            }) => (
+              <GrpcClientRow
+                url={url}
+                group={group}
+              />
+            ))}
+          </Tbody>
+        </Table>
+      </TableContainer>
+    </Box>
+  );
+};
+
+export default GrpcClientTable;
diff --git a/components/client/HTTPClientTable.tsx b/components/client/HTTPClientTable.tsx
new file mode 100644
index 0000000..832ea91
--- /dev/null
+++ b/components/client/HTTPClientTable.tsx
@@ -0,0 +1,217 @@
+/*
+ * 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,
+  Select,
+  Input,
+  Table,
+  Thead,
+  Tbody,
+  Tr,
+  Th,
+  Td,
+  TableContainer,
+  useToast,
+  Box,
+  Button,
+} from '@chakra-ui/react';
+import axios from 'axios';
+import { useContext, useEffect, useState } from 'react';
+import { AppContext } from '../../context/context';
+
+interface HTTPClient {
+  env: string,
+  subsystem: string,
+  url: string,
+  pid: number,
+  host: string,
+  port: number,
+  version: string,
+  idc: string,
+  group: string,
+  purpose: string,
+  protocol: string,
+}
+
+interface HTTPClientProps {
+  url: string,
+  group: string,
+}
+
+interface RemoveHTTPClientRequest {
+  url: string,
+}
+
+const HTTPClientRow = ({
+  url, group,
+}: HTTPClientProps) => {
+  const { state } = useContext(AppContext);
+
+  const toast = useToast();
+  const [loading, setLoading] = useState(false);
+  const onRemoveClick = async () => {
+    try {
+      setLoading(true);
+      await axios.delete<RemoveHTTPClientRequest>(`${state.endpoint}/client/http`, {
+        data: {
+          url,
+        },
+      });
+      setLoading(false);
+    } catch (error) {
+      if (axios.isAxiosError(error)) {
+        toast({
+          title: 'Failed to remove the HTTP Client',
+          description: error.message,
+          status: 'error',
+          duration: 3000,
+          isClosable: true,
+        });
+      }
+    }
+  };
+
+  return (
+    <Tr>
+      <Td>{url}</Td>
+      <Td>{group}</Td>
+      <Td>
+        <HStack>
+          <Button
+            colorScheme="red"
+            isLoading={loading}
+            onClick={onRemoveClick}
+          >
+            Remove
+          </Button>
+        </HStack>
+      </Td>
+    </Tr>
+  );
+};
+
+const HTTPClientTable = () => {
+  const { state } = useContext(AppContext);
+
+  const [searchInput, setSearchInput] = useState<string>('');
+  const handleSearchInputChange = (event: React.FormEvent<HTMLInputElement>) => {
+    setSearchInput(event.currentTarget.value);
+  };
+
+  const [groupSet, setGroupSet] = useState<Set<string>>(new Set());
+  const [groupFilter, setGroupFilter] = useState<string>('');
+  const handleGroupSelectChange = (event: React.FormEvent<HTMLSelectElement>) => {
+    setGroupFilter(event.currentTarget.value);
+  };
+
+  const [HTTPClientList, setHTTPClientList] = useState<HTTPClient[]>([]);
+  const toast = useToast();
+  useEffect(() => {
+    const fetch = async () => {
+      try {
+        const { data } = await axios.get<HTTPClient[]>(`${state.endpoint}/client/http`);
+        setHTTPClientList(data);
+
+        const nextGroupSet = new Set<string>();
+        data.forEach(({ group }) => {
+          nextGroupSet.add(group);
+        });
+        setGroupSet(nextGroupSet);
+      } catch (error) {
+        if (axios.isAxiosError(error)) {
+          toast({
+            title: 'Failed to fetch the list of HTTP Clients',
+            description: 'Unable to connect to the EventMesh daemon',
+            status: 'error',
+            duration: 3000,
+            isClosable: true,
+          });
+          setHTTPClientList([]);
+        }
+      }
+    };
+
+    fetch();
+  }, []);
+
+  return (
+    <Box
+      maxW="full"
+      bg="white"
+      borderWidth="1px"
+      borderRadius="md"
+      overflow="hidden"
+      p="4"
+    >
+      <HStack
+        spacing="2"
+      >
+        <Input
+          w="200%"
+          placeholder="Search"
+          value={searchInput}
+          onChange={handleSearchInputChange}
+        />
+        <Select
+          placeholder="Select Group"
+          onChange={handleGroupSelectChange}
+        >
+          {Array.from(groupSet).map((group) => (
+            <option value={group} key={group}>{group}</option>
+          ))}
+        </Select>
+      </HStack>
+
+      <TableContainer>
+        <Table variant="simple">
+          <Thead>
+            <Tr>
+              <Th>URL</Th>
+              <Th>Group</Th>
+              <Th>Action</Th>
+            </Tr>
+          </Thead>
+          <Tbody>
+            {HTTPClientList && HTTPClientList.filter(({
+              url, group,
+            }) => {
+              if (searchInput && !url.includes(searchInput)) {
+                return false;
+              }
+              if (groupFilter && groupFilter !== group) {
+                return false;
+              }
+              return true;
+            }).map(({
+              url, group,
+            }) => (
+              <HTTPClientRow
+                url={url}
+                group={group}
+              />
+            ))}
+          </Tbody>
+        </Table>
+      </TableContainer>
+    </Box>
+  );
+};
+
+export default HTTPClientTable;
diff --git a/components/client/TCPClientTable.tsx b/components/client/TCPClientTable.tsx
new file mode 100644
index 0000000..dc24589
--- /dev/null
+++ b/components/client/TCPClientTable.tsx
@@ -0,0 +1,223 @@
+/*
+ * 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,
+  Select,
+  Input,
+  Table,
+  Thead,
+  Tbody,
+  Tr,
+  Th,
+  Td,
+  TableContainer,
+  useToast,
+  Box,
+  Button,
+} from '@chakra-ui/react';
+import axios from 'axios';
+import { useContext, useEffect, useState } from 'react';
+import { AppContext } from '../../context/context';
+
+interface TCPClient {
+  env: string,
+  subsystem: string,
+  url: string,
+  pid: number,
+  host: string,
+  port: number,
+  version: string,
+  idc: string,
+  group: string,
+  purpose: string,
+  protocol: string,
+}
+
+interface TCPClientProps {
+  host: string,
+  port: number,
+  group: string,
+}
+
+interface RemoveTCPClientRequest {
+  host: string,
+  port: number,
+}
+
+const TCPClientRow = ({
+  host, port, group,
+}: TCPClientProps) => {
+  const { state } = useContext(AppContext);
+
+  const toast = useToast();
+  const [loading, setLoading] = useState(false);
+  const onRemoveClick = async () => {
+    try {
+      setLoading(true);
+      await axios.delete<RemoveTCPClientRequest>(`${state.endpoint}/client/tcp`, {
+        data: {
+          host,
+          port,
+        },
+      });
+      setLoading(false);
+    } catch (error) {
+      if (axios.isAxiosError(error)) {
+        toast({
+          title: 'Failed to remove the TCP Client',
+          description: error.message,
+          status: 'error',
+          duration: 3000,
+          isClosable: true,
+        });
+      }
+    }
+  };
+
+  return (
+    <Tr>
+      <Td>{`${host}:${port}`}</Td>
+      <Td>{group}</Td>
+      <Td>
+        <HStack>
+          <Button
+            colorScheme="red"
+            isLoading={loading}
+            onClick={onRemoveClick}
+          >
+            Remove
+          </Button>
+        </HStack>
+      </Td>
+    </Tr>
+  );
+};
+
+const TCPClientTable = () => {
+  const { state } = useContext(AppContext);
+
+  const [searchInput, setSearchInput] = useState<string>('');
+  const handleSearchInputChange = (event: React.FormEvent<HTMLInputElement>) => {
+    setSearchInput(event.currentTarget.value);
+  };
+
+  const [groupSet, setGroupSet] = useState<Set<string>>(new Set());
+  const [groupFilter, setGroupFilter] = useState<string>('');
+  const handleGroupSelectChange = (event: React.FormEvent<HTMLSelectElement>) => {
+    setGroupFilter(event.currentTarget.value);
+  };
+
+  const [TCPClientList, setTCPClientList] = useState<TCPClient[]>([]);
+  const toast = useToast();
+  useEffect(() => {
+    const fetch = async () => {
+      try {
+        const { data } = await axios.get<TCPClient[]>(`${state.endpoint}/client/tcp`);
+        setTCPClientList(data);
+
+        const nextGroupSet = new Set<string>();
+        data.forEach(({ group }) => {
+          nextGroupSet.add(group);
+        });
+        setGroupSet(nextGroupSet);
+      } catch (error) {
+        if (axios.isAxiosError(error)) {
+          toast({
+            title: 'Failed to fetch the list of TCP Clients',
+            description: 'Unable to connect to the EventMesh daemon',
+            status: 'error',
+            duration: 3000,
+            isClosable: true,
+          });
+          setTCPClientList([]);
+        }
+      }
+    };
+
+    fetch();
+  }, []);
+
+  return (
+    <Box
+      maxW="full"
+      bg="white"
+      borderWidth="1px"
+      borderRadius="md"
+      overflow="hidden"
+      p="4"
+    >
+      <HStack
+        spacing="2"
+      >
+        <Input
+          w="200%"
+          placeholder="Search"
+          value={searchInput}
+          onChange={handleSearchInputChange}
+        />
+        <Select
+          placeholder="Select Group"
+          onChange={handleGroupSelectChange}
+        >
+          {Array.from(groupSet).map((group) => (
+            <option value={group} key={group}>{group}</option>
+          ))}
+        </Select>
+      </HStack>
+
+      <TableContainer>
+        <Table variant="simple">
+          <Thead>
+            <Tr>
+              <Th>Host</Th>
+              <Th>Host</Th>
+              <Th>Group</Th>
+              <Th>Action</Th>
+            </Tr>
+          </Thead>
+          <Tbody>
+            {TCPClientList && TCPClientList.filter(({
+              host, port, group,
+            }) => {
+              const address = `${host}:${port}`;
+              if (searchInput && !address.includes(searchInput)) {
+                return false;
+              }
+              if (groupFilter && groupFilter !== group) {
+                return false;
+              }
+              return true;
+            }).map(({
+              host, port, group,
+            }) => (
+              <TCPClientRow
+                host={host}
+                port={port}
+                group={group}
+              />
+            ))}
+          </Tbody>
+        </Table>
+      </TableContainer>
+    </Box>
+  );
+};
+
+export default TCPClientTable;
diff --git a/components/event/EventTable.tsx b/components/event/EventTable.tsx
new file mode 100644
index 0000000..b0d0a13
--- /dev/null
+++ b/components/event/EventTable.tsx
@@ -0,0 +1,371 @@
+/*
+ * 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,
+  Select,
+  VStack,
+  Textarea,
+} from '@chakra-ui/react';
+import axios from 'axios';
+import { useContext, useEffect, useState } from 'react';
+import { CloudEvent } from 'cloudevents';
+import { AppContext } from '../../context/context';
+
+interface Topic {
+  name: string,
+  messageCount: number,
+}
+
+interface EventProps {
+  event: CloudEvent<string>,
+}
+
+interface CreateEventRequest {
+  event: CloudEvent<string>,
+}
+
+const CreateEventModal = () => {
+  const { state } = useContext(AppContext);
+
+  const { isOpen, onOpen, onClose } = useDisclosure();
+
+  const [id, setId] = useState('');
+  const handleIdChange = (event: React.FormEvent<HTMLInputElement>) => {
+    setId(event.currentTarget.value);
+  };
+
+  const [source, setSource] = useState('');
+  const handleSourceChange = (event: React.FormEvent<HTMLInputElement>) => {
+    setSource(event.currentTarget.value);
+  };
+
+  const [subject, setSubject] = useState('');
+  const handleSubjectChange = (event: React.FormEvent<HTMLInputElement>) => {
+    setSubject(event.currentTarget.value);
+  };
+
+  const [type, setType] = useState('');
+  const handleTypeChange = (event: React.FormEvent<HTMLInputElement>) => {
+    setType(event.currentTarget.value);
+  };
+
+  const [data, setData] = useState('');
+  const handleDataChange = (event: React.FormEvent<HTMLInputElement>) => {
+    setData(event.currentTarget.value);
+  };
+
+  const toast = useToast();
+  const [loading, setLoading] = useState(false);
+  const onCreateClick = async () => {
+    try {
+      setLoading(true);
+      await axios.post<CreateEventRequest>(`${state.endpoint}/event`, new CloudEvent({
+        source,
+        subject,
+        type,
+        data,
+        specversion: '1.0',
+      }));
+      onClose();
+    } catch (error) {
+      if (axios.isAxiosError(error)) {
+        toast({
+          title: 'Failed to publish the event',
+          description: error.message,
+          status: 'error',
+          duration: 3000,
+          isClosable: true,
+        });
+      }
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <>
+      <Button
+        w="25%"
+        colorScheme="blue"
+        onClick={onOpen}
+      >
+        Create Event
+      </Button>
+      <Modal isOpen={isOpen} onClose={onClose}>
+        <ModalOverlay />
+        <ModalContent>
+          <ModalHeader>Create Event</ModalHeader>
+          <ModalCloseButton />
+          <ModalBody>
+            <VStack>
+              <Input
+                placeholder="Event ID"
+                value={id}
+                onChange={handleIdChange}
+              />
+              <Input
+                placeholder="Event Source"
+                value={source}
+                onChange={handleSourceChange}
+              />
+              <Input
+                placeholder="Event Subject"
+                value={subject}
+                onChange={handleSubjectChange}
+              />
+              <Input
+                placeholder="Event Type"
+                value={type}
+                onChange={handleTypeChange}
+              />
+              <Input
+                placeholder="Event Data"
+                value={data}
+                onChange={handleDataChange}
+              />
+            </VStack>
+          </ModalBody>
+
+          <ModalFooter>
+            <Button
+              mr={2}
+              onClick={onClose}
+            >
+              Close
+            </Button>
+            <Button
+              colorScheme="blue"
+              onClick={onCreateClick}
+              isLoading={loading}
+              isDisabled={
+                id.length === 0 || subject.length === 0 || source.length === 0 || type.length === 0
+              }
+            >
+              Create
+            </Button>
+          </ModalFooter>
+        </ModalContent>
+      </Modal>
+    </>
+  );
+};
+
+const EventRow = ({
+  event,
+}: EventProps) => {
+  const { isOpen, onOpen, onClose } = useDisclosure();
+  const eventDataBase64 = event.data_base64 || '';
+  const eventData = Buffer.from(eventDataBase64, 'base64').toString('utf-8');
+
+  return (
+    <>
+      <Modal isOpen={isOpen} onClose={onClose}>
+        <ModalOverlay />
+        <ModalContent>
+          <ModalHeader>Event Data</ModalHeader>
+          <ModalCloseButton />
+          <ModalBody>
+            <Box>
+              <Textarea isDisabled value={eventData} />
+            </Box>
+          </ModalBody>
+
+          <ModalFooter>
+            <Button
+              mr={2}
+              onClick={onClose}
+            >
+              Close
+            </Button>
+          </ModalFooter>
+        </ModalContent>
+      </Modal>
+
+      <Tr>
+        <Td>{event.id}</Td>
+        <Td>{event.subject}</Td>
+        <Td>{new Date(Number(event.reqc2eventmeshtimestamp)).toLocaleString()}</Td>
+        <Td>
+          <HStack>
+            <Button
+              colorScheme="blue"
+              onClick={onOpen}
+            >
+              View Data
+            </Button>
+          </HStack>
+
+        </Td>
+      </Tr>
+    </>
+  );
+};
+
+const EventTable = () => {
+  const { state } = useContext(AppContext);
+
+  const [searchInput, setSearchInput] = useState<string>('');
+  const handleSearchInputChange = (event: React.FormEvent<HTMLInputElement>) => {
+    setSearchInput(event.currentTarget.value);
+  };
+
+  const [eventList, setEventList] = useState<CloudEvent<string>[]>([]);
+  const [topicList, setTopicList] = useState<Topic[]>([]);
+  const [topic, setTopic] = useState<Topic>({
+    name: '',
+    messageCount: 0,
+  });
+  const handleTopicChange = (event: React.FormEvent<HTMLSelectElement>) => {
+    setTopic({
+      name: event.currentTarget.value,
+      messageCount: 0,
+    });
+  };
+
+  const toast = useToast();
+
+  useEffect(() => {
+    const fetch = async () => {
+      try {
+        const { data } = await axios.get<Topic[]>(`${state.endpoint}/topic`);
+        setTopicList(data);
+        if (data.length !== 0) {
+          setTopic(data[0]);
+        }
+      } catch (error) {
+        if (axios.isAxiosError(error)) {
+          toast({
+            title: 'Failed to fetch the list of events',
+            description: 'unable to connect to the EventMesh daemon',
+            status: 'error',
+            duration: 3000,
+            isClosable: true,
+          });
+          setEventList([]);
+        }
+      }
+    };
+
+    fetch();
+  }, []);
+
+  useEffect(() => {
+    const fetch = async () => {
+      try {
+        if (topic.name !== '') {
+          const eventResponse = await axios.get<string[]>(`${state.endpoint}/event`, {
+            params: {
+              topicName: topic.name,
+              offset: 0,
+              length: 15,
+            },
+          });
+          setEventList(eventResponse.data.map((rawEvent) => JSON.parse(rawEvent)));
+        }
+      } catch (error) {
+        if (axios.isAxiosError(error)) {
+          toast({
+            title: 'Failed to fetch the list of events',
+            description: 'Unable to connect to the EventMesh daemon',
+            status: 'error',
+            duration: 3000,
+            isClosable: true,
+          });
+          setEventList([]);
+        }
+      }
+    };
+
+    fetch();
+  }, [topic]);
+
+  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}
+        />
+        <Select
+          w="100%"
+          onChange={handleTopicChange}
+        >
+          {topicList.map(({ name }) => (
+            <option value={name} key={name} selected={topic.name === name}>{name}</option>
+          ))}
+        </Select>
+        <CreateEventModal />
+      </HStack>
+
+      <TableContainer>
+        <Table variant="simple">
+          <Thead>
+            <Tr>
+              <Th>Event Id</Th>
+              <Th>Event Subject</Th>
+              <Th>Event Time</Th>
+              <Th>Action</Th>
+            </Tr>
+          </Thead>
+          <Tbody>
+            {eventList.filter(() => true).map((event) => (
+              <EventRow
+                key={event.id}
+                event={event}
+              />
+            ))}
+          </Tbody>
+        </Table>
+      </TableContainer>
+    </Box>
+  );
+};
+
+export default EventTable;
diff --git a/components/eventCatalogs/Create.tsx b/components/eventCatalogs/Create.tsx
new file mode 100644
index 0000000..7d75f4b
--- /dev/null
+++ b/components/eventCatalogs/Create.tsx
@@ -0,0 +1,137 @@
+/*
+ * 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, { FC, useRef, useState } from 'react';
+
+import {
+  Drawer,
+  DrawerContent,
+  DrawerCloseButton,
+  DrawerOverlay,
+  DrawerHeader,
+  DrawerBody,
+  DrawerFooter,
+  Button,
+  Box,
+  useToast,
+  Spinner,
+} from '@chakra-ui/react';
+import Editor, { Monaco } from '@monaco-editor/react';
+import axios from 'axios';
+
+const ApiRoot = process.env.NEXT_PUBLIC_EVENTCATALOG_API_ROOT;
+
+const Create: FC<{ visible: boolean; onClose: () => void; onSucceed:()=>void
+}> = ({ visible = false, onClose, onSucceed }) => {
+  const toast = useToast();
+  const [isSubmitting, setIsSubmitting] = useState(false);
+  const editorRef = useRef<Monaco | null>(null);
+  const defaultEditorValue = '# Your code goes here';
+
+  const handleEditorDidMount = (editor: any) => {
+    // here is the editor instance
+    // you can store it in `useRef` for further usage
+    editorRef.current = editor;
+  };
+
+  const onSubmit = () => {
+    setIsSubmitting(true);
+
+    try {
+      const value = editorRef.current.getValue();
+      if (value === defaultEditorValue) {
+        toast({
+          title: 'Invalid definition',
+          description: 'Please input your workflow definition properly',
+          status: 'warning',
+          position: 'top-right',
+        });
+        setIsSubmitting(false);
+        return;
+      }
+      axios
+        .post(
+          `${ApiRoot}/catalog`,
+          { event: { definition: value } },
+          {
+            headers: {
+              'Content-Type': 'application/json',
+            },
+          },
+        )
+        .then(() => {
+          toast({
+            title: 'Succeeded',
+            status: 'success',
+            position: 'top-right',
+          });
+          onSucceed();
+          setIsSubmitting(false);
+        })
+        .catch((error) => {
+          toast({
+            title: 'Failed',
+            description: error.response.data,
+            status: 'error',
+            position: 'top-right',
+          });
+          setIsSubmitting(false);
+        });
+    } catch (error) {
+      setIsSubmitting(false);
+    }
+  };
+
+  return (
+    <Drawer
+      isOpen={visible}
+      size="xl"
+      placement="right"
+      onClose={() => onClose()}
+    >
+      <DrawerOverlay />
+      <DrawerContent>
+        <DrawerCloseButton />
+        <DrawerHeader>Create Catalog</DrawerHeader>
+        <DrawerBody>
+          <Box height="full">
+            <Editor
+              height="100%"
+              defaultLanguage="yaml"
+              defaultValue="# Your code goes here"
+              onMount={handleEditorDidMount}
+              theme="vs-dark"
+            />
+          </Box>
+        </DrawerBody>
+        <DrawerFooter justifyContent="flex-start">
+          <Button colorScheme="blue" mr={3} onClick={onSubmit}>
+            {isSubmitting ? <Spinner colorScheme="white" size="sm" /> : 'Submit'}
+          </Button>
+          <Button variant="ghost" colorScheme="blue" onClick={onClose}>
+            Cancel
+          </Button>
+        </DrawerFooter>
+      </DrawerContent>
+
+    </Drawer>
+  );
+};
+
+export default Create;
diff --git a/components/eventCatalogs/Details.tsx b/components/eventCatalogs/Details.tsx
new file mode 100644
index 0000000..b4d33ee
--- /dev/null
+++ b/components/eventCatalogs/Details.tsx
@@ -0,0 +1,121 @@
+/*
+ * 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, { FC, useRef } from 'react';
+
+import {
+  Drawer,
+  DrawerContent,
+  DrawerCloseButton,
+  DrawerOverlay,
+  DrawerHeader,
+  DrawerBody,
+  FormLabel,
+  Box,
+  Flex,
+  Text,
+  Stack,
+  Badge,
+} from '@chakra-ui/react';
+import moment from 'moment';
+import Editor, { Monaco } from '@monaco-editor/react';
+import { EventCatalogType } from './types';
+// import { WorkflowStatusMap } from './constant';
+
+const Details: FC<{
+  visible: boolean;
+  data?: EventCatalogType | undefined;
+  onClose: () => void;
+}> = ({ visible = false, data, onClose = () => {} }) => {
+  const editorRef = useRef(null);
+  const handleEditorDidMount = (editor: Monaco) => {
+    editorRef.current = editor;
+    editor.setValue(data?.definition ?? '');
+  };
+
+  return (
+    <Drawer
+      isOpen={visible}
+      size="xl"
+      placement="right"
+      onClose={() => onClose()}
+    >
+      <DrawerOverlay />
+      <DrawerContent>
+        <DrawerCloseButton />
+        <DrawerHeader>
+          {data?.title}
+          <Badge ml={2}>
+            Version
+            {' '}
+            {data?.version}
+          </Badge>
+        </DrawerHeader>
+        <DrawerBody>
+          <Flex flexDirection="column" h="full">
+            <Stack direction="row">
+              <Flex width="240px" flexDirection="column">
+                <Box mb="1">
+                  <FormLabel opacity={0.5}>Catalog ID</FormLabel>
+                  <Text>{data?.id}</Text>
+                </Box>
+                <Box mt="1" mb="3">
+                  <FormLabel opacity={0.5}>Title</FormLabel>
+                  <Text>{data?.title}</Text>
+                </Box>
+                <Box mt="1" mb="3">
+                  <FormLabel opacity={0.5}>File Name</FormLabel>
+                  <Text>{data?.file_name}</Text>
+                </Box>
+              </Flex>
+              <Flex flexDirection="column">
+                <Box mb="1">
+                  <FormLabel opacity={0.5}>Created At</FormLabel>
+                  <Text>
+                    {moment(data?.create_time).format('YYYY-MM-DD HH:mm:ss')}
+                  </Text>
+                </Box>
+                <Box mt="1" mb="3">
+                  <FormLabel opacity={0.5}>Updated At</FormLabel>
+                  <Text>
+                    {moment(data?.update_time).format('YYYY-MM-DD HH:mm:ss')}
+                  </Text>
+                </Box>
+              </Flex>
+            </Stack>
+            <Box flex={1}>
+              <Editor
+                height="100%"
+                defaultLanguage="yaml"
+                defaultValue="# Your code goes here"
+                onMount={handleEditorDidMount}
+                theme="vs-dark"
+                options={{ readOnly: true }}
+              />
+            </Box>
+          </Flex>
+        </DrawerBody>
+      </DrawerContent>
+    </Drawer>
+  );
+};
+Details.defaultProps = {
+  data: undefined,
+};
+export default Details;
diff --git a/components/eventCatalogs/constant.ts b/components/eventCatalogs/constant.ts
new file mode 100644
index 0000000..df9cfab
--- /dev/null
+++ b/components/eventCatalogs/constant.ts
@@ -0,0 +1,27 @@
+/*
+ * 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 { CatalogStatusEnum } from './types';
+
+export const WorkflowStatusMap = new Map([
+  [CatalogStatusEnum.Normal, 'Normal'],
+  [CatalogStatusEnum.Deleted, 'Deleted'],
+]);
+
+export default WorkflowStatusMap;
diff --git a/components/eventCatalogs/types.ts b/components/eventCatalogs/types.ts
new file mode 100644
index 0000000..8ab7004
--- /dev/null
+++ b/components/eventCatalogs/types.ts
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+export type SchemaTypes = {
+  schemaId: string;
+  lastVersion: string;
+  description: string;
+};
+
+export type EventCatalogType = {
+  create_time: string,
+  definition: string,
+  file_name: string,
+  id: 0,
+  status: 0,
+  title: string,
+  update_time: string,
+  version: string
+};
+
+export enum CatalogStatusEnum {
+  'Normal' = 1,
+  'Deleted' = -1,
+}
diff --git a/components/index/Configuration.tsx b/components/index/Configuration.tsx
new file mode 100755
index 0000000..8258db2
--- /dev/null
+++ b/components/index/Configuration.tsx
@@ -0,0 +1,249 @@
+/*
+ * 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 {
+  Text,
+  Table,
+  TableContainer,
+  Tbody,
+  Th,
+  Thead,
+  Tr,
+  Td,
+  Box,
+} from '@chakra-ui/react';
+import axios from 'axios';
+import React, { useContext, useEffect, useState } from 'react';
+import { AppContext } from '../../context/context';
+
+interface EventMeshConfiguration {
+  sysID: string;
+  namesrvAddr: string;
+  eventMeshEnv: string;
+  eventMeshIDC: string;
+  eventMeshCluster: string;
+  eventMeshServerIp: string;
+  eventMeshName: string;
+  eventMeshWebhookOrigin: string;
+  eventMeshServerSecurityEnable: boolean;
+  eventMeshServerRegistryEnable: boolean;
+
+  eventMeshTcpServerPort: number;
+  eventMeshTcpServerEnabled: boolean;
+
+  eventMeshHttpServerPort: number;
+  eventMeshHttpServerUseTls: boolean;
+
+  eventMeshGrpcServerPort: number;
+  eventMeshGrpcServerUseTls: boolean;
+}
+
+const Configuration = () => {
+  const { state } = useContext(AppContext);
+  const [configuration, setConfiguration] = useState<
+  Partial<EventMeshConfiguration>
+  >({});
+
+  useEffect(() => {
+    const controller = new AbortController();
+    const fetch = async () => {
+      try {
+        const { data } = await axios.get<EventMeshConfiguration>(
+          `${state.endpoint}/configuration`,
+          {
+            signal: controller.signal,
+          },
+        );
+        setConfiguration(data);
+      } catch (error) {
+        setConfiguration({});
+      }
+    };
+
+    fetch();
+
+    return () => {
+      controller.abort();
+    };
+  }, [state.endpoint]);
+
+  type ConfigurationRecord = Record<
+  string,
+  string | number | boolean | undefined
+  >;
+  const commonConfiguration: ConfigurationRecord = {
+    'System ID': configuration.sysID,
+    'NameServer Address': configuration.namesrvAddr,
+    'EventMesh Environment': configuration.eventMeshEnv,
+    'EventMesh IDC': configuration.eventMeshIDC,
+    'EventMesh Cluster': configuration.eventMeshCluster,
+    'EventMesh Server IP': configuration.eventMeshServerIp,
+    'EventMEsh Name': configuration.eventMeshName,
+    'EventMesh Webhook Origin': configuration.eventMeshWebhookOrigin,
+    'EventMesh Server Security Enable':
+      configuration.eventMeshServerSecurityEnable,
+    'EventMesh Server Registry Enable':
+      configuration.eventMeshServerRegistryEnable,
+  };
+
+  const tcpConfiguration: ConfigurationRecord = {
+    'TCP Server Port': configuration.eventMeshTcpServerPort,
+    'TCP Server Enabled': configuration.eventMeshTcpServerEnabled,
+  };
+
+  const httpConfiguration: ConfigurationRecord = {
+    'HTTP Server Port': configuration.eventMeshHttpServerPort,
+    'HTTP Server TLS Enabled': configuration.eventMeshHttpServerUseTls,
+  };
+
+  const grpcConfiguration: ConfigurationRecord = {
+    'gRPC Server Port': configuration.eventMeshGrpcServerPort,
+    'gRPC Server TLS Enabled': configuration.eventMeshGrpcServerUseTls,
+  };
+
+  const convertConfigurationToTable = (
+    configurationRecord: Record<string, string | number | boolean | undefined>,
+  ) => Object.entries(configurationRecord).map(([key, value]) => {
+    if (value === undefined) {
+      return (
+        <Tr>
+          <Td>{key}</Td>
+          <Td>Undefined</Td>
+        </Tr>
+      );
+    }
+
+    return (
+      <Tr>
+        <Td>{key}</Td>
+        <Td>{value.toString()}</Td>
+      </Tr>
+    );
+  });
+
+  if (Object.keys(configuration).length === 0) {
+    return (
+      <Box
+        maxW="full"
+        bg="white"
+        borderWidth="2px"
+        borderRadius="md"
+        borderColor="rgb(211,85,25)"
+        overflow="hidden"
+        p="4"
+        mt="4"
+        opacity="0.8"
+      >
+        <Text
+          fontSize="l"
+          fontWeight="semibold"
+          color="rgb(211,85,25)"
+          textAlign={['left', 'center']}
+        >
+          EventMesh Daemon Not Connected
+        </Text>
+      </Box>
+    );
+  }
+
+  return (
+    <>
+      <Box
+        maxW="full"
+        bg="white"
+        borderWidth="1px"
+        borderRadius="md"
+        overflow="hidden"
+        p="4"
+        mt="4"
+      >
+        <Text w="full">EventMesh Configuration</Text>
+
+        <TableContainer mt="4">
+          <Table variant="simple">
+            <Thead>
+              <Tr>
+                <Th>Configuration Field</Th>
+                <Th>Value</Th>
+              </Tr>
+            </Thead>
+            <Tbody>{convertConfigurationToTable(commonConfiguration)}</Tbody>
+          </Table>
+        </TableContainer>
+      </Box>
+      <Box
+        maxW="full"
+        bg="white"
+        borderWidth="1px"
+        borderRadius="md"
+        overflow="hidden"
+        p="4"
+        mt="4"
+      >
+        <Text w="full">TCP Configuration</Text>
+
+        <TableContainer mt="4">
+          <Table variant="simple">
+            <Thead>
+              <Tr>
+                <Th>Configuration Field</Th>
+                <Th>Value</Th>
+              </Tr>
+            </Thead>
+            <Tbody>{convertConfigurationToTable(tcpConfiguration)}</Tbody>
+          </Table>
+        </TableContainer>
+
+        <Text w="full" mt="4">
+          HTTP Configuration
+        </Text>
+
+        <TableContainer mt="4">
+          <Table variant="simple">
+            <Thead>
+              <Tr>
+                <Th>Configuration Field</Th>
+                <Th>Value</Th>
+              </Tr>
+            </Thead>
+            <Tbody>{convertConfigurationToTable(httpConfiguration)}</Tbody>
+          </Table>
+        </TableContainer>
+
+        <Text w="full" mt="4">
+          gRPC Configuration
+        </Text>
+
+        <TableContainer mt="4">
+          <Table variant="simple">
+            <Thead>
+              <Tr>
+                <Th>Configuration Field</Th>
+                <Th>Value</Th>
+              </Tr>
+            </Thead>
+            <Tbody>{convertConfigurationToTable(grpcConfiguration)}</Tbody>
+          </Table>
+        </TableContainer>
+      </Box>
+    </>
+  );
+};
+
+export default Configuration;
diff --git a/components/index/Endpoint.tsx b/components/index/Endpoint.tsx
new file mode 100755
index 0000000..9e550fa
--- /dev/null
+++ b/components/index/Endpoint.tsx
@@ -0,0 +1,105 @@
+/*
+ * 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,
+  VStack,
+  Button,
+  Text,
+  useToast,
+} from '@chakra-ui/react';
+import axios from 'axios';
+import React, { useContext, useEffect, useState } from 'react';
+import { AppContext } from '../../context/context';
+
+const Endpoint = () => {
+  const { state, dispatch } = useContext(AppContext);
+  const toast = useToast();
+  const [endpointInput, setEndpointInput] = useState('http://localhost:10106');
+  const [loading, setLoading] = useState(false);
+
+  useEffect(() => {
+    setEndpointInput(state.endpoint);
+  }, [state.endpoint]);
+
+  const handleEndpointInputChange = (event: React.FormEvent<HTMLInputElement>) => {
+    setEndpointInput(event.currentTarget.value);
+  };
+
+  const handleSaveButtonClick = async () => {
+    try {
+      setLoading(true);
+      await axios.get(`${endpointInput}/client`);
+      dispatch({
+        type: 'SetEndPointAction',
+        payload: {
+          endpoint: endpointInput,
+        },
+      });
+    } catch (error) {
+      if (axios.isAxiosError(error)) {
+        toast({
+          title: `Failed to connect to ${endpointInput}`,
+          description: error.message,
+          status: 'error',
+          duration: 3000,
+          isClosable: true,
+        });
+      }
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <VStack
+      maxW="full"
+      bg="white"
+      borderWidth="1px"
+      borderRadius="md"
+      overflow="hidden"
+      p="4"
+    >
+      <Text
+        w="full"
+      >
+        EventMesh Admin Endpoint
+      </Text>
+      <HStack
+        w="full"
+      >
+        <Input
+          placeholder="Apache EventMesh Backend Endpoint"
+          value={endpointInput}
+          onChange={handleEndpointInputChange}
+        />
+        <Button
+          colorScheme="blue"
+          isLoading={loading}
+          onClick={handleSaveButtonClick}
+        >
+          Save
+        </Button>
+      </HStack>
+    </VStack>
+  );
+};
+
+export default Endpoint;
diff --git a/components/metrics/MetricsTable.tsx b/components/metrics/MetricsTable.tsx
new file mode 100755
index 0000000..9b80d49
--- /dev/null
+++ b/components/metrics/MetricsTable.tsx
@@ -0,0 +1,313 @@
+/*
+ * 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 {
+  Text,
+  Table,
+  TableContainer,
+  Tbody,
+  Th,
+  Thead,
+  Tr,
+  Td,
+  Box,
+} from '@chakra-ui/react';
+import axios from 'axios';
+import React, { useContext, useEffect, useState } from 'react';
+import { AppContext } from '../../context/context';
+
+interface EventMeshMetrics {
+  maxHTTPTPS: number,
+  avgHTTPTPS: number,
+  maxHTTPCost: number,
+  avgHTTPCost: number,
+  avgHTTPBodyDecodeCost: number,
+  httpDiscard: number,
+  maxBatchSendMsgTPS: number,
+  avgBatchSendMsgTPS: number,
+  sendBatchMsgNumSum: number,
+  sendBatchMsgFailNumSum: number,
+  sendBatchMsgFailRate: number,
+  sendBatchMsgDiscardNumSum: number,
+  maxSendMsgTPS: number,
+  avgSendMsgTPS: number,
+  sendMsgNumSum: number,
+  sendMsgFailNumSum: number,
+  sendMsgFailRate: number,
+  replyMsgNumSum: number,
+  replyMsgFailNumSum: number,
+  maxPushMsgTPS: number,
+  avgPushMsgTPS: number,
+  pushHTTPMsgNumSum: number,
+  pushHTTPMsgFailNumSum: number,
+  pushHTTPMsgFailRate: number,
+  maxHTTPPushLatency : number,
+  avgHTTPPushLatency : number,
+  batchMsgQueueSize : number,
+  sendMsgQueueSize : number,
+  pushMsgQueueSize : number,
+  retryHTTPQueueSize : number,
+  avgBatchSendMsgCost : number,
+  avgSendMsgCost : number,
+  avgReplyMsgCost : number,
+
+  // TCP Metrics
+  retryTCPQueueSize: number,
+  client2eventMeshTCPTPS : number,
+  eventMesh2mqTCPTPS : number,
+  mq2eventMeshTCPTPS : number,
+  eventMesh2clientTCPTPS : number,
+  allTCPTPS : number,
+  allTCPConnections : number,
+  subTopicTCPNum : number
+}
+
+const MetricsTable = () => {
+  const { state } = useContext(AppContext);
+  const [metrics, setMetrics] = useState<Partial<EventMeshMetrics>>({});
+
+  useEffect(() => {
+    const fetch = async () => {
+      try {
+        const { data } = await axios.get<EventMeshMetrics>(`${state.endpoint}/metrics`);
+        setMetrics(data);
+      } catch (error) {
+        setMetrics({});
+      }
+    };
+
+    fetch();
+  }, []);
+
+  type MetricRecord = Record<string, number | undefined>;
+  const httpMetrics: MetricRecord = {
+    'Max HTTP TPS': metrics.maxHTTPTPS,
+    'Avg HTTP TPS': metrics.avgHTTPTPS,
+    'Max HTTP Cost': metrics.maxHTTPCost,
+    'Avg HTTP Cost': metrics.avgHTTPCost,
+    'Avg HTTP Body Decode Cost': metrics.avgHTTPBodyDecodeCost,
+    'HTTP Discard': metrics.httpDiscard,
+  };
+  const batchMetrics: MetricRecord = {
+    'Max Batch Send Msg TPS': metrics.maxBatchSendMsgTPS,
+    'Avg Batch Send Msg TPS': metrics.avgBatchSendMsgTPS,
+    'Send Batch Msg Num Sum': metrics.sendBatchMsgNumSum,
+    'Send Batch Msg Fail Num Sum': metrics.sendBatchMsgFailNumSum,
+    'Send Batch Msg Fail Rate': metrics.sendBatchMsgFailRate,
+    'Send Batch Msg Discard Num Sum': metrics.sendBatchMsgDiscardNumSum,
+  };
+  const sendMetrics: MetricRecord = {
+    'Max Send Msg TPS': metrics.maxSendMsgTPS,
+    'Avg Send Msg TPS': metrics.avgSendMsgTPS,
+    'Send Msg Num Sum': metrics.sendMsgNumSum,
+    'Send Msg Fail Num Sum': metrics.sendMsgFailNumSum,
+    'Send Msg Fail Rate': metrics.sendMsgFailRate,
+    'Reply Msg Num Sum': metrics.replyMsgNumSum,
+    'Reply Msg Fail Num Sum': metrics.replyMsgFailNumSum,
+  };
+  const pushMetrics: MetricRecord = {
+    'Max Push Msg TPS': metrics.maxPushMsgTPS,
+    'Avg Push Msg TPS': metrics.avgPushMsgTPS,
+    'Push HTTP Msg Num Sum': metrics.pushHTTPMsgNumSum,
+    'Push HTTP Msg Fail Num Sum': metrics.pushHTTPMsgFailNumSum,
+    'Push HTTP Msg Fail Rate': metrics.pushHTTPMsgFailRate,
+    'Max HTTP Push Latency': metrics.maxHTTPPushLatency,
+    'Avg HTTP Push Latency': metrics.avgHTTPPushLatency,
+  };
+  const tcpMetrics: MetricRecord = {
+    'Retry TCP Queue Size': metrics.retryTCPQueueSize,
+    'Client2eventMesh TCP TPS': metrics.client2eventMeshTCPTPS,
+    'EventMesh2mq TCP TPS': metrics.eventMesh2mqTCPTPS,
+    'MQ2eventMesh TCP TPS': metrics.mq2eventMeshTCPTPS,
+    'EventMesh2client TCP TPS': metrics.eventMesh2clientTCPTPS,
+    'All TCP TPS': metrics.allTCPTPS,
+    'All TCP Connections': metrics.allTCPConnections,
+    'Sub Topic TCP Num': metrics.subTopicTCPNum,
+  };
+
+  const convertConfigurationToTable = (
+    metricRecord: Record<string, string | number | boolean | undefined>,
+  ) => Object.entries(metricRecord).map(([key, value]) => {
+    if (value === undefined) {
+      return (
+        <Tr>
+          <Td>{key}</Td>
+          <Td>Undefined</Td>
+        </Tr>
+      );
+    }
+
+    return (
+      <Tr>
+        <Td>{key}</Td>
+        <Td>{value.toString()}</Td>
+      </Tr>
+    );
+  });
+
+  if (Object.keys(metrics).length === 0) {
+    return (
+      <Box
+        maxW="full"
+        bg="white"
+        borderWidth="2px"
+        borderRadius="md"
+        borderColor="rgb(211,85,25)"
+        overflow="hidden"
+        p="4"
+        mt="4"
+        opacity="0.8"
+      >
+        <Text fontSize="l" fontWeight="semibold" color="rgb(211,85,25)" textAlign={['left', 'center']}>
+          EventMesh Daemon Not Connected
+        </Text>
+      </Box>
+    );
+  }
+  return (
+    <>
+      <Box
+        maxW="full"
+        bg="white"
+        borderWidth="1px"
+        borderRadius="md"
+        overflow="hidden"
+        p="4"
+        mt="4"
+      >
+        <Text
+          w="full"
+        >
+          EventMesh HTTP Metrics
+        </Text>
+
+        <TableContainer mt="4">
+          <Table variant="simple">
+            <Thead>
+              <Tr>
+                <Th>Metric</Th>
+                <Th>Value</Th>
+              </Tr>
+            </Thead>
+            <Tbody>
+              {convertConfigurationToTable(httpMetrics)}
+            </Tbody>
+          </Table>
+        </TableContainer>
+
+        <Text
+          w="full"
+          mt="4"
+        >
+          HTTP Batch Metrics
+        </Text>
+
+        <TableContainer mt="4">
+          <Table variant="simple">
+            <Thead>
+              <Tr>
+                <Th>Metric</Th>
+                <Th>Value</Th>
+              </Tr>
+            </Thead>
+            <Tbody>
+              {convertConfigurationToTable(batchMetrics)}
+            </Tbody>
+          </Table>
+        </TableContainer>
+
+        <Text
+          w="full"
+          mt="4"
+        >
+          HTTP Send Metrics
+        </Text>
+
+        <TableContainer mt="4">
+          <Table variant="simple">
+            <Thead>
+              <Tr>
+                <Th>Metric</Th>
+                <Th>Value</Th>
+              </Tr>
+            </Thead>
+            <Tbody>
+              {convertConfigurationToTable(sendMetrics)}
+            </Tbody>
+          </Table>
+        </TableContainer>
+
+        <Text
+          w="full"
+          mt="4"
+        >
+          HTTP Push Metrics
+        </Text>
+
+        <TableContainer mt="4">
+          <Table variant="simple">
+            <Thead>
+              <Tr>
+                <Th>Metric</Th>
+                <Th>Value</Th>
+              </Tr>
+            </Thead>
+            <Tbody>
+              {convertConfigurationToTable(pushMetrics)}
+            </Tbody>
+          </Table>
+        </TableContainer>
+      </Box>
+      <Box
+        maxW="full"
+        bg="white"
+        borderWidth="1px"
+        borderRadius="md"
+        overflow="hidden"
+        p="4"
+        mt="4"
+      >
+
+        <Text
+          w="full"
+          mt="4"
+        >
+          TCP Metrics
+        </Text>
+
+        <TableContainer mt="4">
+          <Table variant="simple">
+            <Thead>
+              <Tr>
+                <Th>Metric</Th>
+                <Th>Value</Th>
+              </Tr>
+            </Thead>
+            <Tbody>
+              {convertConfigurationToTable(tcpMetrics)}
+            </Tbody>
+          </Table>
+        </TableContainer>
+
+      </Box>
+    </>
+  );
+};
+
+export default MetricsTable;
diff --git a/components/navigation/MenuItem.tsx b/components/navigation/MenuItem.tsx
new file mode 100644
index 0000000..31f119b
--- /dev/null
+++ b/components/navigation/MenuItem.tsx
@@ -0,0 +1,115 @@
+/*
+ * 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, { FC, ReactNode } from 'react';
+import {
+  Flex, FlexProps, Link, Text,
+} from '@chakra-ui/react';
+
+interface MenuItemProps extends FlexProps {
+  selected: boolean;
+  active: boolean;
+  href: string;
+  children: string | number;
+  setActiveName: (name: string) => void;
+}
+
+export const MenuItem = ({
+  selected,
+  active,
+  href,
+  children,
+  setActiveName,
+}: MenuItemProps) => (
+  <Link
+    position="relative"
+    href={href}
+    h="8"
+    mt={1.5}
+    mb={1.5}
+    ml={3}
+    mr={3}
+    boxSizing="border-box"
+    style={{ textDecoration: 'none' }}
+    _focus={{ boxShadow: 'none' }}
+    onMouseOver={() => setActiveName(children.toString())}
+    onMouseOut={() => setActiveName('')}
+  >
+    <Flex
+      position="absolute"
+      zIndex={2}
+      w="full"
+      h="full"
+      p="6"
+      borderRadius="lg"
+      align="center"
+      role="group"
+      cursor="pointer"
+      fontSize="md"
+      transition="color 0.15s, background-color 0.15s"
+      color={selected || active ? '#2a62ad' : 'current'}
+      fontWeight={selected ? 'bolder' : 'none'}
+      bgColor={selected || active ? '#dce5fe' : 'none'}
+      wordBreak="break-word"
+      overflowWrap="normal"
+    >
+      {children}
+    </Flex>
+    {/* <Box
+      position="absolute"
+      zIndex={1}
+      p={selected || active ? '4' : 0}
+      borderRadius="lg"
+      role="group"
+      cursor="pointer"
+      transition="width 0.3s, opacity 0.4s"
+      bgGradient="linear(28deg,#2a4cad, #2a6bad, #28c9ff)"
+      h="100%"
+      w={selected || active ? '100%' : '0'}
+      opacity={selected || active ? 1 : 0}
+    /> */}
+  </Link>
+);
+
+export const MenuGroupItem: FC<{ name: string; children: ReactNode }> = (
+  props,
+) => {
+  const { name, children } = props;
+  return (
+    <Flex
+      flexDirection="column"
+      w="full"
+      justifyContent="center"
+      alignContent="center"
+    >
+      {name && (
+        <Text
+          mt={5}
+          mb="2"
+          pl="6"
+          fontSize="xs"
+          color="#a2b5d8"
+          fontWeight="bold"
+        >
+          {name.toUpperCase()}
+        </Text>
+      )}
+      {children}
+    </Flex>
+  );
+};
diff --git a/components/navigation/Menus.tsx b/components/navigation/Menus.tsx
new file mode 100644
index 0000000..72be935
--- /dev/null
+++ b/components/navigation/Menus.tsx
@@ -0,0 +1,179 @@
+/*
+ * 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, { FC, ReactNode, useState } from 'react';
+import {
+  Box,
+  BoxProps,
+  Button,
+  Flex,
+  useColorModeValue,
+  Image,
+} from '@chakra-ui/react';
+import { IconType } from 'react-icons';
+import { ArrowBackIcon } from '@chakra-ui/icons';
+
+import { useRouter } from 'next/router';
+
+import {
+  FiList, FiGrid, FiServer, FiDatabase, FiMenu,
+} from 'react-icons/fi';
+import LogoImg from '../../static/images/logo.png';
+import { MenuItem, MenuGroupItem } from './MenuItem';
+
+const Menus: Array<{
+  group?: string;
+  name: string;
+  icon: IconType;
+  href: string;
+  subPath?:string[]
+}> = [
+  { name: 'Overview', icon: FiList, href: '/' },
+  { name: 'Metrics', icon: FiMenu, href: '/metrics' },
+  { name: 'Registry', icon: FiDatabase, href: '/registry' },
+  { name: 'Topic', icon: FiGrid, href: '/topic' },
+  { name: 'Event', icon: FiDatabase, href: '/event' },
+  {
+    group: 'Workflow',
+    name: 'Workflows',
+    icon: FiServer,
+    href: '/workflows',
+    subPath: ['/workflows/create'],
+  },
+  {
+    group: 'Workflow',
+    name: 'Event Catalogs',
+    icon: FiServer,
+    href: '/eventCatalogs',
+  },
+  {
+    group: 'Clients',
+    name: 'TCP',
+    icon: FiServer,
+    href: '/tcp',
+  },
+  {
+    group: 'Clients',
+    name: 'HTTP',
+    icon: FiServer,
+    href: '/http',
+  },
+  {
+    group: 'Clients',
+    name: 'gRPC',
+    icon: FiServer,
+    href: '/grpc',
+  },
+];
+
+interface MenuProps extends BoxProps {
+  onClose: () => void;
+}
+interface IGroupItem {
+  name?: string;
+  children: ReactNode[];
+}
+
+const NavMenu: FC<MenuProps> = ({ display = {}, onClose }) => {
+  const router = useRouter();
+  const [curMenu, setCurMenu] = useState('');
+  const curRoute = router.pathname;
+
+  const MenuByGroup = Menus.reduce<{
+    [groupName: string]: IGroupItem;
+  }>(
+    (groupItems, item) => {
+      const {
+        group, name, href, subPath,
+      } = item;
+      const menuItem = (
+        <MenuItem
+          key={`menu_item_${name}`}
+          selected={curRoute === href || (subPath?.includes(curRoute) ?? false)}
+          active={curMenu === group}
+          href={href}
+          setActiveName={(selectedName:string) => setCurMenu(selectedName)}
+        >
+          {name}
+        </MenuItem>
+      );
+
+      if (!group) {
+        groupItems.topMenu.children.push(menuItem);
+        return groupItems;
+      }
+
+      if (!groupItems[group]) {
+        groupItems[group] = { name: group, children: [] };
+      }
+      groupItems[group].children.push(menuItem);
+
+      return groupItems;
+    },
+    { topMenu: { children: [] } },
+  );
+
+  return (
+    <Box
+      display={display}
+      pos="fixed"
+      w={{ base: 'full', md: 60 }}
+      borderRight="1px"
+      borderRightColor={useColorModeValue('gray.200', 'gray.700')}
+      h="full"
+      bg={useColorModeValue('white', 'gray.900')}
+      boxShadow="base"
+    >
+      <Flex
+        mt={{ base: 5, md: 10 }}
+        mb={{ base: 5, md: 10 }}
+        alignItems={{ base: 'space-between', md: 'center' }}
+        justifyContent={{ base: 'space-between', md: 'center' }}
+        w={{ base: 'full' }}
+      >
+        <Image
+          display={{ base: 'none', md: 'block' }}
+          w={100}
+          src={LogoImg.src}
+          alt="Dan Abramov"
+        />
+        <Button
+          display={{ base: 'block', md: 'none' }}
+          w={{ base: 'full' }}
+          size="lg"
+          textAlign="left"
+          onClick={onClose}
+        >
+          <ArrowBackIcon mr={2} />
+          Back
+        </Button>
+      </Flex>
+
+      <Flex flexDirection="column" alignItems="center">
+        {Object.entries(MenuByGroup).map((groupItem) => (
+          <MenuGroupItem key={`group_item_${groupItem[1].name}`} name={groupItem[1].name ?? ''}>
+            {groupItem[1].children}
+          </MenuGroupItem>
+        ))}
+      </Flex>
+    </Box>
+  );
+};
+
+export default NavMenu;
diff --git a/components/navigation/MenusMobile.tsx b/components/navigation/MenusMobile.tsx
new file mode 100644
index 0000000..8885f14
--- /dev/null
+++ b/components/navigation/MenusMobile.tsx
@@ -0,0 +1,58 @@
+/*
+ * 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 {
+  IconButton,
+  Flex,
+  useColorModeValue,
+  Text,
+  FlexProps,
+} from '@chakra-ui/react';
+import { FiMenu } from 'react-icons/fi';
+
+interface MobileProps extends FlexProps {
+  onOpen: () => void;
+}
+
+const MobileNav = ({ onOpen }: MobileProps) => (
+  <Flex
+    ml={{ base: 0, md: 60 }}
+    px={{ base: 4, md: 24 }}
+    height="20"
+    alignItems="center"
+    bg={useColorModeValue('white', 'gray.900')}
+    borderBottomWidth="1px"
+    borderBottomColor={useColorModeValue('gray.200', 'gray.700')}
+    justifyContent="flex-start"
+    display={{ base: 'flex', md: 'none' }}
+  >
+    <IconButton
+      variant="outline"
+      onClick={onOpen}
+      aria-label="open menu"
+      icon={<FiMenu />}
+    />
+
+    <Text fontSize="2xl" ml="8" fontWeight="bold">
+      EventMesh
+    </Text>
+  </Flex>
+);
+
+export default MobileNav;
diff --git a/components/navigation/Sidebar.tsx b/components/navigation/Sidebar.tsx
new file mode 100644
index 0000000..e8e349b
--- /dev/null
+++ b/components/navigation/Sidebar.tsx
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+/* eslint-disable react/jsx-props-no-spreading */
+import React, { FC, ReactNode } from 'react';
+import {
+  Box,
+  useColorModeValue,
+  Drawer,
+  DrawerContent,
+  useDisclosure,
+} from '@chakra-ui/react';
+import Menus from './Menus';
+import MenusMobile from './MenusMobile';
+
+const Sidebar: FC<{ children: ReactNode }> = ({ children }) => {
+  const { isOpen, onOpen, onClose } = useDisclosure();
+  return (
+    <>
+      <Box minH="100vh" bg={useColorModeValue('gray.100', 'gray.900')}>
+        <Menus
+          display={{ base: 'none', md: 'block' }}
+          onClose={() => onClose}
+        />
+        <MenusMobile onOpen={onOpen} />
+        <Box ml={{ base: 0, md: 60 }} p="4">
+          {children}
+        </Box>
+      </Box>
+      <Drawer
+        autoFocus={false}
+        isOpen={isOpen}
+        placement="left"
+        onClose={onClose}
+        returnFocusOnClose={false}
+        onOverlayClick={onClose}
+        size="full"
+      >
+        <DrawerContent>
+          <Menus onClose={onClose} />
+        </DrawerContent>
+      </Drawer>
+    </>
+  );
+};
+
+export default Sidebar;
diff --git a/components/registry/RegistryTable.tsx b/components/registry/RegistryTable.tsx
new file mode 100644
index 0000000..719fde6
--- /dev/null
+++ b/components/registry/RegistryTable.tsx
@@ -0,0 +1,170 @@
+/*
+ * 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,
+  Select,
+  Input,
+  Table,
+  Thead,
+  Tbody,
+  Tr,
+  Th,
+  Td,
+  TableContainer,
+  useToast,
+  Box,
+} from '@chakra-ui/react';
+import axios from 'axios';
+import { useContext, useEffect, useState } from 'react';
+import { AppContext } from '../../context/context';
+
+interface EventMeshInstance {
+  eventMeshClusterName: string,
+  eventMeshName: string,
+  endpoint: string,
+  lastUpdateTimestamp: number,
+  metadata: string
+}
+
+const EventMeshInstanceRow = ({
+  eventMeshClusterName, eventMeshName, endpoint, lastUpdateTimestamp, metadata,
+}: EventMeshInstance) => (
+  <Tr>
+    <Td>{eventMeshClusterName}</Td>
+    <Td>{eventMeshName}</Td>
+    <Td>{endpoint}</Td>
+    <Td>{lastUpdateTimestamp}</Td>
+    <Td>{metadata}</Td>
+  </Tr>
+);
+
+const RegistryTable = () => {
+  const { state } = useContext(AppContext);
+
+  const [searchInput, setSearchInput] = useState<string>('');
+  const handleSearchInputChange = (event: React.FormEvent<HTMLInputElement>) => {
+    setSearchInput(event.currentTarget.value);
+  };
+
+  const [groupSet, setGroupSet] = useState<Set<string>>(new Set());
+  const [groupFilter, setGroupFilter] = useState<string>('');
+  const handleGroupSelectChange = (event: React.FormEvent<HTMLSelectElement>) => {
+    setGroupFilter(event.currentTarget.value);
+  };
+
+  const [EventMeshInstanceList, setEventMeshInstanceList] = useState<EventMeshInstance[]>([]);
+  const toast = useToast();
+  useEffect(() => {
+    const fetch = async () => {
+      try {
+        const { data } = await axios.get<EventMeshInstance[]>(`${state.endpoint}/registry`);
+        setEventMeshInstanceList(data);
+
+        const nextGroupSet = new Set<string>();
+        data.forEach(({ eventMeshClusterName }) => {
+          nextGroupSet.add(eventMeshClusterName);
+        });
+        setGroupSet(nextGroupSet);
+      } catch (error) {
+        if (axios.isAxiosError(error)) {
+          toast({
+            title: 'Failed to fetch registry list',
+            description: 'unable to connect to the EventMesh daemon',
+            status: 'error',
+            duration: 3000,
+            isClosable: true,
+          });
+          setEventMeshInstanceList([]);
+        }
+      }
+    };
+
+    fetch();
+  }, []);
+
+  return (
+    <Box
+      maxW="full"
+      bg="white"
+      borderWidth="1px"
+      borderRadius="md"
+      overflow="hidden"
+      p="4"
+    >
+      <HStack
+        spacing="2"
+      >
+        <Input
+          w="200%"
+          placeholder="Search"
+          value={searchInput}
+          onChange={handleSearchInputChange}
+        />
+        <Select
+          placeholder="Select Group"
+          onChange={handleGroupSelectChange}
+        >
+          {Array.from(groupSet).map((group) => (
+            <option value={group} key={group}>{group}</option>
+          ))}
+        </Select>
+      </HStack>
+
+      <TableContainer>
+        <Table variant="simple">
+          <Thead>
+            <Tr>
+              <Th>Event Mesh Cluster Name</Th>
+              <Th>Event Mesh Name</Th>
+              <Th>Endpoint</Th>
+              <Th>Last Update Timestamp</Th>
+              <Th>Metadata</Th>
+            </Tr>
+          </Thead>
+          <Tbody>
+            {EventMeshInstanceList && EventMeshInstanceList.filter(({
+              eventMeshClusterName, eventMeshName,
+            }) => {
+              if (searchInput && !eventMeshName.includes(searchInput)) {
+                return false;
+              }
+              if (groupFilter && groupFilter !== eventMeshClusterName) {
+                return false;
+              }
+              return true;
+            }).map(({
+              eventMeshClusterName, eventMeshName, endpoint, lastUpdateTimestamp, metadata,
+            }) => (
+              <EventMeshInstanceRow
+                eventMeshClusterName={eventMeshClusterName}
+                eventMeshName={eventMeshName}
+                endpoint={endpoint}
+                lastUpdateTimestamp={lastUpdateTimestamp}
+                metadata={metadata}
+              />
+            ))}
+          </Tbody>
+        </Table>
+      </TableContainer>
+    </Box>
+  );
+};
+
+export default RegistryTable;
diff --git a/components/topic/TopicTable.tsx b/components/topic/TopicTable.tsx
new file mode 100644
index 0000000..f3a1369
--- /dev/null
+++ b/components/topic/TopicTable.tsx
@@ -0,0 +1,275 @@
+/*
+ * 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;
diff --git a/components/workflow/Create.tsx b/components/workflow/Create.tsx
new file mode 100644
index 0000000..d15d5a4
--- /dev/null
+++ b/components/workflow/Create.tsx
@@ -0,0 +1,136 @@
+/*
+ * 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, { FC, useRef, useState } from 'react';
+
+import {
+  Drawer,
+  DrawerContent,
+  DrawerOverlay,
+  DrawerHeader,
+  DrawerBody,
+  DrawerFooter,
+  DrawerCloseButton,
+  Button,
+  useToast,
+  Spinner,
+} from '@chakra-ui/react';
+import axios from 'axios';
+
+import Editor, { Monaco } from '@monaco-editor/react';
+
+const ApiRoot = process.env.NEXT_PUBLIC_WORKFLOW_API_ROOT;
+
+const Create: FC<{
+  visible: boolean;
+  onClose: () => void;
+  onSucceed: () => void;
+}> = ({ visible = false, onClose = () => {}, onSucceed = () => {} }) => {
+  const toast = useToast();
+  const [isSubmitting, setIsSubmitting] = useState(false);
+  const editorRef = useRef<Monaco | null>(null);
+  const defaultEditorValue = '# Your code goes here';
+
+  const onSubmit = () => {
+    setIsSubmitting(true);
+
+    try {
+      const value = editorRef.current.getValue();
+      if (value === defaultEditorValue) {
+        toast({
+          title: 'Invalid definition',
+          description: 'Please input your workflow definition properly',
+          status: 'warning',
+          position: 'top-right',
+        });
+        setIsSubmitting(false);
+        return;
+      }
+      axios
+        .post(
+          `${ApiRoot}/workflow`,
+          { workflow: { definition: value } },
+          {
+            headers: {
+              'Content-Type': 'application/json',
+            },
+          },
+        )
+        .then(() => {
+          toast({
+            title: 'Succeeded',
+            status: 'success',
+            position: 'top-right',
+          });
+          onSucceed();
+          setIsSubmitting(false);
+        })
+        .catch((error) => {
+          toast({
+            title: 'Failed',
+            description: error.response.data,
+            status: 'error',
+            position: 'top-right',
+          });
+          setIsSubmitting(false);
+        });
+    } catch (error) {
+      setIsSubmitting(false);
+    }
+  };
+
+  const handleEditorDidMount = (editor: Monaco) => {
+    editorRef.current = editor;
+  };
+
+  return (
+    <Drawer
+      isOpen={visible}
+      size="xl"
+      placement="right"
+      closeOnEsc={false}
+      onClose={() => onClose()}
+    >
+      <DrawerOverlay />
+      <DrawerContent>
+        <DrawerCloseButton />
+        <DrawerHeader>Create New Workflow</DrawerHeader>
+        <DrawerBody>
+          <Editor
+            height="1000px"
+            defaultLanguage="yaml"
+            defaultValue={defaultEditorValue}
+            onMount={handleEditorDidMount}
+            theme="vs-dark"
+          />
+        </DrawerBody>
+        <DrawerFooter justifyContent="flex-start">
+          <Button colorScheme="blue" mr={3} onClick={onSubmit}>
+            {isSubmitting ? <Spinner colorScheme="white" size="sm" /> : 'Submit'}
+          </Button>
+          <Button variant="ghost" colorScheme="blue" onClick={onClose}>
+            Cancel
+          </Button>
+        </DrawerFooter>
+      </DrawerContent>
+    </Drawer>
+  );
+};
+
+export default React.memo(Create);
diff --git a/components/workflow/Details/Details.tsx b/components/workflow/Details/Details.tsx
new file mode 100644
index 0000000..601cee9
--- /dev/null
+++ b/components/workflow/Details/Details.tsx
@@ -0,0 +1,285 @@
+/*
+ * 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, {
+  FC, useEffect, useRef, useState,
+} from 'react';
+
+import {
+  Drawer,
+  DrawerContent,
+  DrawerOverlay,
+  DrawerHeader,
+  DrawerBody,
+  DrawerFooter,
+  DrawerCloseButton,
+  Box,
+  FormLabel,
+  Flex,
+  Text,
+  Button,
+  Stack,
+  Tabs,
+  TabList,
+  TabPanels,
+  Tab,
+  TabPanel,
+  useToast,
+  AlertDialog,
+  AlertDialogOverlay,
+  AlertDialogContent,
+  AlertDialogHeader,
+  AlertDialogBody,
+  AlertDialogFooter,
+  Alert,
+  Badge,
+} from '@chakra-ui/react';
+
+import { WarningIcon, InfoIcon } from '@chakra-ui/icons';
+import moment from 'moment';
+
+import Editor, { Monaco } from '@monaco-editor/react';
+import axios from 'axios';
+import { WorkflowType, WorkflowStatusEnum } from '../types';
+// import { WorkflowStatusMap } from '../constant';
+import Intances from './Instances';
+
+const ApiRoot = process.env.NEXT_PUBLIC_WORKFLOW_API_ROOT;
+
+const Details: FC<{
+  visible: boolean;
+  data?: WorkflowType | null;
+  onClose: () => void;
+  onSaved: () => void;
+}> = ({
+  visible = false, data, onClose = () => {}, onSaved = () => {},
+}) => {
+  const toast = useToast();
+  const editorRef = useRef<Monaco | null>(null);
+  const [isShowConfirm, setIsShowComfirm] = useState(false);
+  const cancelRef = useRef(null);
+  const handleEditorDidMount = (editor: Monaco) => {
+    editorRef.current = editor;
+    editor.setValue(data?.definition ?? '');
+  };
+
+  const onSubmit = () => {
+    const value = editorRef.current.getValue();
+
+    axios
+      .post(
+        `${ApiRoot}/workflow`,
+        {
+          workflow: {
+            workflow_id: data?.workflow_id,
+            definition: value,
+          },
+        },
+        {
+          headers: {
+            'Content-Type': 'application/json',
+          },
+        },
+      )
+      .then(() => {
+        toast({
+          title: 'Workflow saved',
+          status: 'success',
+          position: 'top-right',
+        });
+        setIsShowComfirm(false);
+        onSaved();
+        onClose();
+      })
+      .catch((error) => {
+        setIsShowComfirm(false);
+        toast({
+          title: 'Failed to save',
+          description: error.response.data,
+          status: 'error',
+          position: 'top-right',
+        });
+      });
+  };
+
+  const onConfirm = () => {
+    const value = editorRef.current.getValue();
+    if (data?.definition === value) {
+      onClose();
+      return;
+    }
+    setIsShowComfirm(true);
+  };
+
+  useEffect(() => {
+    if (editorRef.current) {
+      editorRef.current.setValue(data?.definition ?? '');
+    }
+  }, [data, editorRef]);
+
+  return (
+    <>
+      <AlertDialog
+        leastDestructiveRef={cancelRef}
+        isOpen={isShowConfirm}
+        onClose={onClose}
+      >
+        <AlertDialogOverlay>
+          <AlertDialogContent>
+            <AlertDialogHeader
+              fontSize="lg"
+              fontWeight="bold"
+              alignItems="center"
+            >
+              <WarningIcon boxSize="6" mr={2} color="orange" />
+              <Text fontSize="xl" as="b">
+                Confirm
+              </Text>
+            </AlertDialogHeader>
+            <AlertDialogBody>
+              Are you sure to save the changes to
+              {' '}
+              <Text as="b">{data?.workflow_id}</Text>
+              ?
+            </AlertDialogBody>
+            <AlertDialogFooter>
+              <Button ref={cancelRef} onClick={() => setIsShowComfirm(false)}>
+                No
+              </Button>
+              <Button colorScheme="blue" onClick={onSubmit} ml={3}>
+                Save
+              </Button>
+            </AlertDialogFooter>
+          </AlertDialogContent>
+        </AlertDialogOverlay>
+      </AlertDialog>
+
+      <Drawer
+        isOpen={visible}
+        size="xl"
+        placement="right"
+        onClose={() => onClose()}
+      >
+        <DrawerOverlay />
+        <DrawerContent>
+          <DrawerCloseButton />
+          <DrawerHeader>
+            {data?.workflow_id}
+            {/* <Badge
+              ml={2}
+              colorScheme={
+                data?.status === WorkflowStatusEnum.Normal ? 'blue' : 'red'
+              }
+            >
+              {WorkflowStatusMap.get(data?.status ?? 0) ?? '-'}
+            </Badge> */}
+            <Badge ml={2}>
+              Version
+              {' '}
+              {data?.version}
+            </Badge>
+          </DrawerHeader>
+          <DrawerBody>
+            <Stack mb="15px" direction="row" spacing={10}>
+              <Flex flexDirection="column" h="full">
+                <Box mb={2}>
+                  <FormLabel opacity={0.5}>Workflow Name</FormLabel>
+                  <Text>{data?.workflow_name}</Text>
+                </Box>
+                <Box mb={2}>
+                  <FormLabel opacity={0.5}>Created At</FormLabel>
+                  <Text>
+                    {moment(data?.update_time).format('YYYY-MM-DD HH:mm:ss')}
+                  </Text>
+                </Box>
+                <Box>
+                  <FormLabel opacity={0.5}>Updated At</FormLabel>
+                  <Text>
+                    {moment(data?.update_time).format('YYYY-MM-DD HH:mm:ss')}
+                  </Text>
+                </Box>
+              </Flex>
+
+              <Flex flexDirection="column" h="full">
+                <Box mb={2}>
+                  <FormLabel opacity={0.5}>Total Instance</FormLabel>
+                  <Text>{data?.total_instances}</Text>
+                </Box>
+                <Box mb={2}>
+                  <FormLabel opacity={0.5}>Running</FormLabel>
+                  <Text>{data?.total_running_instances}</Text>
+                </Box>
+                <Box>
+                  <FormLabel opacity={0.5}>Failed</FormLabel>
+                  <Text>{data?.total_failed_instances}</Text>
+                </Box>
+              </Flex>
+            </Stack>
+
+            <Tabs>
+              <TabList>
+                <Tab>Definition</Tab>
+                <Tab>Instances</Tab>
+              </TabList>
+              <TabPanels>
+                <TabPanel>
+                  <Alert status="info" mb={2}>
+                    <InfoIcon color="#3182ce" mr={2} />
+                    <Text fontSize="sm" color="#3182ce">
+                      You can edit the workflow directly and click &quot;OK&quot; to save
+                      it
+                    </Text>
+                  </Alert>
+                  <Editor
+                    height="1000px"
+                    defaultLanguage="yaml"
+                    defaultValue="# Your code goes here"
+                    onMount={handleEditorDidMount}
+                    theme="vs-dark"
+                  />
+                </TabPanel>
+                <TabPanel>
+                  <Intances workflowId={data?.workflow_id ?? ''} />
+                </TabPanel>
+              </TabPanels>
+            </Tabs>
+          </DrawerBody>
+          <DrawerFooter justifyContent="flex-start">
+            <Button colorScheme="blue" mr={3} onClick={onConfirm}>
+              OK
+            </Button>
+            <Button
+              colorScheme="blue"
+              mr={3}
+              variant="ghost"
+              onClick={onClose}
+            >
+              Cancel
+            </Button>
+          </DrawerFooter>
+        </DrawerContent>
+      </Drawer>
+    </>
+  );
+};
+
+Details.defaultProps = {
+  data: null,
+};
+export default Details;
diff --git a/components/workflow/Details/Instances.tsx b/components/workflow/Details/Instances.tsx
new file mode 100644
index 0000000..3c46ebd
--- /dev/null
+++ b/components/workflow/Details/Instances.tsx
@@ -0,0 +1,164 @@
+/*
+ * 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, {
+  FC, useCallback, useEffect, useState,
+} from 'react';
+import axios from 'axios';
+import {
+  Flex,
+  Text,
+  Table,
+  Thead,
+  Tbody,
+  Tr,
+  Th,
+  Td,
+  TableContainer,
+  TableCaption,
+  Tag,
+  Spinner,
+} from '@chakra-ui/react';
+import moment from 'moment';
+import { WorkflowInstanceType } from '../types';
+import {
+  WorkflowIntanceStatusMap,
+  WorkflowIntanceStatusColorMap,
+} from '../constant';
+
+const ApiRoot = process.env.NEXT_PUBLIC_WORKFLOW_API_ROOT;
+
+const Instances: FC<{ workflowId: string }> = ({ workflowId }) => {
+  const [isLoading, setIsLoading] = useState(true);
+  const [instances, setInstances] = useState<WorkflowInstanceType[]>([]);
+  const [total, setTotal] = useState(0);
+  const [pageIndex, setPageIndex] = useState(1);
+  const pageSize = 10;
+
+  const getWorkflows = useCallback(async () => {
+    setIsLoading(true);
+    try {
+      const reqParams: {
+        page: number;
+        size: number;
+        workflow_id?: string;
+      } = {
+        page: pageIndex,
+        size: pageSize,
+        workflow_id: workflowId,
+      };
+
+      const { data } = await axios.get<{
+        total: number;
+        workflow_instances: WorkflowInstanceType[];
+      }>(`${ApiRoot}/workflow/instances`, {
+        params: reqParams,
+      });
+      setInstances([...instances, ...(data?.workflow_instances ?? [])]);
+      setTotal(data.total);
+      setIsLoading(false);
+    } catch (error) {
+      setIsLoading(false);
+    }
+  }, [workflowId, pageIndex, pageSize]);
+
+  useEffect(() => {
+    const controller = new AbortController();
+    getWorkflows();
+    return () => {
+      controller.abort();
+    };
+  }, [workflowId, pageIndex, pageSize]);
+
+  return (
+    <TableContainer>
+      <Table variant="simple">
+        <Thead>
+          <Tr>
+            <Th>Instance ID</Th>
+            <Th>Status</Th>
+            <Th>Updated at</Th>
+            <Th>Created At</Th>
+          </Tr>
+        </Thead>
+        <Tbody>
+          {instances.map((workflow) => (
+            <Tr key={workflow.workflow_instance_id}>
+              <Td>{workflow.workflow_instance_id}</Td>
+              <Td>
+                <Tag
+                  size="sm"
+                  colorScheme={WorkflowIntanceStatusColorMap.get(
+                    workflow.workflow_status,
+                  )}
+                  variant="outline"
+                >
+                  {WorkflowIntanceStatusMap.get(workflow.workflow_status)}
+                </Tag>
+              </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>
+            </Tr>
+          ))}
+        </Tbody>
+
+        {instances.length === 0 && (
+          <TableCaption>
+            <Text variant="xs" color="#909090">
+              empty
+            </Text>
+          </TableCaption>
+        )}
+
+        {instances.length > 0 && (
+        <TableCaption>
+          <Flex alignItems="center">
+            <Text fontSize="sx">
+              {`${instances.length} of ${total}  intance${
+                total > 1 ? 's' : ''
+              } in list `}
+            </Text>
+            {isLoading ? (
+              <Spinner ml={2} size="sm" colorScheme="blue" />
+            ) : (
+              instances.length < total && (
+              <Text
+                color={instances.length < total ? '#3182ce' : ''}
+                ml="2"
+                cursor="pointer"
+                as="u"
+                onClick={() => setPageIndex(pageIndex + 1)}
+              >
+                Load More
+              </Text>
+              )
+            )}
+          </Flex>
+        </TableCaption>
+        )}
+      </Table>
+    </TableContainer>
+  );
+};
+
+export default Instances;
diff --git a/components/workflow/Details/index.ts b/components/workflow/Details/index.ts
new file mode 100644
index 0000000..11a5b7d
--- /dev/null
+++ b/components/workflow/Details/index.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 Details from './Details';
+
+export default Details;
diff --git a/components/workflow/constant.ts b/components/workflow/constant.ts
new file mode 100644
index 0000000..f39a0c0
--- /dev/null
+++ b/components/workflow/constant.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 { WorkflowStatusEnum, WorkflowInstanceStatusEnum } from './types';
+
+export const WorkflowStatusMap = new Map([
+  [WorkflowStatusEnum.Normal, 'Normal'],
+  [WorkflowStatusEnum.Deleted, 'Deleted'],
+]);
+
+export const WorkflowIntanceStatusMap = new Map([
+  [WorkflowInstanceStatusEnum.Sleep, 'Sleeping'],
+  [WorkflowInstanceStatusEnum.Wait, 'Waiting'],
+  [WorkflowInstanceStatusEnum.Process, 'Processing'],
+  [WorkflowInstanceStatusEnum.Succeed, 'Succeeded'],
+  [WorkflowInstanceStatusEnum.Fail, 'Failed'],
+]);
+
+export const WorkflowIntanceStatusColorMap = new Map([
+  [WorkflowInstanceStatusEnum.Sleep, 'gray'],
+  [WorkflowInstanceStatusEnum.Wait, 'orange'],
+  [WorkflowInstanceStatusEnum.Process, 'blue'],
+  [WorkflowInstanceStatusEnum.Succeed, 'green'],
+  [WorkflowInstanceStatusEnum.Fail, 'red'],
+]);
diff --git a/components/workflow/types.ts b/components/workflow/types.ts
new file mode 100644
index 0000000..45fe58a
--- /dev/null
+++ b/components/workflow/types.ts
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+export type WorkflowType = {
+  create_time: string;
+  definition: string;
+  id: number;
+  status: number;
+  total_failed_instances: number;
+  total_instances: number;
+  total_running_instances: number;
+  update_time: string;
+  version: string;
+  workflow_id: string;
+  workflow_name: string;
+};
+
+export type WorkflowInstanceType = {
+  create_time: string,
+  id: number,
+  update_time: string,
+  workflow_id : string,
+  workflow_instance_id : string,
+  workflow_status : number
+};
+
+export enum WorkflowStatusEnum {
+  'Normal' = 1,
+  'Deleted' = -1,
+}
+
+export enum WorkflowInstanceStatusEnum {
+  Sleep = 1,
+  Wait = 2,
+  Process = 3,
+  Succeed = 4,
+  Fail = 5,
+}
diff --git a/context/context.tsx b/context/context.tsx
new file mode 100644
index 0000000..19b074f
--- /dev/null
+++ b/context/context.tsx
@@ -0,0 +1,87 @@
+/*
+ * 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 {
+  useMemo,
+  useEffect,
+  useState,
+  createContext,
+  Dispatch,
+} from 'react';
+import { useImmerReducer } from 'use-immer';
+import { State, Action } from './type';
+import reducer from './reducer';
+
+const initialState: State = {
+  endpoint: 'http://localhost:10106',
+};
+
+const AppContext = createContext<{
+  state: State;
+  dispatch: Dispatch<Action>;
+}>({
+  state: initialState,
+  dispatch: () => null,
+});
+
+interface AppProviderProps {
+  children: React.ReactNode;
+}
+
+const AppProvider = ({ children }: AppProviderProps) => {
+  const [state, dispatch] = useImmerReducer(
+    reducer,
+    initialState,
+  );
+  const [isLoading, setIsLoading] = useState(true);
+
+  useEffect(() => {
+    const localState = localStorage.getItem('state');
+    if (localState === null) {
+      return;
+    }
+    const parsedState: State = JSON.parse(localState);
+    dispatch({
+      type: 'SetState',
+      payload: {
+        endpoint: parsedState.endpoint,
+      },
+    });
+  }, []);
+
+  useEffect(() => {
+    if (!isLoading) localStorage.setItem('state', JSON.stringify(state));
+    setIsLoading(false);
+  }, [state]);
+
+  const context = useMemo(() => ({
+    state,
+    dispatch,
+  }), [state]);
+
+  return (
+    <AppContext.Provider
+      value={context}
+    >
+      {children}
+    </AppContext.Provider>
+  );
+};
+
+export { AppContext, AppProvider };
diff --git a/context/reducer.ts b/context/reducer.ts
new file mode 100644
index 0000000..6e4eed7
--- /dev/null
+++ b/context/reducer.ts
@@ -0,0 +1,40 @@
+/*
+ * 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 { Action, State } from './type';
+
+const reducer = (
+  state: State,
+  action: Action,
+) => {
+  switch (action.type) {
+    case 'SetState':
+      state.endpoint = action.payload.endpoint;
+      break;
+
+    case 'SetEndPointAction':
+      state.endpoint = action.payload.endpoint;
+      break;
+
+    default:
+      break;
+  }
+};
+
+export default reducer;
diff --git a/context/type.ts b/context/type.ts
new file mode 100644
index 0000000..5deb2ba
--- /dev/null
+++ b/context/type.ts
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+export interface State {
+  endpoint: string;
+}
+
+interface SetState {
+  type: 'SetState';
+  payload: {
+    endpoint: string;
+  };
+}
+
+interface SetEndPointAction {
+  type: 'SetEndPointAction';
+  payload: {
+    endpoint: string;
+  };
+}
+
+export type Action =
+  | SetState
+  | SetEndPointAction;
diff --git a/next.config.js b/next.config.js
new file mode 100644
index 0000000..b0f480c
--- /dev/null
+++ b/next.config.js
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+  reactStrictMode: true,
+}
+
+module.exports = nextConfig
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..22d8dc8
--- /dev/null
+++ b/package.json
@@ -0,0 +1,45 @@
+{
+  "name": "eventmesh-dashboard",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "dev": "next dev",
+    "build": "next build",
+    "start": "next start",
+    "lint": "eslint . --cache --fix --ext .ts,.tsx"
+  },
+  "dependencies": {
+    "@chakra-ui/icons": "^2.0.15",
+    "@chakra-ui/react": "^2.1.2",
+    "@emotion/react": "^11.9.0",
+    "@emotion/styled": "^11.8.1",
+    "@fontsource/inter": "^4.5.10",
+    "@monaco-editor/react": "^4.4.6",
+    "axios": "^0.27.2",
+    "cloudevents": "^6.0.2",
+    "framer-motion": "^6.3.6",
+    "immer": "^9.0.15",
+    "moment": "^2.29.4",
+    "next": "12.1.6",
+    "react": "18.1.0",
+    "react-dom": "18.1.0",
+    "react-icons": "^4.4.0",
+    "swr": "^1.3.0",
+    "use-immer": "^0.7.0"
+  },
+  "devDependencies": {
+    "@types/node": "17.0.38",
+    "@types/react": "18.0.10",
+    "@types/react-dom": "18.0.5",
+    "@typescript-eslint/eslint-plugin": "^5.4.0",
+    "@typescript-eslint/parser": "^5.4.0",
+    "eslint": "8.16.0",
+    "eslint-config-airbnb": "^19.0.1",
+    "eslint-config-airbnb-typescript": "^17.0.0",
+    "eslint-plugin-import": "^2.25.3",
+    "eslint-plugin-jsx-a11y": "^6.5.1",
+    "eslint-plugin-react": "^7.27.1",
+    "eslint-plugin-react-hooks": "^4.3.0",
+    "typescript": "4.7.2"
+  }
+}
diff --git a/pages/_app.tsx b/pages/_app.tsx
new file mode 100644
index 0000000..6095173
--- /dev/null
+++ b/pages/_app.tsx
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+/* eslint-disable react/jsx-props-no-spreading */
+import '@fontsource/inter';
+import { ChakraProvider, extendTheme } from '@chakra-ui/react';
+import type { AppProps } from 'next/app';
+import Sidebar from '../components/navigation/Sidebar';
+import { AppProvider } from '../context/context';
+
+const theme = extendTheme({
+  initialColorMode: 'light',
+  useSystemColorMode: true,
+  fonts: {
+    heading: 'Inter, sans-serif',
+    body: 'Inter, sans-serif',
+  },
+});
+
+const Application = ({ Component, pageProps }: AppProps) => (
+  <ChakraProvider theme={theme}>
+    <AppProvider>
+      <Sidebar>
+        <Component {...pageProps} />
+      </Sidebar>
+    </AppProvider>
+  </ChakraProvider>
+);
+
+export default Application;
diff --git a/pages/_document.tsx b/pages/_document.tsx
new file mode 100644
index 0000000..dd32fc0
--- /dev/null
+++ b/pages/_document.tsx
@@ -0,0 +1,38 @@
+/*
+ * 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 { ColorModeScript } from '@chakra-ui/react';
+import NextDocument, {
+  Html, Head, Main, NextScript,
+} from 'next/document';
+
+export default class Document extends NextDocument {
+  render() {
+    return (
+      <Html lang="en">
+        <Head />
+        <body>
+          <ColorModeScript />
+          <Main />
+          <NextScript />
+        </body>
+      </Html>
+    );
+  }
+}
diff --git a/pages/event.tsx b/pages/event.tsx
new file mode 100644
index 0000000..8214d74
--- /dev/null
+++ b/pages/event.tsx
@@ -0,0 +1,33 @@
+/*
+ * 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 Head from 'next/head';
+import type { NextPage } from 'next';
+import EventTable from '../components/event/EventTable';
+
+const Event: NextPage = () => (
+  <>
+    <Head>
+      <title>Event | Apache EventMesh Dashboard</title>
+    </Head>
+    <EventTable />
+  </>
+);
+
+export default Event;
diff --git a/pages/eventCatalogs.tsx b/pages/eventCatalogs.tsx
new file mode 100644
index 0000000..f33d57e
--- /dev/null
+++ b/pages/eventCatalogs.tsx
@@ -0,0 +1,223 @@
+/*
+ * 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, { useState, useEffect, useCallback } from 'react';
+import Head from 'next/head';
+import type { NextPage } from 'next';
+
+import {
+  Divider,
+  Button,
+  Flex,
+  Table,
+  Thead,
+  Tbody,
+  Tr,
+  Th,
+  Td,
+  TableContainer,
+  Box,
+  Spinner,
+  Text,
+} from '@chakra-ui/react';
+import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons';
+import axios from 'axios';
+import moment from 'moment';
+import Details from '../components/eventCatalogs/Details';
+import CreateCatalog from '../components/eventCatalogs/Create';
+import { EventCatalogType } from '../components/eventCatalogs/types';
+import { WorkflowStatusMap } from '../components/eventCatalogs/constant';
+
+const ApiRoot = process.env.NEXT_PUBLIC_EVENTCATALOG_API_ROOT;
+
+const EventCatalogs: NextPage = () => {
+  const [isShowCreate, setIsShowCreate] = useState(false);
+  const [curCatalog, setCurCatalog] = useState<EventCatalogType>();
+
+  const [catalogs, setCatalogs] = useState<EventCatalogType[]>([]);
+  const [total, setTotal] = useState(0);
+
+  const pageSize = 10;
+  const [isLoading, setIsLoading] = useState(true);
+  const [pageIndex, setPageIndex] = useState(1);
+
+  const [refreshFlag, setRefreshFlag] = useState<number>(+new Date());
+
+  const getEventCatalogs = useCallback(async () => {
+    setIsLoading(true);
+    try {
+      const { data } = await axios.get<{
+        total: number;
+        events: EventCatalogType[];
+      }>(`${ApiRoot}/catalog`, {
+        params: { page: pageIndex, size: pageSize },
+      });
+      setCatalogs(data.events);
+      setTotal(data.total);
+      setIsLoading(false);
+    } catch (error) {
+      setIsLoading(false);
+    }
+  }, []);
+
+  useEffect(() => {
+    const controller = new AbortController();
+    getEventCatalogs();
+    return () => {
+      controller.abort();
+    };
+  }, [pageIndex, pageSize, refreshFlag]);
+
+  return (
+    <>
+      <Head>
+        <title>Event Catalogs | Apache EventMesh Dashboard</title>
+      </Head>
+      <Box
+        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 Catalog
+          </Button>
+          <Button
+            size="md"
+            colorScheme="blue"
+            variant="ghost"
+            onClick={() => setRefreshFlag(+new Date())}
+          >
+            Refresh
+          </Button>
+        </Flex>
+        <Divider mt="15" mb="15" orientation="horizontal" />
+        <TableContainer>
+          <Table variant="simple">
+            <Thead>
+              <Tr>
+                {/* <Th>Catalog ID</Th> */}
+                <Th>Title</Th>
+                <Th>File Name</Th>
+                <Th>Version</Th>
+                <Th>Status</Th>
+                <Th>Created At</Th>
+                <Th>Updated At</Th>
+              </Tr>
+            </Thead>
+            <Tbody>
+              {catalogs.map((catalog) => (
+                <Tr key={catalog.id}>
+                  {/* <Td>
+                    <Button
+                      colorScheme="blue"
+                      variant="ghost"
+                      onClick={() => setCurCatalog(catalog)}
+                    >
+                      {catalog.id}
+                    </Button>
+                  </Td> */}
+                  <Td>
+                    <Button
+                      colorScheme="blue"
+                      variant="ghost"
+                      onClick={() => setCurCatalog(catalog)}
+                    >
+                      {catalog.title}
+                    </Button>
+                  </Td>
+                  <Td>{catalog.file_name}</Td>
+                  <Td>{catalog.version}</Td>
+                  <Td>{WorkflowStatusMap.get(catalog.status)}</Td>
+                  <Td>
+                    {moment(catalog.create_time).format('YYYY-MM-DD HH:mm:ss')}
+                  </Td>
+                  <Td>
+                    {moment(catalog.update_time).format('YYYY-MM-DD HH:mm:ss')}
+                  </Td>
+                </Tr>
+              ))}
+            </Tbody>
+          </Table>
+        </TableContainer>
+        <Flex mt={4} alignItems="center">
+          {isLoading ? (
+            <Spinner colorScheme="blue" size="sm" />
+          ) : (
+            <Text fontSize="sm" color="#909090">
+              {total}
+              {` catalog${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>
+      </Box>
+      <Details
+        visible={Boolean(curCatalog)}
+        data={curCatalog}
+        onClose={() => setCurCatalog(undefined)}
+      />
+      <CreateCatalog
+        visible={isShowCreate}
+        onSucceed={() => {
+          setIsShowCreate(false);
+          setPageIndex(1);
+          setRefreshFlag(+new Date());
+        }}
+        onClose={() => setIsShowCreate(false)}
+      />
+    </>
+  );
+};
+
+export default EventCatalogs;
diff --git a/pages/grpc.tsx b/pages/grpc.tsx
new file mode 100644
index 0000000..8d68014
--- /dev/null
+++ b/pages/grpc.tsx
@@ -0,0 +1,33 @@
+/*
+ * 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 Head from 'next/head';
+import type { NextPage } from 'next';
+import GrpcClientTable from '../components/client/GrpcClientTable';
+
+const GrpcClient: NextPage = () => (
+  <>
+    <Head>
+      <title>Grpc Client | Apache EventMesh Dashboard</title>
+    </Head>
+    <GrpcClientTable />
+  </>
+);
+
+export default GrpcClient;
diff --git a/pages/http.tsx b/pages/http.tsx
new file mode 100644
index 0000000..2d692d9
--- /dev/null
+++ b/pages/http.tsx
@@ -0,0 +1,33 @@
+/*
+ * 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 Head from 'next/head';
+import type { NextPage } from 'next';
+import HTTPClientTable from '../components/client/HTTPClientTable';
+
+const HTTPClient: NextPage = () => (
+  <>
+    <Head>
+      <title>HTTP Client | Apache EventMesh Dashboard</title>
+    </Head>
+    <HTTPClientTable />
+  </>
+);
+
+export default HTTPClient;
diff --git a/pages/index.tsx b/pages/index.tsx
new file mode 100755
index 0000000..7077600
--- /dev/null
+++ b/pages/index.tsx
@@ -0,0 +1,35 @@
+/*
+ * 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 Head from 'next/head';
+import type { NextPage } from 'next';
+import Endpoint from '../components/index/Endpoint';
+import Configuration from '../components/index/Configuration';
+
+const Index: NextPage = () => (
+  <>
+    <Head>
+      <title>Apache EventMesh Dashboard</title>
+    </Head>
+    <Endpoint />
+    <Configuration />
+  </>
+);
+
+export default Index;
diff --git a/pages/metrics.tsx b/pages/metrics.tsx
new file mode 100644
index 0000000..3793b64
--- /dev/null
+++ b/pages/metrics.tsx
@@ -0,0 +1,33 @@
+/*
+ * 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 Head from 'next/head';
+import type { NextPage } from 'next';
+import MetricsTable from '../components/metrics/MetricsTable';
+
+const Metrics: NextPage = () => (
+  <>
+    <Head>
+      <title>Metrics | Apache EventMesh Dashboard</title>
+    </Head>
+    <MetricsTable />
+  </>
+);
+
+export default Metrics;
diff --git a/pages/registry.tsx b/pages/registry.tsx
new file mode 100644
index 0000000..b2dd367
--- /dev/null
+++ b/pages/registry.tsx
@@ -0,0 +1,33 @@
+/*
+ * 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 Head from 'next/head';
+import type { NextPage } from 'next';
+import RegistryTable from '../components/registry/RegistryTable';
+
+const Registry: NextPage = () => (
+  <>
+    <Head>
+      <title>Registry | Apache EventMesh Dashboard</title>
+    </Head>
+    <RegistryTable />
+  </>
+);
+
+export default Registry;
diff --git a/pages/tcp.tsx b/pages/tcp.tsx
new file mode 100644
index 0000000..93a4028
--- /dev/null
+++ b/pages/tcp.tsx
@@ -0,0 +1,33 @@
+/*
+ * 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 Head from 'next/head';
+import type { NextPage } from 'next';
+import TCPClientTable from '../components/client/TCPClientTable';
+
+const TCPClient: NextPage = () => (
+  <>
+    <Head>
+      <title>TCP Client | Apache EventMesh Dashboard</title>
+    </Head>
+    <TCPClientTable />
+  </>
+);
+
+export default TCPClient;
diff --git a/pages/topic.tsx b/pages/topic.tsx
new file mode 100644
index 0000000..9023151
--- /dev/null
+++ b/pages/topic.tsx
@@ -0,0 +1,33 @@
+/*
+ * 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 Head from 'next/head';
+import type { NextPage } from 'next';
+import TopicTable from '../components/topic/TopicTable';
+
+const Topic: NextPage = () => (
+  <>
+    <Head>
+      <title>Topic | Apache EventMesh Dashboard</title>
+    </Head>
+    <TopicTable />
+  </>
+);
+
+export default Topic;
diff --git a/pages/workflows.tsx b/pages/workflows.tsx
new file mode 100644
index 0000000..29fd982
--- /dev/null
+++ b/pages/workflows.tsx
@@ -0,0 +1,375 @@
+/*
+ * 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;
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..718d6fe
--- /dev/null
+++ b/public/favicon.ico
Binary files differ
diff --git a/static/images/logo.png b/static/images/logo.png
new file mode 100644
index 0000000..e854551
--- /dev/null
+++ b/static/images/logo.png
Binary files differ
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..99710e8
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,20 @@
+{
+  "compilerOptions": {
+    "target": "es5",
+    "lib": ["dom", "dom.iterable", "esnext"],
+    "allowJs": true,
+    "skipLibCheck": true,
+    "strict": true,
+    "forceConsistentCasingInFileNames": true,
+    "noEmit": true,
+    "esModuleInterop": true,
+    "module": "esnext",
+    "moduleResolution": "node",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "jsx": "preserve",
+    "incremental": true
+  },
+  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+  "exclude": ["node_modules"]
+}