You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@eventmesh.apache.org by xi...@apache.org on 2022/07/27 05:00:30 UTC
[incubator-eventmesh] 01/01: Apache EventMesh Dashboard - Client Management (#907)
This is an automated email from the ASF dual-hosted git repository.
xiaoyang pushed a commit to branch dashboard
in repository https://gitbox.apache.org/repos/asf/incubator-eventmesh.git
commit 2c4bcf7dfc89dadcc169d278b849a58a46408ea6
Author: Xiaoyang Liu <si...@gmail.com>
AuthorDate: Tue Jul 26 21:58:56 2022 -0700
Apache EventMesh Dashboard - Client Management (#907)
---
eventmesh-dashboard/.eslintrc.js | 53 +++++
eventmesh-dashboard/.gitignore | 125 ++++++++++
eventmesh-dashboard/README.md | 34 +++
eventmesh-dashboard/components/Sidebar.tsx | 188 +++++++++++++++
.../components/client/ClientTable.tsx | 245 +++++++++++++++++++
eventmesh-dashboard/components/index/Endpoint.tsx | 104 ++++++++
eventmesh-dashboard/next-env.d.ts | 24 ++
eventmesh-dashboard/next.config.js | 25 ++
eventmesh-dashboard/package.json | 39 +++
eventmesh-dashboard/pages/_app.tsx | 46 ++++
eventmesh-dashboard/pages/_document.tsx | 38 +++
eventmesh-dashboard/pages/client.tsx | 33 +++
eventmesh-dashboard/pages/index.tsx | 33 +++
eventmesh-dashboard/public/favicon.ico | Bin 0 -> 25931 bytes
eventmesh-dashboard/tsconfig.json | 20 ++
.../admin/controller/ClientManageController.java | 15 +-
.../runtime/admin/handler/ClientHandler.java | 262 +++++++++++++++++++++
.../runtime/admin/request/DeleteClientRequest.java | 44 ++++
.../eventmesh/runtime/admin/response/Error.java | 33 +++
.../runtime/admin/response/GetClientResponse.java | 63 +++++
.../runtime/admin/utils/HttpExchangeUtils.java | 43 ++++
.../eventmesh/runtime/admin/utils/JsonUtils.java | 71 ++++++
.../eventmesh/runtime/boot/EventMeshServer.java | 4 +
.../eventmesh/runtime/boot/EventMeshTCPServer.java | 14 --
.../protocol/grpc/consumer/ConsumerManager.java | 4 +
.../consumergroup/ConsumerGroupClient.java | 20 +-
.../protocol/http/consumer/ConsumerManager.java | 4 +
27 files changed, 1559 insertions(+), 25 deletions(-)
diff --git a/eventmesh-dashboard/.eslintrc.js b/eventmesh-dashboard/.eslintrc.js
new file mode 100644
index 00000000..120b0073
--- /dev/null
+++ b/eventmesh-dashboard/.eslintrc.js
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+module.exports = {
+ env: {
+ browser: true,
+ es2021: true,
+ },
+ extends: [
+ 'plugin:react/recommended',
+ 'airbnb',
+ 'airbnb-typescript',
+ ],
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ ecmaVersion: 13,
+ sourceType: 'module',
+ project: 'tsconfig.json',
+ },
+ plugins: [
+ 'react',
+ '@typescript-eslint',
+ ],
+ rules: {
+ 'react/react-in-jsx-scope': 'off',
+ 'react/function-component-definition': [
+ 'error',
+ {
+ namedComponents: 'arrow-function',
+ },
+ ],
+ 'no-param-reassign': 'off'
+ },
+};
diff --git a/eventmesh-dashboard/.gitignore b/eventmesh-dashboard/.gitignore
new file mode 100644
index 00000000..119a1e32
--- /dev/null
+++ b/eventmesh-dashboard/.gitignore
@@ -0,0 +1,125 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# Next.js build output
+.next
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and *not* Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# vercel
+.vercel
diff --git a/eventmesh-dashboard/README.md b/eventmesh-dashboard/README.md
new file mode 100644
index 00000000..c87e0421
--- /dev/null
+++ b/eventmesh-dashboard/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/eventmesh-dashboard/components/Sidebar.tsx b/eventmesh-dashboard/components/Sidebar.tsx
new file mode 100644
index 00000000..321207ce
--- /dev/null
+++ b/eventmesh-dashboard/components/Sidebar.tsx
@@ -0,0 +1,188 @@
+/*
+ * 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, { ReactNode } from 'react';
+import {
+ IconButton,
+ Box,
+ CloseButton,
+ Flex,
+ Icon,
+ useColorModeValue,
+ Link,
+ Drawer,
+ DrawerContent,
+ Text,
+ useDisclosure,
+ BoxProps,
+ FlexProps,
+} from '@chakra-ui/react';
+import {
+ FiList,
+ FiGrid,
+ FiServer,
+ FiDatabase,
+ FiMenu,
+} from 'react-icons/fi';
+import { IconType } from 'react-icons';
+
+interface LinkItemProps {
+ name: string;
+ icon: IconType;
+ href: string;
+}
+
+const LinkItems: Array<LinkItemProps> = [
+ { name: 'Overview', icon: FiList, href: '/' },
+ { name: 'Client', icon: FiServer, href: '/client' },
+ { name: 'Topic', icon: FiGrid, href: '/topic' },
+ { name: 'Event', icon: FiDatabase, href: '/event' },
+];
+
+interface NavItemProps extends FlexProps {
+ icon: IconType;
+ href: string;
+ children: string | number;
+}
+
+const NavItem = ({
+ icon, href, children, ...rest
+}: NavItemProps) => (
+ <Link
+ href={href}
+ style={{ textDecoration: 'none' }}
+ _focus={{ boxShadow: 'none' }}
+ >
+ <Flex
+ align="center"
+ p="4"
+ mx="4"
+ borderRadius="lg"
+ role="group"
+ cursor="pointer"
+ _hover={{
+ bg: 'blue.500',
+ color: 'white',
+ }}
+ {...rest}
+ >
+ {icon && (
+ <Icon
+ mr="4"
+ fontSize="16"
+ _groupHover={{
+ color: 'white',
+ }}
+ as={icon}
+ />
+ )}
+ {children}
+ </Flex>
+ </Link>
+);
+
+interface SidebarProps extends BoxProps {
+ onClose: () => void;
+}
+
+const SidebarContent = ({ onClose, ...rest }: SidebarProps) => (
+ <Box
+ bg={useColorModeValue('white', 'gray.900')}
+ borderRight="1px"
+ borderRightColor={useColorModeValue('gray.200', 'gray.700')}
+ w={{ base: 'full', md: 60 }}
+ pos="fixed"
+ h="full"
+ {...rest}
+ >
+ <Flex h="20" alignItems="center" mx="8" justifyContent="space-between">
+ <Text fontSize="2xl" fontWeight="bold">
+ EventMesh
+ </Text>
+ <CloseButton display={{ base: 'flex', md: 'none' }} onClick={onClose} />
+ </Flex>
+ {LinkItems.map((link) => (
+ <NavItem key={link.name} href={link.href} icon={link.icon}>
+ {link.name}
+ </NavItem>
+ ))}
+ </Box>
+);
+
+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>
+);
+
+const Sidebar = ({ children }: { children: ReactNode }) => {
+ const { isOpen, onOpen, onClose } = useDisclosure();
+ return (
+ <Box minH="100vh" bg={useColorModeValue('gray.100', 'gray.900')}>
+ <SidebarContent
+ onClose={() => onClose}
+ display={{ base: 'none', md: 'block' }}
+ />
+ <Drawer
+ autoFocus={false}
+ isOpen={isOpen}
+ placement="left"
+ onClose={onClose}
+ returnFocusOnClose={false}
+ onOverlayClick={onClose}
+ size="full"
+ >
+ <DrawerContent>
+ <SidebarContent onClose={onClose} />
+ </DrawerContent>
+ </Drawer>
+
+ <MobileNav onOpen={onOpen} />
+ <Box ml={{ base: 0, md: 60 }} p="4">
+ {children}
+ </Box>
+ </Box>
+ );
+};
+
+export default Sidebar;
diff --git a/eventmesh-dashboard/components/client/ClientTable.tsx b/eventmesh-dashboard/components/client/ClientTable.tsx
new file mode 100644
index 00000000..9a82fbf3
--- /dev/null
+++ b/eventmesh-dashboard/components/client/ClientTable.tsx
@@ -0,0 +1,245 @@
+/*
+ * 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 { useEffect, useState } from 'react';
+
+interface Client {
+ env: string,
+ subsystem: string,
+ url: string,
+ pid: number,
+ host: string,
+ port: number,
+ version: string,
+ idc: string,
+ group: string,
+ purpose: string,
+ protocol: string,
+}
+
+interface ClientProps {
+ host: string,
+ port: number,
+ group: string,
+ protocol: string,
+ url: string,
+}
+
+interface RemoveClientRequest {
+ host: string,
+ port: number,
+ protocol: string,
+ url: string,
+}
+
+const ClientRow = ({
+ host, port, group, protocol, url,
+}: ClientProps) => {
+ const toast = useToast();
+ const [loading, setLoading] = useState(false);
+ const onRemoveClick = async () => {
+ try {
+ setLoading(true);
+ await axios.delete<RemoveClientRequest>('/client', {
+ data: {
+ host,
+ port,
+ protocol,
+ url,
+ },
+ });
+ setLoading(false);
+ } catch (error) {
+ if (axios.isAxiosError(error)) {
+ toast({
+ title: 'Failed to remove the client',
+ description: error.message,
+ status: 'error',
+ duration: 3000,
+ isClosable: true,
+ });
+ }
+ }
+ };
+
+ return (
+ <Tr>
+ {protocol === 'TCP' && <Td>{`${host}:${port}`}</Td>}
+ {(protocol === 'HTTP' || protocol === 'gRPC') && <Td>{url}</Td>}
+ <Td>{group}</Td>
+ <Td>{protocol}</Td>
+ <Td>
+ <HStack>
+ <Button
+ isDisabled
+ colorScheme="red"
+ isLoading={loading}
+ onClick={onRemoveClick}
+ >
+ Remove
+ </Button>
+ </HStack>
+ </Td>
+ </Tr>
+ );
+};
+
+const ClientTable = () => {
+ const [searchInput, setSearchInput] = useState<string>('');
+ const handleSearchInputChange = (event: React.FormEvent<HTMLInputElement>) => {
+ setSearchInput(event.currentTarget.value);
+ };
+
+ const [protocolFilter, setProtocolFilter] = useState<string>('');
+ const handleProtocolSelectChange = (event: React.FormEvent<HTMLSelectElement>) => {
+ setProtocolFilter(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 [clientList, setClientList] = useState<Client[]>([]);
+ const toast = useToast();
+ useEffect(() => {
+ const fetch = async () => {
+ try {
+ const { data } = await axios.get<Client[]>('/client');
+ setClientList(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 clients',
+ description: error.message,
+ status: 'error',
+ duration: 3000,
+ isClosable: true,
+ });
+ setClientList([]);
+ }
+ }
+ };
+
+ 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 Protocol"
+ onChange={handleProtocolSelectChange}
+ >
+ <option value="TCP">TCP</option>
+ <option value="HTTP">HTTP</option>
+ <option value="gRPC">gRPC</option>
+ </Select>
+ <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 or url</Th>
+ <Th>Group</Th>
+ <Th>Protocol</Th>
+ <Th>Action</Th>
+ </Tr>
+ </Thead>
+ <Tbody>
+ {clientList && clientList.filter(({
+ host, port, group, protocol,
+ }) => {
+ const address = `${host}:${port}`;
+ if (searchInput && !address.includes(searchInput)) {
+ return false;
+ }
+ if (groupFilter && groupFilter !== group) {
+ return false;
+ }
+ if (protocolFilter && protocolFilter !== protocol) {
+ return false;
+ }
+ return true;
+ }).map(({
+ host, port, group, protocol, url,
+ }) => (
+ <ClientRow
+ host={host}
+ port={port}
+ group={group}
+ protocol={protocol}
+ url={url}
+ />
+ ))}
+ </Tbody>
+ </Table>
+ </TableContainer>
+ </Box>
+ );
+};
+
+export default ClientTable;
diff --git a/eventmesh-dashboard/components/index/Endpoint.tsx b/eventmesh-dashboard/components/index/Endpoint.tsx
new file mode 100644
index 00000000..6aa45a17
--- /dev/null
+++ b/eventmesh-dashboard/components/index/Endpoint.tsx
@@ -0,0 +1,104 @@
+/*
+ * 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, { useEffect, useState } from 'react';
+
+const Endpoint = () => {
+ const toast = useToast();
+ const [endpointInput, setEndpointInput] = useState('http://localhost:10106');
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ const endpoint = localStorage.getItem('endpoint');
+ if (endpoint === null) {
+ return;
+ }
+ setEndpointInput(endpoint);
+ axios.defaults.baseURL = endpoint;
+ }, []);
+
+ const handleEndpointInputChange = (event: React.FormEvent<HTMLInputElement>) => {
+ setEndpointInput(event.currentTarget.value);
+ };
+
+ const handleSaveButtonClick = async () => {
+ try {
+ setLoading(true);
+ await axios.get(`${endpointInput}/client`);
+ axios.defaults.baseURL = endpointInput;
+ localStorage.setItem('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/eventmesh-dashboard/next-env.d.ts b/eventmesh-dashboard/next-env.d.ts
new file mode 100644
index 00000000..f13f10eb
--- /dev/null
+++ b/eventmesh-dashboard/next-env.d.ts
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+/// <reference types="next" />
+/// <reference types="next/image-types/global" />
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/eventmesh-dashboard/next.config.js b/eventmesh-dashboard/next.config.js
new file mode 100644
index 00000000..b0f480c7
--- /dev/null
+++ b/eventmesh-dashboard/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/eventmesh-dashboard/package.json b/eventmesh-dashboard/package.json
new file mode 100644
index 00000000..1934a208
--- /dev/null
+++ b/eventmesh-dashboard/package.json
@@ -0,0 +1,39 @@
+{
+ "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/react": "^2.1.2",
+ "@emotion/react": "^11.9.0",
+ "@emotion/styled": "^11.8.1",
+ "@fontsource/inter": "^4.5.10",
+ "axios": "^0.27.2",
+ "framer-motion": "^6.3.6",
+ "next": "12.1.6",
+ "react": "18.1.0",
+ "react-dom": "18.1.0",
+ "react-icons": "^4.4.0",
+ "swr": "^1.3.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/eventmesh-dashboard/pages/_app.tsx b/eventmesh-dashboard/pages/_app.tsx
new file mode 100644
index 00000000..d76c7e3d
--- /dev/null
+++ b/eventmesh-dashboard/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 axios from 'axios';
+import type { AppProps } from 'next/app';
+import Sidebar from '../components/Sidebar';
+
+axios.defaults.baseURL = 'http://localhost:10106';
+
+const theme = extendTheme({
+ initialColorMode: 'light',
+ useSystemColorMode: true,
+ fonts: {
+ heading: 'Inter, sans-serif',
+ body: 'Inter, sans-serif',
+ },
+});
+
+const Application = ({ Component, pageProps }: AppProps) => (
+ <ChakraProvider theme={theme}>
+ <Sidebar>
+ <Component {...pageProps} />
+ </Sidebar>
+ </ChakraProvider>
+);
+
+export default Application;
diff --git a/eventmesh-dashboard/pages/_document.tsx b/eventmesh-dashboard/pages/_document.tsx
new file mode 100644
index 00000000..dd32fc0c
--- /dev/null
+++ b/eventmesh-dashboard/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/eventmesh-dashboard/pages/client.tsx b/eventmesh-dashboard/pages/client.tsx
new file mode 100644
index 00000000..bf7aba10
--- /dev/null
+++ b/eventmesh-dashboard/pages/client.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 ClientTable from '../components/client/ClientTable';
+
+const Client: NextPage = () => (
+ <>
+ <Head>
+ <title>Client | Apache EventMesh Dashboard</title>
+ </Head>
+ <ClientTable />
+ </>
+);
+
+export default Client;
diff --git a/eventmesh-dashboard/pages/index.tsx b/eventmesh-dashboard/pages/index.tsx
new file mode 100644
index 00000000..52961dcc
--- /dev/null
+++ b/eventmesh-dashboard/pages/index.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 Endpoint from '../components/index/Endpoint';
+
+const Index: NextPage = () => (
+ <>
+ <Head>
+ <title>Apache EventMesh Dashboard</title>
+ </Head>
+ <Endpoint />
+ </>
+);
+
+export default Index;
diff --git a/eventmesh-dashboard/public/favicon.ico b/eventmesh-dashboard/public/favicon.ico
new file mode 100644
index 00000000..718d6fea
Binary files /dev/null and b/eventmesh-dashboard/public/favicon.ico differ
diff --git a/eventmesh-dashboard/tsconfig.json b/eventmesh-dashboard/tsconfig.json
new file mode 100644
index 00000000..99710e85
--- /dev/null
+++ b/eventmesh-dashboard/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"]
+}
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/controller/ClientManageController.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/controller/ClientManageController.java
index 1c951276..ecb6cc4f 100644
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/controller/ClientManageController.java
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/controller/ClientManageController.java
@@ -18,6 +18,7 @@
package org.apache.eventmesh.runtime.admin.controller;
import org.apache.eventmesh.admin.rocketmq.controller.AdminController;
+import org.apache.eventmesh.runtime.admin.handler.ClientHandler;
import org.apache.eventmesh.runtime.admin.handler.QueryRecommendEventMeshHandler;
import org.apache.eventmesh.runtime.admin.handler.RedirectClientByIpPortHandler;
import org.apache.eventmesh.runtime.admin.handler.RedirectClientByPathHandler;
@@ -28,6 +29,8 @@ import org.apache.eventmesh.runtime.admin.handler.RejectClientBySubSystemHandler
import org.apache.eventmesh.runtime.admin.handler.ShowClientBySystemHandler;
import org.apache.eventmesh.runtime.admin.handler.ShowClientHandler;
import org.apache.eventmesh.runtime.admin.handler.ShowListenClientByTopicHandler;
+import org.apache.eventmesh.runtime.boot.EventMeshGrpcServer;
+import org.apache.eventmesh.runtime.boot.EventMeshHTTPServer;
import org.apache.eventmesh.runtime.boot.EventMeshTCPServer;
import java.io.IOException;
@@ -43,11 +46,19 @@ public class ClientManageController {
private static final Logger logger = LoggerFactory.getLogger(ClientManageController.class);
private EventMeshTCPServer eventMeshTCPServer;
+ private final EventMeshHTTPServer eventMeshHTTPServer;
+ private final EventMeshGrpcServer eventMeshGrpcServer;
private AdminController adminController;
- public ClientManageController(EventMeshTCPServer eventMeshTCPServer) {
+ public ClientManageController(
+ EventMeshTCPServer eventMeshTCPServer,
+ EventMeshHTTPServer eventMeshHTTPServer,
+ EventMeshGrpcServer eventMeshGrpcServer
+ ) {
this.eventMeshTCPServer = eventMeshTCPServer;
+ this.eventMeshHTTPServer = eventMeshHTTPServer;
+ this.eventMeshGrpcServer = eventMeshGrpcServer;
}
public void start() throws IOException {
@@ -64,6 +75,8 @@ public class ClientManageController {
server.createContext("/clientManage/showListenClientByTopic", new ShowListenClientByTopicHandler(eventMeshTCPServer));
server.createContext("/eventMesh/recommend", new QueryRecommendEventMeshHandler(eventMeshTCPServer));
+ server.createContext("/client", new ClientHandler(eventMeshTCPServer, eventMeshHTTPServer, eventMeshGrpcServer));
+
adminController = new AdminController();
adminController.run(server);
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/handler/ClientHandler.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/handler/ClientHandler.java
new file mode 100644
index 00000000..a317c961
--- /dev/null
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/handler/ClientHandler.java
@@ -0,0 +1,262 @@
+/*
+ * 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.
+ */
+
+package org.apache.eventmesh.runtime.admin.handler;
+
+import org.apache.eventmesh.common.protocol.tcp.UserAgent;
+import org.apache.eventmesh.runtime.admin.request.DeleteClientRequest;
+import org.apache.eventmesh.runtime.admin.response.Error;
+import org.apache.eventmesh.runtime.admin.response.GetClientResponse;
+import org.apache.eventmesh.runtime.admin.utils.HttpExchangeUtils;
+import org.apache.eventmesh.runtime.admin.utils.JsonUtils;
+import org.apache.eventmesh.runtime.boot.EventMeshGrpcServer;
+import org.apache.eventmesh.runtime.boot.EventMeshHTTPServer;
+import org.apache.eventmesh.runtime.boot.EventMeshTCPServer;
+import org.apache.eventmesh.runtime.core.protocol.grpc.consumer.ConsumerManager;
+import org.apache.eventmesh.runtime.core.protocol.grpc.consumer.consumergroup.ConsumerGroupClient;
+import org.apache.eventmesh.runtime.core.protocol.http.processor.inf.Client;
+import org.apache.eventmesh.runtime.core.protocol.tcp.client.EventMeshTcp2Client;
+import org.apache.eventmesh.runtime.core.protocol.tcp.client.group.ClientSessionGroupMapping;
+import org.apache.eventmesh.runtime.core.protocol.tcp.client.session.Session;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+
+/**
+ * The client handler
+ */
+public class ClientHandler implements HttpHandler {
+ private static final Logger logger = LoggerFactory.getLogger(ShowClientHandler.class);
+
+ private final EventMeshTCPServer eventMeshTCPServer;
+ private final EventMeshHTTPServer eventMeshHTTPServer;
+ private final EventMeshGrpcServer eventMeshGrpcServer;
+
+ public ClientHandler(
+ EventMeshTCPServer eventMeshTCPServer,
+ EventMeshHTTPServer eventMeshHTTPServer,
+ EventMeshGrpcServer eventMeshGrpcServer
+ ) {
+ this.eventMeshTCPServer = eventMeshTCPServer;
+ this.eventMeshHTTPServer = eventMeshHTTPServer;
+ this.eventMeshGrpcServer = eventMeshGrpcServer;
+ }
+
+ /**
+ * OPTION /client
+ */
+ void preflight(HttpExchange httpExchange) throws IOException {
+ httpExchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
+ httpExchange.getResponseHeaders().add("Access-Control-Allow-Method", "*");
+ httpExchange.getResponseHeaders().add("Access-Control-Max-Age", "86400");
+ httpExchange.sendResponseHeaders(200, 0);
+ OutputStream out = httpExchange.getResponseBody();
+ out.close();
+ }
+
+ /**
+ * DELETE /client
+ */
+ void delete(HttpExchange httpExchange) throws IOException {
+ OutputStream out = httpExchange.getResponseBody();
+ try {
+ String request = HttpExchangeUtils.streamToString(httpExchange.getRequestBody());
+ DeleteClientRequest deleteClientRequest = JsonUtils.toObject(request, DeleteClientRequest.class);
+ String host = deleteClientRequest.host;
+ int port = deleteClientRequest.port;
+ String protocol = deleteClientRequest.protocol;
+ String url = deleteClientRequest.url;
+
+ if (Objects.equals(protocol, "TCP")) {
+ ClientSessionGroupMapping clientSessionGroupMapping = eventMeshTCPServer.getClientSessionGroupMapping();
+ ConcurrentHashMap<InetSocketAddress, Session> sessionMap = clientSessionGroupMapping.getSessionMap();
+ if (!sessionMap.isEmpty()) {
+ for (Map.Entry<InetSocketAddress, Session> entry : sessionMap.entrySet()) {
+ if (entry.getKey().getHostString().equals(host) && entry.getKey().getPort() == port) {
+ EventMeshTcp2Client.serverGoodby2Client(
+ eventMeshTCPServer,
+ entry.getValue(),
+ clientSessionGroupMapping
+ );
+ }
+ }
+ }
+ }
+
+ if (Objects.equals(protocol, "HTTP")) {
+ for (List<Client> clientList : eventMeshHTTPServer.localClientInfoMapping.values()) {
+ clientList.removeIf(client -> Objects.equals(client.url, url));
+ }
+ }
+
+ if (Objects.equals(protocol, "gRPC")) {
+ ConsumerManager consumerManager = eventMeshGrpcServer.getConsumerManager();
+ Map<String, List<ConsumerGroupClient>> clientTable = consumerManager.getClientTable();
+ for (List<ConsumerGroupClient> clientList : clientTable.values()) {
+ for (ConsumerGroupClient client : clientList) {
+ if (Objects.equals(client.getUrl(), url)) {
+ consumerManager.deregisterClient(client);
+ }
+ }
+ }
+ }
+
+ httpExchange.getResponseHeaders().add("Content-Type", "application/json");
+ httpExchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
+ httpExchange.sendResponseHeaders(200, 0);
+ } catch (Exception e) {
+ Error error = new Error(e.toString());
+ String result = JsonUtils.toJson(error);
+ httpExchange.sendResponseHeaders(500, result.getBytes().length);
+ out.write(result.getBytes());
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ logger.warn("out close failed...", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * GET /client
+ * Return a response that contains the list of clients
+ */
+ void list(HttpExchange httpExchange) throws IOException {
+ OutputStream out = httpExchange.getResponseBody();
+ httpExchange.getResponseHeaders().add("Content-Type", "application/json");
+ httpExchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
+
+ try {
+ // Get the list of TCP clients
+ ClientSessionGroupMapping clientSessionGroupMapping = eventMeshTCPServer.getClientSessionGroupMapping();
+ Map<InetSocketAddress, Session> sessionMap = clientSessionGroupMapping.getSessionMap();
+ List<GetClientResponse> getClientResponseList = new ArrayList<>();
+ for (Session session : sessionMap.values()) {
+ UserAgent userAgent = session.getClient();
+ GetClientResponse getClientResponse = new GetClientResponse(
+ Optional.ofNullable(userAgent.getEnv()).orElse(""),
+ Optional.ofNullable(userAgent.getSubsystem()).orElse(""),
+ Optional.ofNullable(userAgent.getPath()).orElse(""),
+ String.valueOf(userAgent.getPid()),
+ Optional.ofNullable(userAgent.getHost()).orElse(""),
+ userAgent.getPort(),
+ Optional.ofNullable(userAgent.getVersion()).orElse(""),
+ Optional.ofNullable(userAgent.getIdc()).orElse(""),
+ Optional.ofNullable(userAgent.getGroup()).orElse(""),
+ Optional.ofNullable(userAgent.getPurpose()).orElse(""),
+ "TCP"
+ );
+ getClientResponseList.add(getClientResponse);
+ }
+
+ // Get the list of HTTP clients
+ for (List<Client> clientList : eventMeshHTTPServer.localClientInfoMapping.values()) {
+ for (Client client : clientList) {
+ GetClientResponse getClientResponse = new GetClientResponse(
+ Optional.ofNullable(client.env).orElse(""),
+ Optional.ofNullable(client.sys).orElse(""),
+ Optional.ofNullable(client.url).orElse(""),
+ "0",
+ Optional.ofNullable(client.hostname).orElse(""),
+ 0,
+ Optional.ofNullable(client.apiVersion).orElse(""),
+ Optional.ofNullable(client.idc).orElse(""),
+ Optional.ofNullable(client.consumerGroup).orElse(""),
+ "",
+ "HTTP"
+ );
+ getClientResponseList.add(getClientResponse);
+ }
+ }
+
+ // Get the list of gRPC clients
+ ConsumerManager consumerManager = eventMeshGrpcServer.getConsumerManager();
+ Map<String, List<ConsumerGroupClient>> clientTable = consumerManager.getClientTable();
+ for (List<ConsumerGroupClient> clientList : clientTable.values()) {
+ for (ConsumerGroupClient client : clientList) {
+ GetClientResponse getClientResponse = new GetClientResponse(
+ Optional.ofNullable(client.env).orElse(""),
+ Optional.ofNullable(client.sys).orElse(""),
+ Optional.ofNullable(client.url).orElse(""),
+ "0",
+ Optional.ofNullable(client.hostname).orElse(""),
+ 0,
+ Optional.ofNullable(client.apiVersion).orElse(""),
+ Optional.ofNullable(client.idc).orElse(""),
+ Optional.ofNullable(client.consumerGroup).orElse(""),
+ "",
+ "gRPC"
+ );
+ getClientResponseList.add(getClientResponse);
+ }
+ }
+
+ getClientResponseList.sort((lhs, rhs) -> {
+ if (lhs.host.equals(rhs.host)) {
+ return lhs.host.compareTo(rhs.host);
+ }
+ return Integer.compare(rhs.port, lhs.port);
+ });
+
+ String result = JsonUtils.toJson(getClientResponseList);
+ httpExchange.sendResponseHeaders(200, result.getBytes().length);
+ out.write(result.getBytes());
+ } catch (Exception e) {
+ Error error = new Error(e.toString());
+ String result = JsonUtils.toJson(error);
+ httpExchange.sendResponseHeaders(500, result.getBytes().length);
+ out.write(result.getBytes());
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ logger.warn("out close failed...", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void handle(HttpExchange httpExchange) throws IOException {
+ if (httpExchange.getRequestMethod().equals("OPTION")) {
+ preflight(httpExchange);
+ }
+ if (httpExchange.getRequestMethod().equals("GET")) {
+ list(httpExchange);
+ }
+ if (httpExchange.getRequestMethod().equals("DELETE")) {
+ delete(httpExchange);
+ }
+ }
+}
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/request/DeleteClientRequest.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/request/DeleteClientRequest.java
new file mode 100644
index 00000000..d7ec05bc
--- /dev/null
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/request/DeleteClientRequest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+package org.apache.eventmesh.runtime.admin.request;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class DeleteClientRequest {
+ public String host;
+ public int port;
+ public String protocol;
+ public String url;
+
+ @JsonCreator
+ public DeleteClientRequest(
+ @JsonProperty("host") String host,
+ @JsonProperty("port") int port,
+ @JsonProperty("protocol") String protocol,
+ @JsonProperty("url") String url
+ ) {
+ super();
+ this.host = host;
+ this.port = port;
+ this.protocol = protocol;
+ this.url = url;
+ }
+}
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/response/Error.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/response/Error.java
new file mode 100644
index 00000000..f559ef08
--- /dev/null
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/response/Error.java
@@ -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.
+ */
+
+package org.apache.eventmesh.runtime.admin.response;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Error {
+ public String message;
+
+ @JsonCreator
+ public Error(
+ @JsonProperty("message") String message
+ ) {
+ super();
+ this.message = message;
+ }
+}
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/response/GetClientResponse.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/response/GetClientResponse.java
new file mode 100644
index 00000000..381451a7
--- /dev/null
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/response/GetClientResponse.java
@@ -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.
+ */
+
+package org.apache.eventmesh.runtime.admin.response;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class GetClientResponse {
+ public String env;
+ public String subsystem;
+ public String url;
+ public String pid;
+ public String host;
+ public int port;
+ public String version;
+ public String idc;
+ public String group;
+ public String purpose;
+ public String protocol;
+
+ @JsonCreator
+ public GetClientResponse(
+ @JsonProperty("env") String env,
+ @JsonProperty("subsystem") String subsystem,
+ @JsonProperty("url") String url,
+ @JsonProperty("pid") String pid,
+ @JsonProperty("host") String host,
+ @JsonProperty("port") int port,
+ @JsonProperty("version") String version,
+ @JsonProperty("idc") String idc,
+ @JsonProperty("group") String group,
+ @JsonProperty("purpose") String purpose,
+ @JsonProperty("protocol") String protocol
+ ) {
+ super();
+ this.env = env;
+ this.subsystem = subsystem;
+ this.url = url;
+ this.pid = pid;
+ this.host = host;
+ this.port = port;
+ this.idc = idc;
+ this.group = group;
+ this.purpose = purpose;
+ this.version = version;
+ this.protocol = protocol;
+ }
+}
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/utils/HttpExchangeUtils.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/utils/HttpExchangeUtils.java
new file mode 100644
index 00000000..a378d030
--- /dev/null
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/utils/HttpExchangeUtils.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package org.apache.eventmesh.runtime.admin.utils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+public class HttpExchangeUtils {
+ public static String streamToString(InputStream stream) throws IOException {
+ InputStreamReader isr = new InputStreamReader(stream, StandardCharsets.UTF_8);
+ BufferedReader bufferedReader = new BufferedReader(isr);
+
+ int b;
+ StringBuilder buffer = new StringBuilder();
+ while ((b = bufferedReader.read()) != -1) {
+ buffer.append((char) b);
+ }
+
+ bufferedReader.close();
+ isr.close();
+ return buffer.toString();
+ }
+}
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/utils/JsonUtils.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/utils/JsonUtils.java
new file mode 100644
index 00000000..4a6fb75d
--- /dev/null
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/admin/utils/JsonUtils.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+package org.apache.eventmesh.runtime.admin.utils;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+
+public class JsonUtils {
+ private static final ObjectMapper objectMapper;
+
+ static {
+ objectMapper = new ObjectMapper();
+ objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+ objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
+ }
+
+ public static <T> byte[] serialize(String topic, Class<T> data) throws JsonProcessingException {
+ if (data == null) {
+ return null;
+ }
+ return objectMapper.writeValueAsBytes(data);
+ }
+
+ public static String toJson(Object obj) throws JsonProcessingException {
+ if (obj == null) {
+ return null;
+ }
+ return objectMapper.writeValueAsString(obj);
+ }
+
+ public static <T> T toObject(String json, Class<T> clazz) throws JsonProcessingException {
+ return objectMapper.readValue(json, clazz);
+ }
+
+ public static <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException {
+ if (bytes == null || bytes.length == 0) {
+ return null;
+ }
+
+ return objectMapper.readValue(bytes, clazz);
+ }
+
+ public static <T> T deserialize(Class<T> clazz, String json) throws IOException {
+ if (json == null || json.length() == 0) {
+ return null;
+ }
+
+ return objectMapper.readValue(json, clazz);
+ }
+}
\ No newline at end of file
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/boot/EventMeshServer.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/boot/EventMeshServer.java
index a36a48ab..7d114b04 100644
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/boot/EventMeshServer.java
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/boot/EventMeshServer.java
@@ -19,6 +19,7 @@ package org.apache.eventmesh.runtime.boot;
import org.apache.eventmesh.common.utils.ConfigurationContextUtil;
import org.apache.eventmesh.runtime.acl.Acl;
+import org.apache.eventmesh.runtime.admin.controller.ClientManageController;
import org.apache.eventmesh.runtime.common.ServiceState;
import org.apache.eventmesh.runtime.configuration.EventMeshGrpcConfiguration;
import org.apache.eventmesh.runtime.configuration.EventMeshHTTPConfiguration;
@@ -117,6 +118,9 @@ public class EventMeshServer {
}
}
+ ClientManageController clientManageController = new ClientManageController(eventMeshTCPServer, eventMeshHTTPServer, eventMeshGrpcServer);
+ clientManageController.start();
+
String eventStore = System
.getProperty(EventMeshConstants.EVENT_STORE_PROPERTIES, System.getenv(EventMeshConstants.EVENT_STORE_ENV));
logger.info("eventStore : {}", eventStore);
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/boot/EventMeshTCPServer.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/boot/EventMeshTCPServer.java
index cfff7040..c992ed28 100644
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/boot/EventMeshTCPServer.java
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/boot/EventMeshTCPServer.java
@@ -71,8 +71,6 @@ public class EventMeshTCPServer extends AbstractRemotingServer {
private EventMeshTcpMonitor eventMeshTcpMonitor;
- private ClientManageController clientManageController;
-
private EventMeshServer eventMeshServer;
private EventMeshTCPConfiguration eventMeshTCPConfiguration;
@@ -93,14 +91,6 @@ public class EventMeshTCPServer extends AbstractRemotingServer {
this.clientSessionGroupMapping = clientSessionGroupMapping;
}
- public ClientManageController getClientManageController() {
- return clientManageController;
- }
-
- public void setClientManageController(ClientManageController clientManageController) {
- this.clientManageController = clientManageController;
- }
-
public ScheduledExecutorService getScheduler() {
return scheduler;
}
@@ -208,8 +198,6 @@ public class EventMeshTCPServer extends AbstractRemotingServer {
globalTrafficShapingHandler = newGTSHandler();
- clientManageController = new ClientManageController(this);
-
clientSessionGroupMapping = new ClientSessionGroupMapping(this);
clientSessionGroupMapping.init();
@@ -243,8 +231,6 @@ public class EventMeshTCPServer extends AbstractRemotingServer {
eventMeshTcpMonitor.start();
- clientManageController.start();
-
if (eventMeshTCPConfiguration.eventMeshServerRegistryEnable) {
this.register();
eventMeshRebalanceService.start();
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/grpc/consumer/ConsumerManager.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/grpc/consumer/ConsumerManager.java
index 1f881153..5f9f6f26 100644
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/grpc/consumer/ConsumerManager.java
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/grpc/consumer/ConsumerManager.java
@@ -61,6 +61,10 @@ public class ConsumerManager {
this.eventMeshGrpcServer = eventMeshGrpcServer;
}
+ public Map<String, List<ConsumerGroupClient>> getClientTable() {
+ return clientTable;
+ }
+
public void init() throws Exception {
logger.info("Grpc ConsumerManager initialized......");
}
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/grpc/consumer/consumergroup/ConsumerGroupClient.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/grpc/consumer/consumergroup/ConsumerGroupClient.java
index 679ed517..1d17e416 100644
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/grpc/consumer/consumergroup/ConsumerGroupClient.java
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/grpc/consumer/consumergroup/ConsumerGroupClient.java
@@ -32,31 +32,31 @@ import lombok.Getter;
@Getter
public class ConsumerGroupClient {
- private final String env;
+ public final String env;
- private final String idc;
+ public final String idc;
- private final String consumerGroup;
+ public final String consumerGroup;
- private final String topic;
+ public final String topic;
private final GrpcType grpcType;
- private String url;
+ public String url;
private EventEmitter<SimpleMessage> eventEmitter;
private final SubscriptionMode subscriptionMode;
- private final String sys;
+ public final String sys;
- private final String ip;
+ public final String ip;
- private final String pid;
+ public final String pid;
- private final String hostname;
+ public final String hostname;
- private final String apiVersion;
+ public final String apiVersion;
private Date lastUpTime;
diff --git a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/http/consumer/ConsumerManager.java b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/http/consumer/ConsumerManager.java
index 3e22d307..1f2cf758 100644
--- a/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/http/consumer/ConsumerManager.java
+++ b/eventmesh-runtime/src/main/java/org/apache/eventmesh/runtime/core/protocol/http/consumer/ConsumerManager.java
@@ -340,4 +340,8 @@ public class ConsumerManager {
logger.error("onChange event:{} err", event, ex);
}
}
+
+ public ConcurrentHashMap<String, ConsumerGroupManager> getClientTable() {
+ return consumerTable;
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@eventmesh.apache.org
For additional commands, e-mail: commits-help@eventmesh.apache.org