[GSOC][RIP-78][ISSUES#308]: Add part of refactored front-end files (#309)

This commit is contained in:
Crazylychee
2025-06-16 13:46:41 +08:00
committed by GitHub
parent eb51da6ca4
commit b75ace4804
28 changed files with 19709 additions and 28245 deletions

70
frontend-new/README.md Normal file
View File

@@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

17534
frontend-new/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

57
frontend-new/package.json Normal file
View File

@@ -0,0 +1,57 @@
{
"name": "frontend-new",
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^6.0.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"antd": "^5.25.1",
"axios": "^1.9.0",
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"framer-motion": "^12.16.0",
"http-proxy-middleware": "^3.0.5",
"i18next": "^25.1.3",
"moment": "^2.30.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-i18next": "^15.5.1",
"react-redux": "^9.2.0",
"react-router-dom": "^7.6.0",
"react-scripts": "5.0.1",
"react-toastify": "^11.0.5",
"redux": "^5.0.1",
"typescript": "^4.8.3",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "cross-env PORT=3003 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"cross-env": "^7.0.3"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

17
frontend-new/src/App.css Normal file
View File

@@ -0,0 +1,17 @@
/*
* 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.
*/

View File

@@ -14,41 +14,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.App {
text-align: center;
import React from 'react';
import AppRouter from './router'; // router/index.jsx
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import {ConfigProvider} from "antd";
import {useTheme} from "./store/context/ThemeContext";
function App() {
const {currentTheme} = useTheme();
return (
<>
<ConfigProvider theme={currentTheme}>
<ToastContainer />
<AppRouter />
</ConfigProvider>
</>
);
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 40vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
export default App;

View File

@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { render, screen } from '@testing-library/react';
import App from './App';

View File

@@ -0,0 +1,958 @@
/*
* 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.
*/
const appConfig = {
apiBaseUrl: 'http://localhost:8082' // 请替换为你的实际 API Base URL
};
let _redirectHandler = null;
const remoteApi = {
setRedirectHandler: (handler) => {
_redirectHandler = handler;
},
buildUrl: (endpoint) => {
if (endpoint.charAt(0) === '/') {
endpoint = endpoint.substring(1);
}
return `${appConfig.apiBaseUrl}/${endpoint}`;
},
_fetch: async (url, options) => {
try {
// 在 options 中添加 credentials: 'include'
const response = await fetch(url, {
...options, // 保留原有的 options
credentials: 'include' // 关键改动:允许发送 Cookie
});
// 检查响应是否被重定向,并且最终的 URL 包含了登录页的路径。
// 这是会话过期或需要认证时后端重定向到登录页的常见模式。
// 注意fetch 会自动跟随 GET 请求的 3xx 重定向,所以我们检查的是 response.redirected。
if (response.redirected) {
if (_redirectHandler) {
_redirectHandler(); // 如果设置了重定向处理函数,则调用它
}
return { __isRedirectHandled: true };
}
return response;
} catch (error) {
console.error("Fetch 请求出错:", error);
throw error;
}
},
queryTopic: async (skipSysProcess) => {
try {
const params = new URLSearchParams();
if (skipSysProcess) {
params.append('skipSysProcess', 'true');
}
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/list.query?${params.toString()}`));
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching topic list:", error);
}
},
listUsers: async (brokerAddress) => {
const params = new URLSearchParams();
if (brokerAddress) params.append('brokerAddress', brokerAddress);
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/listUsers?${params.toString()}`));
return await response.json();
},
createUser: async (brokerAddress, userInfo) => {
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/createUser'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ brokerAddress, userInfo })
});
return await response.json(); // 返回字符串消息
},
updateUser: async (brokerAddress, userInfo) => {
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/updateUser'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ brokerAddress, userInfo })
});
return await response.json();
},
deleteUser: async (brokerAddress, username) => {
const params = new URLSearchParams();
if (brokerAddress) params.append('brokerAddress', brokerAddress);
params.append('username', username);
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/deleteUser?${params.toString()}`), {
method: 'DELETE'
});
return await response.json();
},
// --- ACL 权限相关 API ---
listAcls: async (brokerAddress, searchParam) => {
const params = new URLSearchParams();
if (brokerAddress) params.append('brokerAddress', brokerAddress);
if (searchParam) params.append('searchParam', searchParam);
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/listAcls?${params.toString()}`));
return await response.json();
},
createAcl: async (brokerAddress, subject, policies) => {
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/createAcl'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ brokerAddress, subject, policies })
});
return await response.json();
},
updateAcl: async (brokerAddress, subject, policies) => {
const response = await remoteApi._fetch(remoteApi.buildUrl('/acl/updateAcl'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ brokerAddress, subject, policies })
});
return await response.json();
},
deleteAcl: async (brokerAddress, subject, resource) => {
const params = new URLSearchParams();
if (brokerAddress) params.append('brokerAddress', brokerAddress);
params.append('subject', subject);
if (resource) params.append('resource', resource);
const response = await remoteApi._fetch(remoteApi.buildUrl(`/acl/deleteAcl?${params.toString()}`), {
method: 'DELETE'
});
return await response.json();
},
queryMessageByMessageId: async (msgId, topic, callback) => {
try {
const params = new URLSearchParams();
params.append('msgId', msgId);
params.append('topic', topic);
const response = await remoteApi._fetch(remoteApi.buildUrl(`/messageTrace/viewMessage.query?${params.toString()}`));
const data = await response.json();
return data
} catch (error) {
console.error("Error querying message by ID:", error);
callback({ status: 1, errMsg: "Failed to query message by ID" });
}
},
queryMessageTraceByMessageId: async (msgId, traceTopic, callback) => {
try {
const params = new URLSearchParams();
params.append('msgId', msgId);
params.append('traceTopic', traceTopic);
const response = await remoteApi._fetch(remoteApi.buildUrl(`/messageTrace/viewMessageTraceGraph.query?${params.toString()}`));
const data = await response.json();
return data;
} catch (error) {
console.error("Error querying message trace:", error);
}
},
queryDlqMessageByConsumerGroup: async (consumerGroup, beginTime, endTime, pageNum, pageSize, taskId) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/dlqMessage/queryDlqMessageByConsumerGroup.query"), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
topic: `%DLQ%${consumerGroup}`,
begin: beginTime,
end: endTime,
pageNum: pageNum,
pageSize: pageSize,
taskId: taskId,
}),
});
const data = await response.json();
return data;
} catch (error) {
console.error("Error querying DLQ messages by consumer group:", error);
return { status: 1, errMsg: "Failed to query DLQ messages by consumer group" };
}
},
resendDlqMessage: async (msgId, consumerGroup, topic) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/message/consumeMessageDirectly.do"), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
params: {
msgId: msgId,
consumerGroup: consumerGroup,
topic: topic
},
});
const data = await response.json();
return data;
} catch (error) {
console.error("Error resending DLQ message:", error);
return { status: 1, errMsg: "Failed to resend DLQ message" };
}
},
exportDlqMessage: async (msgId, consumerGroup) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/dlqMessage/exportDlqMessage.do?msgId=${msgId}&consumerGroup=${consumerGroup}`));
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 假设服务器总是返回 JSON
const data = await response.json();
// 1. 打开一个新的空白窗口
const newWindow = window.open('', '_blank');
if (!newWindow) {
// 浏览器可能会阻止弹窗,需要用户允许
return { status: 1, errMsg: "Failed to open new window. Please allow pop-ups for this site." };
}
// 2. 将 JSON 数据格式化后写入新窗口
newWindow.document.write('<html><head><title>DLQ 导出内容</title></head><body>');
newWindow.document.write('<h1>DLQ 导出 JSON 内容</h1>');
// 使用 <pre> 标签保持格式,并使用 JSON.stringify 格式化 JSON 以便于阅读
newWindow.document.write('<pre>' + JSON.stringify(data, null, 2) + '</pre>');
newWindow.document.write('</body></html>');
newWindow.document.close(); // 关闭文档流,确保内容显示
return { status: 0, msg: "导出请求成功,内容已在新页面显示" };
} catch (error) {
console.error("Error exporting DLQ message:", error);
return { status: 1, errMsg: "Failed to export DLQ message: " + error.message };
}
},
batchResendDlqMessage: async (messages) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/dlqMessage/batchResendDlqMessage.do"), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(messages),
});
const data = await response.json();
return data;
} catch (error) {
console.error("Error batch resending DLQ messages:", error);
return { status: 1, errMsg: "Failed to batch resend DLQ messages" };
}
},
/**
* Queries messages by topic with pagination.
* @param {string} topic The topic to query.
* @param {number} begin Timestamp in milliseconds for the start time.
* @param {number} end Timestamp in milliseconds for the end time.
* @param {number} pageNum The current page number (1-based).
* @param {number} pageSize The number of items per page.
* @param {string} taskId Optional task ID for continuous queries.
* @returns {Promise<Object>} The API response.
*/
queryMessagePageByTopic: async (topic, begin, end, pageNum, pageSize, taskId) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/message/queryMessagePageByTopic.query"), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
topic: topic,
begin: begin,
end: end,
pageNum: pageNum,
pageSize: pageSize,
taskId: taskId
})
});
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching message page by topic:", error);
}
},
/**
* Queries messages by topic and key.
* @param {string} topic The topic to query.
* @param {string} key The message key to query.
* @returns {Promise<Object>} The API response.
*/
queryMessageByTopicAndKey: async (topic, key) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/message/queryMessageByTopicAndKey.query?topic=${topic}&key=${key}`));
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching message by topic and key:", error);
}
},
/**
* Views a message by its message ID and topic.
* @param {string} msgId The message ID.
* @param {string} topic The topic of the message.
* @returns {Promise<Object>} The API response.
*/
viewMessage: async (msgId, topic) => {
try {
const encodedTopic = encodeURIComponent(topic);
const url = remoteApi.buildUrl(
`/message/viewMessage.query?msgId=${msgId}&topic=${encodedTopic}`
);
const response = await remoteApi._fetch(url);
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching message by message ID:", error);
}
},
/**
* Resends a message directly to a consumer group.
* @param {string} msgId The message ID.
* @param {string} consumerGroup The consumer group to resend to.
* @param {string} topic The topic of the message.
* @returns {Promise<Object>} The API response.
*/
resendMessageDirectly: async (msgId, consumerGroup, topic) => {
topic = encodeURIComponent(topic)
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/message/consumeMessageDirectly.do?msgId=${msgId}&consumerGroup=${consumerGroup}&topic=${topic}`), {
method: 'POST',
});
const data = await response.json();
return data;
} catch (error) {
console.error("Error resending message directly:", error);
}
},
queryProducerConnection: async (topic, producerGroup, callback) => {
topic = encodeURIComponent(topic)
producerGroup = encodeURIComponent(producerGroup)
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/producer/producerConnection.query?topic=${topic}&producerGroup=${producerGroup}`));
const data = await response.json();
callback(data);
} catch (error) {
console.error("Error fetching producer connection list:", error);
callback({ status: 1, errMsg: "Failed to fetch producer connection list" }); // Simulate error response
}
},
queryConsumerGroupList: async (skipSysGroup = false) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/groupList.query?skipSysGroup=${skipSysGroup}`));
const data = await response.json();
return data;
} catch (error) {
console.error("Error fetching consumer group list:", error);
return { status: 1, errMsg: "Failed to fetch consumer group list" };
}
},
refreshConsumerGroup: async (consumerGroup) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/group.refresh?consumerGroup=${consumerGroup}`));
const data = await response.json();
return data;
} catch (error) {
console.error(`Error refreshing consumer group ${consumerGroup}:`, error);
return { status: 1, errMsg: `Failed to refresh consumer group ${consumerGroup}` };
}
},
refreshAllConsumerGroup: async () => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/consumer/group.refresh.all"));
const data = await response.json();
return data;
} catch (error) {
console.error("Error refreshing all consumer groups:", error);
return { status: 1, errMsg: "Failed to refresh all consumer groups" };
}
},
queryConsumerMonitorConfig: async (consumeGroupName) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/monitor/consumerMonitorConfigByGroupName.query?consumeGroupName=${consumeGroupName}`));
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching monitor config for ${consumeGroupName}:`, error);
return { status: 1, errMsg: `Failed to fetch monitor config for ${consumeGroupName}` };
}
},
createOrUpdateConsumerMonitor: async (consumeGroupName, minCount, maxDiffTotal) => {
consumeGroupName = encodeURIComponent(consumeGroupName)
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/monitor/createOrUpdateConsumerMonitor.do"), {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ consumeGroupName, minCount, maxDiffTotal })
});
const data = await response.json();
return data;
} catch (error) {
console.error("Error creating or updating consumer monitor:", error);
return { status: 1, errMsg: "Failed to create or update consumer monitor" };
}
},
fetchBrokerNameList: async (consumerGroup) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/fetchBrokerNameList.query?consumerGroup=${consumerGroup}`));
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching broker name list for ${consumerGroup}:`, error);
return { status: 1, errMsg: `Failed to fetch broker name list for ${consumerGroup}` };
}
},
deleteConsumerGroup: async (groupName, brokerNameList) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/consumer/deleteSubGroup.do"), {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ groupName, brokerNameList })
});
const data = await response.json();
return data;
} catch (error) {
console.error(`Error deleting consumer group ${groupName}:`, error);
return { status: 1, errMsg: `Failed to delete consumer group ${groupName}` };
}
},
queryConsumerConfig: async (consumerGroup) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/examineSubscriptionGroupConfig.query?consumerGroup=${consumerGroup}`));
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching consumer config for ${consumerGroup}:`, error);
return { status: 1, errMsg: `Failed to fetch consumer config for ${consumerGroup}` };
}
},
createOrUpdateConsumer: async (consumerRequest) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/consumer/createOrUpdate.do"), {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(consumerRequest)
});
const data = await response.json();
return data;
} catch (error) {
console.error("Error creating or updating consumer:", error);
return { status: 1, errMsg: "Failed to create or update consumer" };
}
},
queryTopicByConsumer: async (consumerGroup, address) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/queryTopicByConsumer.query?consumerGroup=${consumerGroup}&address=${address}`));
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching topics for consumer group ${consumerGroup}:`, error);
return { status: 1, errMsg: `Failed to fetch topics for consumer group ${consumerGroup}` };
}
},
queryConsumerConnection: async (consumerGroup, address) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/consumerConnection.query?consumerGroup=${consumerGroup}&address=${address}`));
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching consumer connections for ${consumerGroup}:`, error);
return { status: 1, errMsg: `Failed to fetch consumer connections for ${consumerGroup}` };
}
},
queryConsumerRunningInfo: async (consumerGroup, clientId, jstack = false) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/consumer/consumerRunningInfo.query?consumerGroup=${consumerGroup}&clientId=${clientId}&jstack=${jstack}`));
const data = await response.json();
return data;
} catch (error) {
console.error(`Error fetching running info for client ${clientId} in group ${consumerGroup}:`, error);
return { status: 1, errMsg: `Failed to fetch running info for client ${clientId} in group ${consumerGroup}` };
}
},
queryTopicList: async () => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/topic/list.queryTopicType"));
return await response.json();
} catch (error) {
console.error("Error fetching topic list:", error);
return { status: 1, errMsg: "Failed to fetch topic list" };
}
},
refreshTopicList: async () => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/topic/refresh"), {
method: 'POST'
});
const result = await response.json();
if (result.status === 0 && result.data === true) {
return remoteApi.queryTopicList();
}
return result;
} catch (error) {
console.error("Error refreshing topic list:", error);
return { status: 1, errMsg: "Failed to refresh topic list" };
}
},
deleteTopic: async (topic) => {
try {
const url = remoteApi.buildUrl(`/topic/deleteTopic.do?topic=${encodeURIComponent(topic)}`);
const response = await remoteApi._fetch(url, {
method: 'POST', // 仍然使用 POST 方法,但参数在 URL 中
headers: {
'Content-Type': 'application/json', // 可以根据你的后端需求决定是否需要这个 header
},
// body: JSON.stringify({ topic }) // 移除 body
});
return await response.json();
} catch (error) {
console.error("Error deleting topic:", error);
return { status: 1, errMsg: "Failed to delete topic" };
}
},
getTopicStats: async (topic) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/stats.query?topic=${topic}`));
return await response.json();
} catch (error) {
console.error("Error fetching topic stats:", error);
return { status: 1, errMsg: "Failed to fetch topic stats" };
}
},
getTopicRoute: async (topic) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/route.query?topic=${topic}`));
return await response.json();
} catch (error) {
console.error("Error fetching topic route:", error);
return { status: 1, errMsg: "Failed to fetch topic route" };
}
},
getTopicConsumers: async (topic) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/queryConsumerByTopic.query?topic=${topic}`));
return await response.json();
} catch (error) {
console.error("Error fetching topic consumers:", error);
return { status: 1, errMsg: "Failed to fetch topic consumers" };
}
},
getTopicConsumerGroups: async (topic) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/queryTopicConsumerInfo.query?topic=${topic}`));
return await response.json();
} catch (error) {
console.error("Error fetching consumer groups:", error);
return { status: 1, errMsg: "Failed to fetch consumer groups" };
}
},
getTopicConfig: async (topic) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/topic/examineTopicConfig.query?topic=${topic}`));
return await response.json();
} catch (error) {
console.error("Error fetching topic config:", error);
return { status: 1, errMsg: "Failed to fetch topic config" };
}
},
getClusterList: async () => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/cluster/list.query"));
return await response.json();
} catch (error) {
console.error("Error fetching cluster list:", error);
return { status: 1, errMsg: "Failed to fetch cluster list" };
}
},
createOrUpdateTopic: async (topicData) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/topic/createOrUpdate.do"), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(topicData)
});
return await response.json();
} catch (error) {
console.error("Error creating/updating topic:", error);
return { status: 1, errMsg: "Failed to create/update topic" };
}
},
resetConsumerOffset: async (data) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/consumer/resetOffset.do"), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
return await response.json();
} catch (error) {
console.error("Error resetting consumer offset:", error);
return { status: 1, errMsg: "Failed to reset consumer offset" };
}
},
skipMessageAccumulate: async (data) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/consumer/skipAccumulate.do"), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
return await response.json();
} catch (error) {
console.error("Error skipping message accumulate:", error);
return { status: 1, errMsg: "Failed to skip message accumulate" };
}
},
sendTopicMessage: async (messageData) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/topic/sendTopicMessage.do"), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(messageData)
});
return await response.json();
} catch (error) {
console.error("Error sending topic message:", error);
return { status: 1, errMsg: "Failed to send topic message" };
}
},
deleteTopicByBroker: async (brokerName, topic) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/topic/deleteTopicByBroker.do"), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ brokerName, topic })
});
return await response.json();
} catch (error) {
console.error("Error deleting topic by broker:", error);
return { status: 1, errMsg: "Failed to delete topic by broker" };
}
},
// New API methods for Ops page
queryOpsHomePage: async () => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/ops/homePage.query"));
return await response.json();
} catch (error) {
console.error("Error fetching ops home page data:", error);
return { status: 1, errMsg: "Failed to fetch ops home page data" };
}
},
updateNameSvrAddr: async (nameSvrAddr) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/ops/updateNameSvrAddr.do?nameSvrAddrList=${encodeURIComponent(nameSvrAddr)}`), {
method: 'POST',
});
return await response.json();
} catch (error) {
console.error("Error updating NameServer address:", error);
return { status: 1, errMsg: "Failed to update NameServer address" };
}
},
addNameSvrAddr: async (newNamesrvAddr) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/ops/addNameSvrAddr.do?newNamesrvAddr=${encodeURIComponent(newNamesrvAddr)}`), {
method: 'POST',
});
return await response.json();
} catch (error) {
console.error("Error adding NameServer address:", error);
return { status: 1, errMsg: "Failed to add NameServer address" };
}
},
updateIsVIPChannel: async (useVIPChannel) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/ops/updateIsVIPChannel.do?useVIPChannel=${useVIPChannel}`), {
method: 'POST',
});
return await response.json();
} catch (error) {
console.error("Error updating VIP Channel status:", error);
return { status: 1, errMsg: "Failed to update VIP Channel status" };
}
},
updateUseTLS: async (useTLS) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl(`/ops/updateUseTLS.do?useTLS=${useTLS}`), {
method: 'POST',
});
return await response.json();
} catch (error) {
console.error("Error updating TLS status:", error);
return { status: 1, errMsg: "Failed to update TLS status" };
}
},
queryClusterList: async (callback) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/cluster/list.query"));
const data = await response.json();
callback(data);
} catch (error) {
console.error("Error fetching cluster list:", error);
callback({ status: 1, errMsg: "Failed to fetch cluster list" });
}
},
queryBrokerHisData: async (date, callback) => {
try {
const url = new URL(remoteApi.buildUrl('/dashboard/broker.query'));
url.searchParams.append('date', date);
const response = await remoteApi._fetch(url.toString(), { signal: AbortSignal.timeout(15000) }); // 15s timeout
const data = await response.json();
callback(data);
} catch (error) {
if (error.name === 'TimeoutError') {
console.error("Broker history data request timed out:", error);
callback({ status: 1, errMsg: "Request timed out for broker history data" });
} else {
console.error("Error fetching broker history data:", error);
callback({ status: 1, errMsg: "Failed to fetch broker history data" });
}
}
},
queryTopicHisData: async (date, topicName, callback) => {
try {
const url = new URL(remoteApi.buildUrl('/dashboard/topic.query'));
url.searchParams.append('date', date);
url.searchParams.append('topicName', topicName);
const response = await remoteApi._fetch(url.toString(), { signal: AbortSignal.timeout(15000) }); // 15s timeout
const data = await response.json();
callback(data);
} catch (error) {
if (error.name === 'TimeoutError') {
console.error("Topic history data request timed out:", error);
callback({ status: 1, errMsg: "Request timed out for topic history data" });
} else {
console.error("Error fetching topic history data:", error);
callback({ status: 1, errMsg: "Failed to fetch topic history data" });
}
}
},
queryTopicCurrentData: async (callback) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl('/dashboard/topicCurrent.query'), { signal: AbortSignal.timeout(15000) }); // 15s timeout
const data = await response.json();
callback(data);
} catch (error) {
if (error.name === 'TimeoutError') {
console.error("Topic current data request timed out:", error);
callback({ status: 1, errMsg: "Request timed out for topic current data" });
} else {
console.error("Error fetching topic current data:", error);
callback({ status: 1, errMsg: "Failed to fetch topic current data" });
}
}
},
queryBrokerConfig: async (brokerAddr, callback) => {
try {
const url = new URL(remoteApi.buildUrl('/cluster/brokerConfig.query'));
url.searchParams.append('brokerAddr', brokerAddr);
const response = await remoteApi._fetch(url.toString());
const data = await response.json();
callback(data);
} catch (error) {
console.error("Error fetching broker config:", error);
callback({ status: 1, errMsg: "Failed to fetch broker config" });
}
},
/**
* 查询 Proxy 首页信息,包括地址列表和当前 Proxy 地址
*/
queryProxyHomePage: async (callback) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/proxy/homePage.query"));
const data = await response.json();
callback(data);
} catch (error) {
console.error("Error fetching proxy home page:", error);
callback({ status: 1, errMsg: "Failed to fetch proxy home page" });
}
},
/**
* 添加新的 Proxy 地址
*/
addProxyAddr: async (newProxyAddr, callback) => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/proxy/addProxyAddr.do"), {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({ newProxyAddr }).toString()
});
const data = await response.json();
callback(data);
} catch (error) {
console.error("Error adding proxy address:", error);
callback({ status: 1, errMsg: "Failed to add proxy address" });
}
},
login: async (username, password) => {
try {
// 2. 发送请求,注意 body 可以是空字符串或 null或者直接省略 body
// 这里使用 GET 方法,因为参数在 URL 上
const response = await remoteApi._fetch(remoteApi.buildUrl("/login/login.do"), {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded' // 这个 header 可能不再需要,或者需要调整
},
body: new URLSearchParams({
username: username, // 假设 username 是变量名
password: password // 假设 password 是变量名
}).toString()
});
// 3. 处理响应
const data = await response.json();
return data;
} catch (error) {
console.error("Error logging in:", error);
return { status: 1, errMsg: "Failed to log in" };
}
},
logout: async () => {
try {
const response = await remoteApi._fetch(remoteApi.buildUrl("/login/logout.do"),{
method: 'POST'
});
return await response.json()
}catch (error) {
console.error("Error logging out:", error);
return { status: 1, errMsg: "Failed to log out" };
}
}
};
const tools = {
// 适配新的数据结构
dashboardRefreshTime: 5000,
generateBrokerMap: (brokerServer, clusterAddrTable, brokerAddrTable) => {
const clusterMap = {}; // 最终存储 { clusterName: [brokerInstance1, brokerInstance2, ...] }
Object.entries(clusterAddrTable).forEach(([clusterName, brokerNamesInCluster]) => {
clusterMap[clusterName] = []; // 初始化当前集群的 broker 列表
brokerNamesInCluster.forEach(brokerName => {
// 从 brokerAddrTable 获取当前 brokerName 下的所有 brokerId 及其地址
const brokerAddrs = brokerAddrTable[brokerName]?.brokerAddrs; // 确保 brokerAddrs 存在
if (brokerAddrs) {
Object.entries(brokerAddrs).forEach(([brokerIdStr, address]) => {
const brokerId = parseInt(brokerIdStr); // brokerId 是字符串,转为数字
// 从 brokerServer 获取当前 brokerName 和 brokerId 对应的详细信息
const detail = brokerServer[brokerName]?.[brokerIdStr];
if (detail) {
clusterMap[clusterName].push({
brokerName: brokerName,
brokerId: brokerId,
address: address,
...detail,
detail: detail,
brokerConfig: {},
});
} else {
console.warn(`No detail found for broker: ${brokerName} with ID: ${brokerIdStr}`);
}
});
} else {
console.warn(`No addresses found for brokerName: ${brokerName} in brokerAddrTable`);
}
});
});
return clusterMap;
}
};
export { remoteApi, tools };

View File

@@ -0,0 +1,209 @@
/*
* 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 } from 'react';
import { Layout, Menu, Dropdown, Button, Drawer, Grid, Space } from 'antd';
import {GlobalOutlined, DownOutlined, UserOutlined, MenuOutlined, BgColorsOutlined} from '@ant-design/icons';
import { useLocation, useNavigate } from 'react-router-dom';
import { useLanguage } from '../i18n/LanguageContext';
import {useTheme} from "../store/context/ThemeContext";
import {remoteApi} from "../api/remoteApi/remoteApi";
const { Header } = Layout;
const { useBreakpoint } = Grid; // Used to determine screen breakpoints
const Navbar = ({ username, rmqVersion = true, showAcl = true}) => {
const location = useLocation();
const navigate = useNavigate();
const { lang, setLang, t } = useLanguage();
const screens = useBreakpoint(); // Get current screen size breakpoints
const { currentThemeName, setCurrentThemeName } = useTheme();
const [drawerVisible, setDrawerVisible] = useState(false); // Controls drawer visibility
// Get selected menu item key based on current route path
const getPath = () => location.pathname.replace('/', '');
const handleMenuClick = ({ key }) => {
navigate(`/${key}`);
setDrawerVisible(false); // Close drawer after clicking a menu item
};
const onLogout = () => {
remoteApi.logout().then(res => {
if (res.status === 0) {
window.sessionStorage.removeItem("username");
window.sessionStorage.removeItem("userRole");
window.sessionStorage.removeItem("token");
window.sessionStorage.removeItem("rmqVersion");
navigate('/login');
} else {
console.error('Logout failed:', res.message)
navigate('/login');
}
})
};
const langMenu = (
<Menu onClick={({ key }) => setLang(key)}>
<Menu.Item key="en">{t.ENGLISH}</Menu.Item>
<Menu.Item key="zh">{t.CHINESE}</Menu.Item>
</Menu>
);
const versionMenu = (
<Menu onClick={({ key }) => alert(t.version.switchVersion + ': ' + key)}>
<Menu.Item key="5">RocketMQ v5</Menu.Item>
<Menu.Item key="4">RocketMQ v4</Menu.Item>
</Menu>
);
const userMenu = (
<Menu>
<Menu.Item key="logout" onClick={onLogout}>{t.LOGOUT}</Menu.Item>
</Menu>
);
const themeMenu = (
<Menu onClick={({ key }) => setCurrentThemeName(key)}>
<Menu.Item key="default">{t.BLUE} ({t.DEFAULT})</Menu.Item>
<Menu.Item key="pink">{t.PINK}</Menu.Item>
<Menu.Item key="green">{t.GREEN}</Menu.Item>
</Menu>
);
// Menu item configuration
const menuItems = [
{ key: 'ops', label: t.OPS },
...(rmqVersion ? [{ key: 'proxy', label: t.PROXY }] : []),
{ key: '', label: t.DASHBOARD }, // Dashboard corresponds to root path
{ key: 'cluster', label: t.CLUSTER },
{ key: 'topic', label: t.TOPIC },
{ key: 'consumer', label: t.CONSUMER },
{ key: 'producer', label: t.PRODUCER },
{ key: 'message', label: t.MESSAGE },
{ key: 'dlqMessage', label: t.DLQ_MESSAGE },
{ key: 'messageTrace', label: t.MESSAGETRACE },
...(showAcl ? [{ key: 'acl', label: t.WHITE_LIST }] : []),
];
// Determine if it's a small screen (e.g., less than md)
const isSmallScreen = !screens.md;
// Determine if it's an extra small screen (e.g., less than sm)
const isExtraSmallScreen = !screens.sm;
return (
<Header
className="navbar"
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: isExtraSmallScreen ? '0 16px' : '0 24px', // Smaller padding on extra small screens
}}
>
<div className="navbar-left" style={{ display: 'flex', alignItems: 'center' }}>
<div
style={{
fontWeight: 'bold',
marginRight: isSmallScreen ? '16px' : '24px', // Adjust margin on small screens
whiteSpace: 'nowrap', // Prevent text wrapping
flexShrink: 0, // Prevent shrinking in flex container
color: 'white', // Title text color also set to white
fontSize: isSmallScreen ? '14px' : '18px',
}}
>
{t.TITLE}
</div>
{!isSmallScreen && ( // Display full menu on large screens
<Menu
onClick={handleMenuClick}
selectedKeys={[getPath()]}
mode="horizontal"
items={menuItems}
theme="dark" // Use dark theme to match Header background
style={{ flex: 1, minWidth: 0 }} // Allow menu items to adapt width
/>
)}
</div>
<Space size={isExtraSmallScreen ? 8 : 16} > {/* Adjust spacing for buttons */}
{/* Theme switch button */}
<Dropdown overlay={themeMenu}>
<Button icon={<BgColorsOutlined />} size="small">
{!isExtraSmallScreen && `${t.TOPIC}: ${currentThemeName}`}
<DownOutlined />
</Button>
</Dropdown>
<Dropdown overlay={langMenu}>
<Button icon={<GlobalOutlined />} size="small">
{!isExtraSmallScreen && t.CHANGE_LANG} {/* Hide text on extra small screens */}
<DownOutlined />
</Button>
</Dropdown>
{username && (
<Dropdown overlay={userMenu}>
{/* 使用一个可点击的元素作为 Dropdown 的唯一子元素 */}
<a onClick={e => e.preventDefault()} style={{ display: 'flex', alignItems: 'center' }}>
<UserOutlined style={{ marginRight: 8 }} /> {/* 添加一些间距 */}
{username}
<DownOutlined style={{ marginLeft: 8 }} />
</a>
</Dropdown>
)}
{isSmallScreen && ( // Display hamburger icon on small screens
<Button
type="primary"
icon={<MenuOutlined />}
onClick={() => setDrawerVisible(true)}
style={{ marginLeft: isExtraSmallScreen ? 8 : 16 }} // Adjust margin for hamburger icon
/>
)}
</Space>
{/* Modify Drawer and Menu components here */}
<Drawer
// Default Drawer background color is white. If you need to change the Drawer's own background color, set it additionally
// or set a dark background in bodyStyle, then let Menu override it
title={t.MENU} // Drawer title
placement="left" // Drawer pops out from the left
onClose={() => setDrawerVisible(false)}
open={drawerVisible}
// If you want the Drawer's background to match the Menu's background color, you can set bodyStyle like this
// or set components.Drawer.colorBgElevated in theme.js, etc.
bodyStyle={{ padding: 0, backgroundColor: '#1c324a' }} // Set Drawer body background to dark
width={200} // Set drawer width
>
<Menu
onClick={handleMenuClick}
selectedKeys={[getPath()]}
mode="inline" // Use vertical menu in drawer
items={menuItems}
theme="dark"
style={{ height: '100%', borderRight: 0 }} // Ensure menu fills the drawer
/>
</Drawer>
</Header>
);
};
export default Navbar;

View File

@@ -14,36 +14,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, {useState, useEffect} from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
const [message, setMessage] = useState("");
useEffect(() => {
fetch('cluster/list.query')
.then(response => response.text())
.then(message => {
setMessage(message);
});
}, [])
import React, { createContext, useState, useContext } from 'react';
import { translations } from '../i18n';
const LanguageContext = createContext({
lang: 'en',
setLang: () => {},
t: translations['en'], // 当前语言的文本资源
});
export const LanguageProvider = ({ children }) => {
const [lang, setLang] = useState('en');
const t = translations[lang] || translations['en'];
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" height="60"/>
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
</header>
<h1>ClusterInfo</h1>
<p>
{message}
</p>
</div>
<LanguageContext.Provider value={{ lang, setLang, t }}>
{children}
</LanguageContext.Provider>
);
}
};
export default App;
export const useLanguage = () => useContext(LanguageContext);

View File

@@ -0,0 +1,540 @@
/*
* 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 const translations = {
zh: {
"SELECT_TRACE_TOPIC_PLACEHOLDER": "请选择消息轨迹主题",
"TRACE_TOPIC_HINT": "消息轨迹主题",
"ONLY_RETURN_64_MESSAGES": "仅返回64条消息",
"SELECT_TOPIC_PLACEHOLDER": "请选择主题",
"MESSAGE_ID_TOPIC_HINT": "消息ID主题",
"OPERATION_FAILED": "操作失败",
"FORM_VALIDATION_FAILED": "表单验证失败",
"CONFIG_FOR_BROKER": "配置代理",
"RETRY_POLICY": "重试策略",
"CONSUME_TIMEOUT": "消费超时",
"MINUTES": "分钟",
"SYSTEM_FLAG": "系统标志",
"GROUP_NAME": "组名称",
"PLEASE_SELECT_CLUSTER_NAME": "请选择集群名称",
"SELECT_CLUSTERS": "选择集群",
"SELECT_BROKERS": "选择代理",
"CONSUME_ENABLE": "启用消费",
"ORDERLY_CONSUMPTION": "有序消费",
"BROADCAST_CONSUMPTION": "广播消费",
"RETRY_QUEUES": "重试队列",
"MAX_RETRIES": "最大重试次数",
"BROKER_ID": "代理ID",
"SLOW_CONSUMPTION_BROKER": "慢消费代理",
"PLEASE_INPUT_NUMBER": "请输入数字",
"TOPIC_CONFIG": "主题配置",
"TOPIC_ADD": "添加主题",
"SELECT_CLUSTER_NAME": "请选择集群",
"FETCH_TOPIC_FAILED": "获取主题列表失败",
"CONFIRM_DELETE": "确认删除",
"CANCEL": "取消",
"SELECT_DELETE_BROKERS":"请选择在哪个Broker删除消费者组",
"DELETE_CONSUMER_GROUP":"删除消费者组",
"ENGLISH": "英文",
"ADD_CONSUMER":"添加消费者",
"CHINESE": "简体中文",
"CANNOT_BE_EMPTY": "不能为空",
"TITLE": "RocketMQ仪表板",
"CLOSE": "关闭",
"NO": "编号",
"ADDRESS": "地址",
"VERSION": "版本",
"PRO_MSG_TPS": "生产消息TPS",
"CUS_MSG_TPS": "消费消息TPS",
"YESTERDAY_PRO_COUNT": "昨日生产总数",
"YESTERDAY_CUS_COUNT": "昨日消费总数",
"TODAY_PRO_COUNT": "今天生产总数",
"TODAY_CUS_COUNT": "今天消费总数",
"INSTANCE": "实例",
"SPLIT": "分片",
"CLUSTER": "集群",
"CLUSTER_DETAIL": "集群详情",
"COMMIT": "提交",
"TOPIC": "主题",
"SUBSCRIPTION_GROUP":"订阅组",
"PRODUCER_GROUP":"生产组",
"CONSUMER":"消费者",
"PRODUCER":"生产者",
"MESSAGE":"消息",
"MESSAGE_DETAIL":"消息详情",
"RESEND_MESSAGE":"重新发送",
"VIEW_EXCEPTION":"查看异常",
"DLQ_MESSAGE":"死信消息",
"MESSAGETRACE":"消息轨迹",
"OPERATION": "操作",
"ADD": "新增",
"UPDATE": "更新",
"STATUS": "状态",
"ROUTER": "路由",
"MANAGE": "管理",
"CONFIG": "配置",
"SEND_MSG": "发送消息",
"RESET_CUS_OFFSET": "重置消费位点",
"SKIP_MESSAGE_ACCUMULATE":"跳过堆积",
"DELETE": "删除",
"CHANGE_LANG": "更换语言",
"CHANGE_VERSION": "更换版本",
"BROKER": "Broker",
"NORMAL": "普通",
"RETRY": "重试",
"FIFO": "顺序",
"TRANSACTION": "事务",
"UNSPECIFIED": "未指定",
"DLQ": "死信",
"QUANTITY":"数量",
"TYPE":"类型",
"MODE":"模式",
"DELAY":"延迟",
"DASHBOARD":"驾驶舱",
"CONSUME_DETAIL":"消费详情",
"CLIENT":"终端",
"LAST_CONSUME_TIME":"最后消费时间",
"TIME":"时间点",
"RESET":"重置",
"DATE":"日期",
"NO_DATA":"暂无数据",
"SEARCH":"搜索",
"BEGIN":"开始",
"END":"结束",
"TOPIC_CHANGE":"修改主题",
"SEND":"发送",
"SUBSCRIPTION_CHANGE":"修改订阅",
"QUEUE":"队列",
"MIN_OFFSET":"最小位点",
"MAX_OFFSET":"最大位点",
"LAST_UPDATE_TIME_STAMP":"上次更新时间",
"QUEUE_DATAS":"队列信息",
"READ_QUEUE_NUMS":"读队列数量",
"WRITE_QUEUE_NUMS":"写队列数量",
"PERM":"perm",
"TAG":"标签",
"KEY":"值",
"MESSAGE_BODY":"消息主体",
"TOPIC_NAME":"主题名",
"ORDER":"顺序",
"CONSUMER_CLIENT":"消费者终端",
"BROKER_OFFSET":"代理者位点",
"CONSUMER_OFFSET":"消费者位点",
"DIFF_TOTAL":"差值",
"LAST_TIME_STAMP":"上次时间",
"RESET_OFFSET":"重置位点",
"CLUSTER_NAME":"集群名",
"OPS":"运维",
"PROXY":"代理",
"AUTO_REFRESH":"自动刷新",
"REFRESH":"刷新",
"LOGOUT":"退出",
"LOGIN":"登录",
"USER_NAME":"用户名",
"PASSWORD":"密码",
"SYSTEM":"系统",
"WELCOME":"您好欢迎使用RocketMQ仪表盘",
"ENABLE_MESSAGE_TRACE":"开启消息轨迹",
"MESSAGE_TRACE_DETAIL":"消息轨迹详情",
"TRACE_TOPIC":"消息轨迹主题",
"SELECT_TRACE_TOPIC":"选择消息轨迹主题",
"EXPORT": "导出",
"NO_MATCH_RESULT": "没有查到符合条件的结果",
"BATCH_RESEND": "批量重发",
"BATCH_EXPORT": "批量导出",
"WHITE_LIST":"白名单",
"ACCOUNT_INFO":"账户信息",
"IS_ADMIN":"是否管理员",
"DEFAULT_TOPIC_PERM":"topic默认权限",
"DEFAULT_GROUP_PERM":"消费组默认权限",
"TOPIC_PERM":"topic权限",
"GROUP_PERM":"消费组权限",
"SYNCHRONIZE":"同步",
"SHOW":"显示",
"HIDE":"隐藏",
"MESSAGE_TYPE":"消息类型",
"MESSAGE_TYPE_UNSPECIFIED": "未指定,为普通消息",
"MESSAGE_TYPE_NORMAL": "普通消息",
"MESSAGE_TYPE_FIFO": "顺序消息",
"MESSAGE_TYPE_DELAY": "定时/延时消息",
"MESSAGE_TYPE_TRANSACTION": "事务消息",
"UPDATE_TIME": "更新时间",
"TREND": "趋势",
"PROXY_CONFIG": "代理配置",
"READ_MORE": "阅读更多",
"FETCH_PROXY_LIST_FAILED": "获取代理列表失败",
"INPUT_PROXY_ADDR_REQUIRED": "请输入代理地址",
"SUCCESS": "成功",
"ADD_PROXY_FAILED": "添加代理失败",
"INPUT_PROXY_ADDR": "输入代理地址",
"NO_CONFIG_DATA": "无配置数据",
"FETCH_MESSAGE_DETAIL_FAILED": "获取消息详情失败",
"MESSAGE_INFO": "消息信息",
"MESSAGE_PROPERTIES": "消息属性",
"SHOW_ALL_CONTENT": "显示全部内容",
"MESSAGE_TRACKING": "消息追踪",
"CONSUMER_GROUP": "消费者组",
"PLEASE_SELECT_BROKER": "请选择Broker",
"DELETE_SUCCESS": "删除成功",
"FAILED_TO_FETCH_DATA": "获取数据失败",
"REFRESH_SUCCESS": "刷新成功",
"REFRESH_FAILED": "刷新失败",
"REFRESHED": "已刷新",
"QUERY_BROKER_HISTORY_FAILED": "查询Broker历史失败",
"QUERY_TOPIC_HISTORY_FAILED": "查询Topic历史失败",
"QUERY_CLUSTER_LIST_FAILED": "查询集群列表失败",
"QUERY_TOPIC_CURRENT_FAILED": "查询当前Topic失败",
"BROKER_NAME": "Broker名称",
"BROKER_ADDR": "Broker地址",
"WARNING": "警告",
"PLEASE_SELECT_CONSUMER_GROUP": "请选择消费者组",
"END_TIME_LATER_THAN_BEGIN_TIME": "结束时间应晚于开始时间",
"NO_RESULT": "无结果",
"QUERY_FAILED": "查询失败",
"MESSAGE_ID_AND_CONSUMER_GROUP_REQUIRED": "消息ID和消费者组为必填项",
"RESEND_SUCCESS": "重新发送成功",
"RESULT": "结果",
"TOPIC_AND_KEY_REQUIRED": "Topic和Key为必填项",
"MESSAGE_ID_REQUIRED": "消息ID为必填项",
"REFRESHING_TOPIC_LIST": "正在刷新Topic列表",
"TOPIC_OPERATION_SUCCESS": "Topic操作成功",
"ARE_YOU_SURE_TO_DELETE": "您确定要删除吗?",
"YES": "是",
"NOT": "否",
"BLUE": "蓝色",
"GREEN": "绿色",
"PINK": "粉色",
"DEFAULT": "默认",
"INVALID_IP_ADDRESSES": "以下IP地址不合法: ",
"ENABLED": "启用",
"DISABLED": "禁用",
"GET_USERS_FAILED": "获取用户列表失败: ",
"UNKNOWN_ERROR": "未知错误",
"GET_USERS_EXCEPTION": "获取用户列表异常",
"N_A": "N/A",
"INVALID_OR_EMPTY_ACL_DATA": "收到无效或空的ACL数据。",
"GET_ACLS_FAILED": "获取ACL列表失败: ",
"GET_ACLS_EXCEPTION": "获取ACL列表异常",
"USER_DELETE_SUCCESS": "用户删除成功",
"USER_DELETE_FAILED": "用户删除失败: ",
"USER_DELETE_EXCEPTION": "用户删除异常",
"USER_UPDATE_SUCCESS": "用户更新成功",
"USER_CREATE_SUCCESS": "用户创建成功",
"SAVE_USER_FAILED": "保存用户失败",
"ACL_DELETE_SUCCESS": "ACL 删除成功",
"ACL_DELETE_FAILED": "ACL 删除失败: ",
"ACL_DELETE_EXCEPTION": "ACL 删除异常",
"ACL_UPDATE_SUCCESS": "ACL 更新成功",
"ACL_UPDATE_FAILED": "ACL 更新失败: ",
"ACL_CREATE_SUCCESS": "ACL 创建成功",
"ACL_CREATE_FAILED": "ACL 创建失败: ",
"SAVE_ACL_FAILED": "保存 ACL 失败",
"USERNAME": "用户名",
"VIEW": "查看",
"USER_TYPE": "用户类型",
"USER_STATUS": "用户状态",
"MODIFY": "修改",
"CONFIRM_DELETE_USER": "确定删除此用户吗?",
"USERNAME_SUBJECT": "用户名/Subject",
"POLICY_TYPE": "策略类型",
"RESOURCE_NAME": "资源名",
"OPERATION_TYPE": "操作类型",
"SOURCE_IP": "来源IP",
"DECISION": "决策",
"CONFIRM_DELETE_ACL": "确定删除此ACL吗",
"ACL_MANAGEMENT": "ACL 管理",
"ACL_USERS": "ACL 用户",
"ACL_PERMISSIONS": "ACL 权限",
"ADD_USER": "添加用户",
"ADD_ACL_PERMISSION": "添加 ACL 权限",
"SEARCH_PLACEHOLDER": "搜索...",
"USER": "用户",
"ACL_PERMISSION": "ACL 权限",
"EDIT_USER": "编辑用户",
"CONFIRM": "确认",
"PLEASE_ENTER_USERNAME": "请输入用户名!",
"PLEASE_ENTER_PASSWORD": "请输入密码!",
"PLEASE_SELECT_USER_TYPE": "请选择用户类型!",
"PLEASE_SELECT_USER_STATUS": "请选择用户状态!",
"EDIT_ACL_PERMISSION": "编辑 ACL 权限",
"SUBJECT_LABEL": "Subject (例如: User:yourUsername)",
"PLEASE_ENTER_SUBJECT": "请输入 Subject!",
"PLEASE_ENTER_POLICY_TYPE": "请输入策略类型!",
"RESOURCE": "资源",
"PLEASE_ADD_RESOURCE": "请添加资源!",
"ENTER_IP_HINT": "请输入 IP 地址,按回车键添加,支持 IPv4、IPv6 和 CIDR",
"PLEASE_ENTER_DECISION": "请输入决策!",
},
en: {
"DEFAULT": "Default",
"BLUE": "Blue",
"GREEN": "Green",
"PINK": "Pink",
"NOT": "No",
"ARE_YOU_SURE_TO_DELETE": "Are you sure to delete?",
"YES": "Yes",
"SELECT_TRACE_TOPIC_PLACEHOLDER": "Please select trace topic",
"TRACE_TOPIC_HINT": "Trace Topic",
"ONLY_RETURN_64_MESSAGES": "Only return 64 messages",
"SELECT_TOPIC_PLACEHOLDER": "Please select topic",
"MESSAGE_ID_TOPIC_HINT": "Message ID Topic",
"TOPIC_ADD": "Add Topic",
"SKIP_MESSAGE_ACCUMULATE":"Skip Message Accumulate",
"OPERATION_FAILED": "Operation Failed",
"FORM_VALIDATION_FAILED": "Form Validation Failed",
"ADD_CONSUMER": "Add Consumer",
"CONFIG_FOR_BROKER": "Config for Broker",
"RETRY_POLICY": "Retry Policy",
"CONSUME_TIMEOUT": "Consume Timeout",
"MINUTES": "Minutes",
"SYSTEM_FLAG": "System Flag",
"GROUP_NAME": "Group Name",
"CANNOT_BE_EMPTY": "Cannot be empty",
"PLEASE_SELECT_CLUSTER_NAME": "Please select cluster name",
"SELECT_CLUSTERS": "Select Clusters",
"SELECT_BROKERS": "Select Brokers",
"CONSUME_ENABLE": "Consume Enable",
"ORDERLY_CONSUMPTION": "Orderly Consumption",
"BROADCAST_CONSUMPTION": "Broadcast Consumption",
"RETRY_QUEUES": "Retry Queues",
"MAX_RETRIES": "Max Retries",
"BROKER_ID": "Broker ID",
"SLOW_CONSUMPTION_BROKER": "Slow Consumption Broker",
"PLEASE_INPUT_NUMBER": "Please input number",
"FETCH_TOPIC_FAILED": "Failed to fetch topic list",
"ENGLISH": "English",
"CHINESE": "Chinese",
"TITLE": "RocketMQ-Dashboard",
"CLOSE": "Close",
"NO": "NO.",
"ADDRESS": "Address",
"VERSION": "Version",
"PRO_MSG_TPS": "Produce Message TPS",
"CUS_MSG_TPS": "Consumer Message TPS",
"YESTERDAY_PRO_COUNT": "Yesterday Produce Count",
"YESTERDAY_CUS_COUNT": "Yesterday Consume Count",
"TODAY_PRO_COUNT": "Today Produce Count",
"TODAY_CUS_COUNT": "Today Consume Count",
"INSTANCE": "Instance",
"SPLIT": "Broker",
"CLUSTER": "Cluster",
"CLUSTER_DETAIL": "Cluster Detail",
"TOPIC": "Topic",
"SUBSCRIPTION_GROUP":"SubscriptionGroup",
"PRODUCER_GROUP":"ProducerGroup",
"CONSUMER":"Consumer",
"PRODUCER":"Producer",
"MESSAGE":"Message",
"MESSAGE_DETAIL":"Message Detail",
"RESEND_MESSAGE":"Resend Message",
"VIEW_EXCEPTION":"View Exception",
"MESSAGETRACE":"MessageTrace",
"DLQ_MESSAGE":"DLQMessage",
"COMMIT": "Commit",
"OPERATION": "Operation",
"ADD": "Add",
"UPDATE": "Update",
"STATUS": "Status",
"ROUTER": "Router",
"MANAGE": "Manage",
"CONFIG": "Config",
"SEND_MSG": "Send Massage",
"RESET_CUS_OFFSET": "Reset Consumer Offset",
"DELETE": "Delete",
"CHANGE_LANG": "ChangeLanguage",
"CHANGE_VERSION": "ChangeVersion",
"BROKER": "Broker",
"NORMAL": "NORMAL",
"RETRY": "RETRY",
"FIFO": "FIFO",
"TRANSACTION": "TRANSACTION",
"UNSPECIFIED": "UNSPECIFIED",
"DLQ": "DLQ",
"QUANTITY":"Quantity",
"TYPE":"Type",
"MODE":"Mode",
"DELAY":"Delay",
"DASHBOARD":"Dashboard",
"CONSUME_DETAIL":"CONSUME DETAIL",
"CLIENT":"CLIENT",
"LAST_CONSUME_TIME":"LastConsumeTime",
"TIME":"Time",
"RESET":"RESET",
"DATE":"Date",
"NO_DATA":"NO DATA",
"SEARCH":"Search",
"BEGIN":"Begin",
"END":"End",
"TOPIC_CHANGE":"Topic Change",
"SEND":"Send",
"SUBSCRIPTION_CHANGE":"Subscription Change",
"QUEUE":"Queue",
"MIN_OFFSET":"minOffset",
"MAX_OFFSET":"maxOffset",
"LAST_UPDATE_TIME_STAMP":"lastUpdateTimeStamp",
"QUEUE_DATAS":"queueDatas",
"READ_QUEUE_NUMS":"readQueueNums",
"WRITE_QUEUE_NUMS":"writeQueueNums",
"PERM":"perm",
"TAG":"Tag",
"KEY":"Key",
"MESSAGE_BODY":"Message Body",
"TOPIC_NAME":"topicName",
"ORDER":"order",
"CONSUMER_CLIENT":"consumerClient",
"BROKER_OFFSET":"brokerOffset",
"CONSUMER_OFFSET":"consumerOffset",
"DIFF_TOTAL":"diffTotal",
"LAST_TIME_STAMP":"lastTimeStamp",
"RESET_OFFSET":"resetOffset",
"CLUSTER_NAME":"clusterName",
"OPS":"OPS",
"PROXY":"Proxy",
"AUTO_REFRESH":"AUTO_REFRESH",
"REFRESH":"REFRESH",
"LOGOUT":"Logout",
"LOGIN":"Login",
"USER_NAME":"Username",
"PASSWORD":"Password",
"SYSTEM":"SYSTEM",
"WELCOME":"Hi, welcome using RocketMQ Dashboard",
"ENABLE_MESSAGE_TRACE":"Enable Message Trace",
"MESSAGE_TRACE_DETAIL":"Message Trace Detail",
"TRACE_TOPIC":"TraceTopic",
"SELECT_TRACE_TOPIC":"selectTraceTopic",
"EXPORT": "export",
"NO_MATCH_RESULT": "no match result",
"BATCH_RESEND": "batchReSend",
"BATCH_EXPORT": "batchExport",
"WHITE_LIST":"White List",
"ACCOUNT_INFO":"Account Info",
"IS_ADMIN":"Is Admin",
"DEFAULT_TOPIC_PERM":"Default Topic Permission",
"DEFAULT_GROUP_PERM":"Default Group Permission",
"TOPIC_PERM":"Topic Permission",
"GROUP_PERM":"Group Permission",
"SYNCHRONIZE":"Synchronize Data",
"SHOW":"Show",
"HIDE":"Hide",
"MESSAGE_TYPE":"messageType",
"MESSAGE_TYPE_UNSPECIFIED": "UNSPECIFIED, is NORMAL",
"MESSAGE_TYPE_NORMAL": "NORMAL",
"MESSAGE_TYPE_FIFO": "FIFO",
"MESSAGE_TYPE_DELAY": "DELAY",
"MESSAGE_TYPE_TRANSACTION": "TRANSACTION",
"UPDATE_TIME": "Update Time",
"TREND": "trend",
"FETCH_PROXY_LIST_FAILED": "Failed to fetch proxy list",
"INPUT_PROXY_ADDR_REQUIRED": "Input proxy address required",
"SUCCESS": "Success",
"ADD_PROXY_FAILED": "Failed to add proxy",
"INPUT_PROXY_ADDR": "Input proxy address",
"NO_CONFIG_DATA": "No configuration data",
"FETCH_MESSAGE_DETAIL_FAILED": "Failed to fetch message details",
"MESSAGE_INFO": "Message info",
"MESSAGE_PROPERTIES": "Message properties",
"SHOW_ALL_CONTENT": "Show all content",
"MESSAGE_TRACKING": "Message tracking",
"CONSUMER_GROUP": "Consumer group",
"PLEASE_SELECT_BROKER": "Please select a broker",
"DELETE_SUCCESS": "Delete successful",
"FAILED_TO_FETCH_DATA": "Failed to fetch data",
"REFRESH_SUCCESS": "Refresh successful",
"REFRESH_FAILED": "Refresh failed",
"REFRESHED": "Refreshed",
"QUERY_BROKER_HISTORY_FAILED": "Failed to query broker history",
"QUERY_TOPIC_HISTORY_FAILED": "Failed to query topic history",
"QUERY_CLUSTER_LIST_FAILED": "Failed to query cluster list",
"QUERY_TOPIC_CURRENT_FAILED": "Failed to query current topic",
"BROKER_NAME": "Broker name",
"BROKER_ADDR": "Broker address",
"WARNING": "Warning",
"PLEASE_SELECT_CONSUMER_GROUP": "Please select a consumer group",
"END_TIME_LATER_THAN_BEGIN_TIME": "End time should be later than begin time",
"NO_RESULT": "No result",
"QUERY_FAILED": "Query failed",
"MESSAGE_ID_AND_CONSUMER_GROUP_REQUIRED": "Message ID and consumer group required",
"RESEND_SUCCESS": "Resend successful",
"RESULT": "Result",
"TOPIC_AND_KEY_REQUIRED": "Topic and key required",
"MESSAGE_ID_REQUIRED": "Message ID required",
"REFRESHING_TOPIC_LIST": "Refreshing topic list",
"TOPIC_OPERATION_SUCCESS": "Topic operation successful",
"INVALID_IP_ADDRESSES": "The following IP addresses are invalid: ",
"ENABLED": "Enabled",
"DISABLED": "Disabled",
"GET_USERS_FAILED": "Failed to get user list: ",
"UNKNOWN_ERROR": "Unknown error",
"GET_USERS_EXCEPTION": "Exception getting user list",
"N_A": "N/A",
"INVALID_OR_EMPTY_ACL_DATA": "Received invalid or empty ACL data.",
"GET_ACLS_FAILED": "Failed to get ACL list: ",
"GET_ACLS_EXCEPTION": "Exception getting ACL list",
"USER_DELETE_SUCCESS": "User deleted successfully",
"USER_DELETE_FAILED": "User deletion failed: ",
"USER_DELETE_EXCEPTION": "User deletion exception",
"USER_UPDATE_SUCCESS": "User updated successfully",
"USER_CREATE_SUCCESS": "User created successfully",
"SAVE_USER_FAILED": "Failed to save user",
"ACL_DELETE_SUCCESS": "ACL deleted successfully",
"ACL_DELETE_FAILED": "ACL deletion failed: ",
"ACL_DELETE_EXCEPTION": "ACL deletion exception",
"ACL_UPDATE_SUCCESS": "ACL updated successfully",
"ACL_UPDATE_FAILED": "ACL update failed: ",
"ACL_CREATE_SUCCESS": "ACL created successfully",
"ACL_CREATE_FAILED": "ACL creation failed: ",
"SAVE_ACL_FAILED": "Failed to save ACL",
"USERNAME": "Username",
"VIEW": "View",
"USER_TYPE": "User Type",
"USER_STATUS": "User Status",
"MODIFY": "Modify",
"CONFIRM_DELETE_USER": "Are you sure you want to delete this user?",
"USERNAME_SUBJECT": "Username/Subject",
"POLICY_TYPE": "Policy Type",
"RESOURCE_NAME": "Resource Name",
"OPERATION_TYPE": "Operation Type",
"SOURCE_IP": "Source IP",
"DECISION": "Decision",
"CONFIRM_DELETE_ACL": "Are you sure you want to delete this ACL?",
"ACL_MANAGEMENT": "ACL Management",
"ACL_USERS": "ACL Users",
"ACL_PERMISSIONS": "ACL Permissions",
"ADD_USER": "Add User",
"ADD_ACL_PERMISSION": "Add ACL Permission",
"SEARCH_PLACEHOLDER": "Search ...",
"USER": "User",
"ACL_PERMISSION": "ACL Permission",
"EDIT_USER": "Edit User",
"CANCEL": "Cancel",
"CONFIRM": "Confirm",
"PLEASE_ENTER_USERNAME": "Please enter username!",
"PLEASE_ENTER_PASSWORD": "Please enter password!",
"PLEASE_SELECT_USER_TYPE": "Please select user type!",
"PLEASE_SELECT_USER_STATUS": "Please select user status!",
"EDIT_ACL_PERMISSION": "Edit ACL Permission",
"SUBJECT_LABEL": "Subject (e.g.: User:yourUsername)",
"PLEASE_ENTER_SUBJECT": "Please enter Subject!",
"PLEASE_ENTER_POLICY_TYPE": "Please enter policy type!",
"RESOURCE": "Resource",
"PLEASE_ADD_RESOURCE": "Please add resource!",
"ENTER_IP_HINT": "Please enter IP address, press Enter to add. Supports IPv4, IPv6, and CIDR.",
"PLEASE_ENTER_DECISION": "Please enter decision!",
}
};

View File

@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',

View File

@@ -14,17 +14,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { App as AntdApp } from 'antd';
import reportWebVitals from './reportWebVitals';
import {LanguageProvider} from "./i18n/LanguageContext";
import {Provider} from "react-redux";
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<LanguageProvider>
<React.StrictMode>
<AntdApp>
<Provider store={store}>
<App/>
</Provider>
</AntdApp>
</React.StrictMode>
</LanguageProvider>
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function

View File

@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {

View File

@@ -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.
*/
import React, {useEffect} from 'react';
import {HashRouter as Router, Navigate, Route, Routes, useLocation, useNavigate} from 'react-router-dom';
import {Layout} from 'antd';
import {AnimatePresence, motion} from 'framer-motion';
import Login from '../pages/Login/login';
import Ops from '../pages/Ops/ops';
import Proxy from '../pages/Proxy/proxy';
import Cluster from '../pages/Cluster/cluster';
import Topic from '../pages/Topic/topic';
import Consumer from '../pages/Consumer/consumer';
import Producer from '../pages/Producer/producer';
import Message from '../pages/Message/message';
import DlqMessage from '../pages/DlqMessage/dlqmessage';
import MessageTrace from '../pages/MessageTrace/messagetrace';
import Acl from '../pages/Acl/acl';
import Navbar from '../components/Navbar';
import DashboardPage from "../pages/Dashboard/DashboardPage";
import {remoteApi} from "../api/remoteApi/remoteApi";
const {Header, Content} = Layout;
const pageVariants = {
initial: {
opacity: 0,
x: "-100vw"
},
in: {
opacity: 1,
x: 0
},
out: {
opacity: 0,
x: "100vw"
}
};
const pageTransition = {
type: "tween",
ease: "anticipate",
duration: 0.2
};
const AppRouter = () => {
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
remoteApi.setRedirectHandler(() => {
navigate('/login', { replace: true });
});
}, [navigate]);
return (
<Layout style={{minHeight: '100vh'}}>
<Header style={{padding: 0, height: 'auto', lineHeight: 'normal'}}>
<Navbar username={window.sessionStorage.getItem("username")}/>
</Header>
<Content style={{padding: '24px'}}>
<AnimatePresence mode="wait">
<Routes location={location} key={location.pathname}>
<Route
path="/login"
element={
<motion.div
variants={pageVariants}
initial="initial"
animate="in"
exit="out"
transition={pageTransition}
>
<Login/>
</motion.div>
}
/>
<Route
path="/"
element={
<motion.div
variants={pageVariants}
initial="initial"
animate="in"
exit="out"
transition={pageTransition}
>
<DashboardPage/>
</motion.div>
}
/>
<Route
path="/ops"
element={
<motion.div
variants={pageVariants}
initial="initial"
animate="in"
exit="out"
transition={pageTransition}
>
<Ops/>
</motion.div>
}
/>
<Route
path="/proxy"
element={
<motion.div
variants={pageVariants}
initial="initial"
animate="in"
exit="out"
transition={pageTransition}
>
<Proxy/>
</motion.div>
}
/>
<Route
path="/cluster"
element={
<motion.div
variants={pageVariants}
initial="initial"
animate="in"
exit="out"
transition={pageTransition}
>
<Cluster/>
</motion.div>
}
/>
<Route
path="/topic"
element={
<motion.div
variants={pageVariants}
initial="initial"
animate="in"
exit="out"
transition={pageTransition}
>
<Topic/>
</motion.div>
}
/>
<Route
path="/consumer"
element={
<motion.div
variants={pageVariants}
initial="initial"
animate="in"
exit="out"
transition={pageTransition}
>
<Consumer/>
</motion.div>
}
/>
<Route
path="/producer"
element={
<motion.div
variants={pageVariants}
initial="initial"
animate="in"
exit="out"
transition={pageTransition}
>
<Producer/>
</motion.div>
}
/>
<Route
path="/message"
element={
<motion.div
variants={pageVariants}
initial="initial"
animate="in"
exit="out"
transition={pageTransition}
>
<Message/>
</motion.div>
}
/>
<Route
path="/dlqMessage"
element={
<motion.div
variants={pageVariants}
initial="initial"
animate="in"
exit="out"
transition={pageTransition}
>
<DlqMessage/>
</motion.div>
}
/>
<Route
path="/messageTrace"
element={
<motion.div
variants={pageVariants}
initial="initial"
animate="in"
exit="out"
transition={pageTransition}
>
<MessageTrace/>
</motion.div>
}
/>
<Route
path="/acl"
element={
<motion.div
variants={pageVariants}
initial="initial"
animate="in"
exit="out"
transition={pageTransition}
>
<Acl/>
</motion.div>
}
/>
<Route path="*" element={<Navigate to="/"/>}/>
</Routes>
</AnimatePresence>
</Content>
</Layout>
);
};
const AppWrapper = () => (
<Router>
<AppRouter/>
</Router>
);
export default AppWrapper;

View File

@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)

16464
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +0,0 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-json-view": "^1.21.3",
"react-scripts": "4.0.3",
"web-vitals": "^1.0.1"
},
"proxy": "http://localhost:8080",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,59 +0,0 @@
<!--
~ 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.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -1,17 +0,0 @@
<!--
~ 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.
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -418,7 +418,7 @@
<artifactId>frontend-maven-plugin</artifactId>
<version>1.11.3</version>
<configuration>
<workingDirectory>frontend</workingDirectory>
<workingDirectory>frontend-new</workingDirectory>
<installDirectory>target</installDirectory>
</configuration>
<executions>