You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@eventmesh.apache.org by ch...@apache.org on 2023/03/28 09:22:51 UTC

[eventmesh-dashboard] branch main created (now 16993bc)

This is an automated email from the ASF dual-hosted git repository.

chenguangsheng pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/eventmesh-dashboard.git


      at 16993bc  transfer dashboard from parent

This branch includes the following new commits:

     new 16993bc  transfer dashboard from parent

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@eventmesh.apache.org
For additional commands, e-mail: commits-help@eventmesh.apache.org


[eventmesh-dashboard] 01/01: transfer dashboard from parent

Posted by ch...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

chenguangsheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/eventmesh-dashboard.git

commit 16993bc8e6dee21c001cb77a82acb82b45c38de5
Author: qqeasonchen <qq...@gmail.com>
AuthorDate: Tue Mar 28 17:22:24 2023 +0800

    transfer dashboard from parent
---
 README.md                                 |  34 +++
 components/client/GrpcClientTable.tsx     | 217 +++++++++++++++++
 components/client/HTTPClientTable.tsx     | 217 +++++++++++++++++
 components/client/TCPClientTable.tsx      | 223 ++++++++++++++++++
 components/event/EventTable.tsx           | 371 +++++++++++++++++++++++++++++
 components/eventCatalogs/Create.tsx       | 137 +++++++++++
 components/eventCatalogs/Details.tsx      | 121 ++++++++++
 components/eventCatalogs/constant.ts      |  27 +++
 components/eventCatalogs/types.ts         |  40 ++++
 components/index/Configuration.tsx        | 249 ++++++++++++++++++++
 components/index/Endpoint.tsx             | 105 +++++++++
 components/metrics/MetricsTable.tsx       | 313 +++++++++++++++++++++++++
 components/navigation/MenuItem.tsx        | 115 +++++++++
 components/navigation/Menus.tsx           | 179 ++++++++++++++
 components/navigation/MenusMobile.tsx     |  58 +++++
 components/navigation/Sidebar.tsx         |  63 +++++
 components/registry/RegistryTable.tsx     | 170 ++++++++++++++
 components/topic/TopicTable.tsx           | 275 ++++++++++++++++++++++
 components/workflow/Create.tsx            | 136 +++++++++++
 components/workflow/Details/Details.tsx   | 285 +++++++++++++++++++++++
 components/workflow/Details/Instances.tsx | 164 +++++++++++++
 components/workflow/Details/index.ts      |  22 ++
 components/workflow/constant.ts           |  41 ++++
 components/workflow/types.ts              |  54 +++++
 context/context.tsx                       |  87 +++++++
 context/reducer.ts                        |  40 ++++
 context/type.ts                           |  40 ++++
 next.config.js                            |  25 ++
 package.json                              |  45 ++++
 pages/_app.tsx                            |  46 ++++
 pages/_document.tsx                       |  38 +++
 pages/event.tsx                           |  33 +++
 pages/eventCatalogs.tsx                   | 223 ++++++++++++++++++
 pages/grpc.tsx                            |  33 +++
 pages/http.tsx                            |  33 +++
 pages/index.tsx                           |  35 +++
 pages/metrics.tsx                         |  33 +++
 pages/registry.tsx                        |  33 +++
 pages/tcp.tsx                             |  33 +++
 pages/topic.tsx                           |  33 +++
 pages/workflows.tsx                       | 375 ++++++++++++++++++++++++++++++
 public/favicon.ico                        | Bin 0 -> 25931 bytes
 static/images/logo.png                    | Bin 0 -> 58522 bytes
 tsconfig.json                             |  20 ++
 44 files changed, 4821 insertions(+)

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
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/static/images/logo.png b/static/images/logo.png
new file mode 100644
index 0000000..e854551
Binary files /dev/null and b/static/images/logo.png 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"]
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@eventmesh.apache.org
For additional commands, e-mail: commits-help@eventmesh.apache.org