mirror of
https://github.com/apache/rocketmq-dashboard.git
synced 2025-09-10 11:40:01 +08:00
@@ -6,19 +6,14 @@ This project was bootstrapped with [Create React App](https://github.com/faceboo
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
### `npm run start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||
Open [http://localhost:3003](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.\
|
||||
|
@@ -18,13 +18,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="shortcut icon" href="./favicon.ico" type="image/x-icon" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="" />
|
||||
<meta name="keywords" content="" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>RocketMQ Dashboard</title>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="shortcut icon" href="./favicon.ico" type="image/x-icon"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta name="description" content=""/>
|
||||
<meta name="keywords" content=""/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<title>RocketMQ Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
@@ -17,19 +17,19 @@
|
||||
|
||||
import React from 'react';
|
||||
import AppRouter from './router'; // 你 router/index.jsx 导出的组件
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
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();
|
||||
const {currentTheme} = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfigProvider theme={currentTheme}>
|
||||
<ToastContainer />
|
||||
<AppRouter />
|
||||
<ToastContainer/>
|
||||
<AppRouter/>
|
||||
</ConfigProvider>
|
||||
</>
|
||||
);
|
||||
|
@@ -15,11 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import {render, screen} from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
render(<App/>);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
|
@@ -16,20 +16,20 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Form, Input, Typography, Modal } from 'antd';
|
||||
import {Form, Input, Typography} from 'antd';
|
||||
import moment from 'moment';
|
||||
import { useLanguage } from '../i18n/LanguageContext'; // 根据实际路径调整
|
||||
import {useLanguage} from '../i18n/LanguageContext'; // 根据实际路径调整
|
||||
|
||||
const { Text } = Typography;
|
||||
const {Text} = Typography;
|
||||
|
||||
const DlqMessageDetailViewDialog = ({ ngDialogData }) => {
|
||||
const { t } = useLanguage();
|
||||
const DlqMessageDetailViewDialog = ({ngDialogData}) => {
|
||||
const {t} = useLanguage();
|
||||
|
||||
const messageView = ngDialogData?.messageView || {};
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<Form layout="horizontal" labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}>
|
||||
<div style={{padding: '20px'}}>
|
||||
<Form layout="horizontal" labelCol={{span: 4}} wrapperCol={{span: 20}}>
|
||||
<Form.Item label="Message ID:">
|
||||
<Text strong>{messageView.msgId}</Text>
|
||||
</Form.Item>
|
||||
@@ -39,7 +39,7 @@ const DlqMessageDetailViewDialog = ({ ngDialogData }) => {
|
||||
<Form.Item label="Properties:">
|
||||
<Input.TextArea
|
||||
value={typeof messageView.properties === 'object' ? JSON.stringify(messageView.properties, null, 2) : messageView.properties}
|
||||
style={{ minHeight: 100, resize: 'none' }}
|
||||
style={{minHeight: 100, resize: 'none'}}
|
||||
readOnly
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -61,7 +61,7 @@ const DlqMessageDetailViewDialog = ({ ngDialogData }) => {
|
||||
<Form.Item label="Message body:">
|
||||
<Input.TextArea
|
||||
value={messageView.messageBody}
|
||||
style={{ minHeight: 100, resize: 'none' }}
|
||||
style={{minHeight: 100, resize: 'none'}}
|
||||
readOnly
|
||||
/>
|
||||
</Form.Item>
|
||||
|
@@ -16,16 +16,16 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Modal, Button, Typography, Descriptions, Tag, Spin, notification } from 'antd';
|
||||
import {Button, Descriptions, Modal, notification, Spin, Tag, Typography} from 'antd';
|
||||
import moment from 'moment';
|
||||
import { ExclamationCircleOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { useLanguage } from '../i18n/LanguageContext';
|
||||
import { remoteApi } from '../api/remoteApi/remoteApi'; // 确保这个路径正确
|
||||
import {SyncOutlined} from '@ant-design/icons';
|
||||
import {useLanguage} from '../i18n/LanguageContext';
|
||||
import {remoteApi} from '../api/remoteApi/remoteApi'; // 确保这个路径正确
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
const {Text, Paragraph} = Typography;
|
||||
|
||||
const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResendMessage }) => {
|
||||
const { t } = useLanguage();
|
||||
const MessageDetailViewDialog = ({visible, onCancel, messageId, topic, onResendMessage}) => {
|
||||
const {t} = useLanguage();
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [messageDetail, setMessageDetail] = React.useState(null);
|
||||
const [error, setError] = React.useState(null);
|
||||
@@ -89,17 +89,21 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
|
||||
>
|
||||
<Spin spinning={loading} tip={t.LOADING}>
|
||||
{error && (
|
||||
<Paragraph type="danger" style={{ textAlign: 'center' }}>
|
||||
<Paragraph type="danger" style={{textAlign: 'center'}}>
|
||||
{error}
|
||||
</Paragraph>
|
||||
)}
|
||||
{messageDetail ? ( // 确保 messageDetail 存在时才渲染内容
|
||||
<>
|
||||
{/* 消息信息部分 */}
|
||||
<Descriptions title={<Text strong>{t.MESSAGE_INFO}</Text>} bordered column={2} size="small" style={{ marginBottom: 20 }}>
|
||||
<Descriptions.Item label="Topic" span={2}><Text copyable>{messageDetail.messageView.topic}</Text></Descriptions.Item>
|
||||
<Descriptions.Item label="Message ID" span={2}><Text copyable>{messageDetail.messageView.msgId}</Text></Descriptions.Item>
|
||||
<Descriptions.Item label="StoreHost">{messageDetail.messageView.storeHost}</Descriptions.Item>
|
||||
<Descriptions title={<Text strong>{t.MESSAGE_INFO}</Text>} bordered column={2} size="small"
|
||||
style={{marginBottom: 20}}>
|
||||
<Descriptions.Item label="Topic" span={2}><Text
|
||||
copyable>{messageDetail.messageView.topic}</Text></Descriptions.Item>
|
||||
<Descriptions.Item label="Message ID" span={2}><Text
|
||||
copyable>{messageDetail.messageView.msgId}</Text></Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
label="StoreHost">{messageDetail.messageView.storeHost}</Descriptions.Item>
|
||||
<Descriptions.Item label="BornHost">{messageDetail.messageView.bornHost}</Descriptions.Item>
|
||||
<Descriptions.Item label="StoreTime">
|
||||
{moment(messageDetail.messageView.storeTimestamp).format("YYYY-MM-DD HH:mm:ss")}
|
||||
@@ -108,26 +112,33 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
|
||||
{moment(messageDetail.messageView.bornTimestamp).format("YYYY-MM-DD HH:mm:ss")}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Queue ID">{messageDetail.messageView.queueId}</Descriptions.Item>
|
||||
<Descriptions.Item label="Queue Offset">{messageDetail.messageView.queueOffset}</Descriptions.Item>
|
||||
<Descriptions.Item label="StoreSize">{messageDetail.messageView.storeSize} bytes</Descriptions.Item>
|
||||
<Descriptions.Item label="ReconsumeTimes">{messageDetail.messageView.reconsumeTimes}</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
label="Queue Offset">{messageDetail.messageView.queueOffset}</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
label="StoreSize">{messageDetail.messageView.storeSize} bytes</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
label="ReconsumeTimes">{messageDetail.messageView.reconsumeTimes}</Descriptions.Item>
|
||||
<Descriptions.Item label="BodyCRC">{messageDetail.messageView.bodyCRC}</Descriptions.Item>
|
||||
<Descriptions.Item label="SysFlag">{messageDetail.messageView.sysFlag}</Descriptions.Item>
|
||||
<Descriptions.Item label="Flag">{messageDetail.messageView.flag}</Descriptions.Item>
|
||||
<Descriptions.Item label="PreparedTransactionOffset">{messageDetail.messageView.preparedTransactionOffset}</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
label="PreparedTransactionOffset">{messageDetail.messageView.preparedTransactionOffset}</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
{/* 消息属性部分 */}
|
||||
{Object.keys(messageDetail.messageView.properties).length > 0 && (
|
||||
<Descriptions title={<Text strong>{t.MESSAGE_PROPERTIES}</Text>} bordered column={1} size="small" style={{ marginBottom: 20 }}>
|
||||
<Descriptions title={<Text strong>{t.MESSAGE_PROPERTIES}</Text>} bordered column={1}
|
||||
size="small" style={{marginBottom: 20}}>
|
||||
{Object.entries(messageDetail.messageView.properties).map(([key, value]) => (
|
||||
<Descriptions.Item label={key} key={key}><Text copyable>{value}</Text></Descriptions.Item>
|
||||
<Descriptions.Item label={key} key={key}><Text
|
||||
copyable>{value}</Text></Descriptions.Item>
|
||||
))}
|
||||
</Descriptions>
|
||||
)}
|
||||
|
||||
{/* 消息体部分 */}
|
||||
<Descriptions title={<Text strong>{t.MESSAGE_BODY}</Text>} bordered column={1} size="small" style={{ marginBottom: 20 }}>
|
||||
<Descriptions title={<Text strong>{t.MESSAGE_BODY}</Text>} bordered column={1} size="small"
|
||||
style={{marginBottom: 20}}>
|
||||
<Descriptions.Item>
|
||||
<Paragraph
|
||||
copyable
|
||||
@@ -146,9 +157,10 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
|
||||
{messageDetail.messageTrackList && messageDetail.messageTrackList.length > 0 ? (
|
||||
<>
|
||||
<Text strong>{t.MESSAGE_TRACKING}</Text>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<div style={{marginTop: 10}}>
|
||||
{messageDetail.messageTrackList.map((track, index) => (
|
||||
<Descriptions bordered column={1} size="small" key={index} style={{ marginBottom: 15 }}>
|
||||
<Descriptions bordered column={1} size="small" key={index}
|
||||
style={{marginBottom: 15}}>
|
||||
<Descriptions.Item label={t.CONSUMER_GROUP}>
|
||||
{track.consumerGroup}
|
||||
</Descriptions.Item>
|
||||
@@ -165,10 +177,10 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t.OPERATION}>
|
||||
<Button
|
||||
icon={<SyncOutlined />}
|
||||
icon={<SyncOutlined/>}
|
||||
onClick={() => onResendMessage(messageDetail.messageView, track.consumerGroup)}
|
||||
size="small"
|
||||
style={{ marginRight: 8 }}
|
||||
style={{marginRight: 8}}
|
||||
>
|
||||
{t.RESEND_MESSAGE}
|
||||
</Button>
|
||||
@@ -181,7 +193,10 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
|
||||
ellipsis={{
|
||||
rows: 2, // 默认显示2行
|
||||
expandable: true,
|
||||
symbol: <Text style={{ color: '#1890ff', cursor: 'pointer' }}>{t.READ_MORE}</Text>, // 蓝色展开文本
|
||||
symbol: <Text style={{
|
||||
color: '#1890ff',
|
||||
cursor: 'pointer'
|
||||
}}>{t.READ_MORE}</Text>, // 蓝色展开文本
|
||||
}}
|
||||
>
|
||||
{track.exceptionDesc}
|
||||
@@ -198,7 +213,8 @@ const MessageDetailViewDialog = ({ visible, onCancel, messageId, topic, onResend
|
||||
</>
|
||||
) : (
|
||||
// 当 messageDetail 为 null 时,可以显示一个占位符或者不显示内容
|
||||
!loading && !error && <Paragraph style={{ textAlign: 'center' }}>{t.NO_MESSAGE_DETAIL_AVAILABLE}</Paragraph>
|
||||
!loading && !error &&
|
||||
<Paragraph style={{textAlign: 'center'}}>{t.NO_MESSAGE_DETAIL_AVAILABLE}</Paragraph>
|
||||
)}
|
||||
</Spin>
|
||||
</Modal>
|
||||
|
@@ -15,15 +15,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Form, Input, Typography, Collapse, Table, Tag } from 'antd';
|
||||
import React, {useEffect, useRef} from 'react';
|
||||
import {Collapse, Form, Input, Table, Tag, Typography} from 'antd';
|
||||
import moment from 'moment';
|
||||
import { useLanguage } from '../i18n/LanguageContext';
|
||||
import {useLanguage} from '../i18n/LanguageContext';
|
||||
import Paragraph from "antd/es/skeleton/Paragraph";
|
||||
import * as echarts from 'echarts'; // Import ECharts
|
||||
|
||||
const { Text } = Typography;
|
||||
const { Panel } = Collapse;
|
||||
const {Text} = Typography;
|
||||
const {Panel} = Collapse;
|
||||
|
||||
// Constants for styling and formatting, derived from the example
|
||||
const SUCCESS_COLOR = '#75d874';
|
||||
@@ -36,8 +36,8 @@ const TIME_FORMAT_PATTERN = "YYYY-MM-DD HH:mm:ss.SSS";
|
||||
const DEFAULT_DISPLAY_DURATION = 10 * 1000;
|
||||
const TRANSACTION_CHECK_COST_TIME = 50; // transactionTraceNode do not have costTime, assume it cost 50ms
|
||||
|
||||
const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
|
||||
const { t } = useLanguage();
|
||||
const MessageTraceDetailViewDialog = ({ngDialogData}) => {
|
||||
const {t} = useLanguage();
|
||||
const messageTraceGraphRef = useRef(null);
|
||||
|
||||
const producerNode = ngDialogData?.producerNode;
|
||||
@@ -125,6 +125,7 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
|
||||
}
|
||||
return `Cost Time: ${formatCostTimeStr(costTime)}<br/>`
|
||||
}
|
||||
|
||||
function buildTimeStamp(timestamp) {
|
||||
if (timestamp < 0) {
|
||||
return 'N/A';
|
||||
@@ -323,94 +324,181 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
|
||||
|
||||
// ... (rest of your existing component code)
|
||||
const transactionColumns = [
|
||||
{ title: t.TIMESTAMP, dataIndex: 'beginTimestamp', key: 'beginTimestamp', align: 'center', render: (text) => moment(text).format('YYYY-MM-DD HH:mm:ss.SSS') },
|
||||
{ title: t.TRANSACTION_STATE, dataIndex: 'transactionState', key: 'transactionState', align: 'center', render: (text) => <Tag color={text === 'COMMIT_MESSAGE' ? 'green' : (text === 'ROLLBACK_MESSAGE' ? 'red' : 'default')}>{text}</Tag> },
|
||||
{ title: t.FROM_TRANSACTION_CHECK, dataIndex: 'fromTransactionCheck', key: 'fromTransactionCheck', align: 'center', render: (text) => (text ? <Tag color="blue">{t.YES}</Tag> : <Tag color="purple">{t.NO}</Tag>) },
|
||||
{ title: t.CLIENT_HOST, dataIndex: 'clientHost', key: 'clientHost', align: 'center' },
|
||||
{ title: t.STORE_HOST, dataIndex: 'storeHost', key: 'storeHost', align: 'center' },
|
||||
{
|
||||
title: t.TIMESTAMP,
|
||||
dataIndex: 'beginTimestamp',
|
||||
key: 'beginTimestamp',
|
||||
align: 'center',
|
||||
render: (text) => moment(text).format('YYYY-MM-DD HH:mm:ss.SSS')
|
||||
},
|
||||
{
|
||||
title: t.TRANSACTION_STATE,
|
||||
dataIndex: 'transactionState',
|
||||
key: 'transactionState',
|
||||
align: 'center',
|
||||
render: (text) => <Tag
|
||||
color={text === 'COMMIT_MESSAGE' ? 'green' : (text === 'ROLLBACK_MESSAGE' ? 'red' : 'default')}>{text}</Tag>
|
||||
},
|
||||
{
|
||||
title: t.FROM_TRANSACTION_CHECK,
|
||||
dataIndex: 'fromTransactionCheck',
|
||||
key: 'fromTransactionCheck',
|
||||
align: 'center',
|
||||
render: (text) => (text ? <Tag color="blue">{t.YES}</Tag> : <Tag color="purple">{t.NO}</Tag>)
|
||||
},
|
||||
{title: t.CLIENT_HOST, dataIndex: 'clientHost', key: 'clientHost', align: 'center'},
|
||||
{title: t.STORE_HOST, dataIndex: 'storeHost', key: 'storeHost', align: 'center'},
|
||||
];
|
||||
|
||||
const consumeColumns = [
|
||||
{ title: t.BEGIN_TIMESTAMP, dataIndex: 'beginTimestamp', key: 'beginTimestamp', align: 'center', render: (text) => text < 0 ? 'N/A' : moment(text).format('YYYY-MM-DD HH:mm:ss.SSS') },
|
||||
{ title: t.END_TIMESTAMP, dataIndex: 'endTimestamp', key: 'endTimestamp', align: 'center', render: (text) => text < 0 ? 'N/A' : moment(text).format('YYYY-MM-DD HH:mm:ss.SSS') },
|
||||
{ title: t.COST_TIME, dataIndex: 'costTime', key: 'costTime', align: 'center', render: (text) => text < 0 ? 'N/A' : `${text === 0 ? '<1' : text}ms` },
|
||||
{ title: t.STATUS, dataIndex: 'status', key: 'status', align: 'center', render: (text) => <Tag color={text === 'SUCCESS' ? 'green' : (text === 'FAILED' ? 'red' : 'default')}>{text}</Tag> },
|
||||
{ title: t.RETRY_TIMES, dataIndex: 'retryTimes', key: 'retryTimes', align: 'center', render: (text) => text < 0 ? 'N/A' : text },
|
||||
{ title: t.CLIENT_HOST, dataIndex: 'clientHost', key: 'clientHost', align: 'center' },
|
||||
{ title: t.STORE_HOST, dataIndex: 'storeHost', key: 'storeHost', align: 'center' },
|
||||
{
|
||||
title: t.BEGIN_TIMESTAMP,
|
||||
dataIndex: 'beginTimestamp',
|
||||
key: 'beginTimestamp',
|
||||
align: 'center',
|
||||
render: (text) => text < 0 ? 'N/A' : moment(text).format('YYYY-MM-DD HH:mm:ss.SSS')
|
||||
},
|
||||
{
|
||||
title: t.END_TIMESTAMP,
|
||||
dataIndex: 'endTimestamp',
|
||||
key: 'endTimestamp',
|
||||
align: 'center',
|
||||
render: (text) => text < 0 ? 'N/A' : moment(text).format('YYYY-MM-DD HH:mm:ss.SSS')
|
||||
},
|
||||
{
|
||||
title: t.COST_TIME,
|
||||
dataIndex: 'costTime',
|
||||
key: 'costTime',
|
||||
align: 'center',
|
||||
render: (text) => text < 0 ? 'N/A' : `${text === 0 ? '<1' : text}ms`
|
||||
},
|
||||
{
|
||||
title: t.STATUS,
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
render: (text) => <Tag
|
||||
color={text === 'SUCCESS' ? 'green' : (text === 'FAILED' ? 'red' : 'default')}>{text}</Tag>
|
||||
},
|
||||
{
|
||||
title: t.RETRY_TIMES,
|
||||
dataIndex: 'retryTimes',
|
||||
key: 'retryTimes',
|
||||
align: 'center',
|
||||
render: (text) => text < 0 ? 'N/A' : text
|
||||
},
|
||||
{title: t.CLIENT_HOST, dataIndex: 'clientHost', key: 'clientHost', align: 'center'},
|
||||
{title: t.STORE_HOST, dataIndex: 'storeHost', key: 'storeHost', align: 'center'},
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px', backgroundColor: '#f0f2f5' }}>
|
||||
<div style={{ marginBottom: '20px', borderRadius: '8px', overflow: 'hidden', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)' }}>
|
||||
<div style={{padding: '20px', backgroundColor: '#f0f2f5'}}>
|
||||
<div style={{
|
||||
marginBottom: '20px',
|
||||
borderRadius: '8px',
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)'
|
||||
}}>
|
||||
<Collapse defaultActiveKey={['messageTraceGraph']} expandIconPosition="right">
|
||||
<Panel header={<Typography.Title level={3} style={{ margin: 0, color: '#333' }}>{t.MESSAGE_TRACE_GRAPH}</Typography.Title>} key="messageTraceGraph">
|
||||
<div ref={messageTraceGraphRef} style={{ height: 500, width: '100%', backgroundColor: '#fff', padding: '10px' }}>
|
||||
<Panel header={<Typography.Title level={3} style={{
|
||||
margin: 0,
|
||||
color: '#333'
|
||||
}}>{t.MESSAGE_TRACE_GRAPH}</Typography.Title>} key="messageTraceGraph">
|
||||
<div ref={messageTraceGraphRef}
|
||||
style={{height: 500, width: '100%', backgroundColor: '#fff', padding: '10px'}}>
|
||||
{/* ECharts message trace graph will be rendered here */}
|
||||
{(!producerNode && subscriptionNodeList.length === 0) && (
|
||||
<Text type="secondary" style={{ display: 'block', textAlign: 'center', marginTop: '150px' }}>{t.TRACE_GRAPH_PLACEHOLDER}</Text>
|
||||
<Text type="secondary" style={{
|
||||
display: 'block',
|
||||
textAlign: 'center',
|
||||
marginTop: '150px'
|
||||
}}>{t.TRACE_GRAPH_PLACEHOLDER}</Text>
|
||||
)}
|
||||
</div>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '20px', borderRadius: '8px', overflow: 'hidden', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)' }}>
|
||||
<div style={{
|
||||
marginBottom: '20px',
|
||||
borderRadius: '8px',
|
||||
overflow: 'hidden',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)'
|
||||
}}>
|
||||
<Collapse defaultActiveKey={['sendMessageTrace']} expandIconPosition="right">
|
||||
<Panel header={<Typography.Title level={3} style={{ margin: 0, color: '#333' }}>{t.SEND_MESSAGE_TRACE}</Typography.Title>} key="sendMessageTrace">
|
||||
<Panel header={<Typography.Title level={3} style={{
|
||||
margin: 0,
|
||||
color: '#333'
|
||||
}}>{t.SEND_MESSAGE_TRACE}</Typography.Title>} key="sendMessageTrace">
|
||||
{!producerNode ? (
|
||||
<Paragraph style={{ padding: '16px', textAlign: 'center', color: '#666' }}>{t.NO_PRODUCER_TRACE_DATA}</Paragraph>
|
||||
<Paragraph style={{
|
||||
padding: '16px',
|
||||
textAlign: 'center',
|
||||
color: '#666'
|
||||
}}>{t.NO_PRODUCER_TRACE_DATA}</Paragraph>
|
||||
) : (
|
||||
<div style={{ padding: '16px', backgroundColor: '#fff' }}>
|
||||
<Typography.Title level={4} style={{ marginBottom: '20px' }}>
|
||||
{t.SEND_MESSAGE_INFO} : ( {t.MESSAGE_ID} <Text strong copyable>{producerNode.msgId}</Text> )
|
||||
<div style={{padding: '16px', backgroundColor: '#fff'}}>
|
||||
<Typography.Title level={4} style={{marginBottom: '20px'}}>
|
||||
{t.SEND_MESSAGE_INFO} : ( {t.MESSAGE_ID} <Text strong
|
||||
copyable>{producerNode.msgId}</Text> )
|
||||
</Typography.Title>
|
||||
<Form layout="vertical" colon={false}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '16px' }}>
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
|
||||
gap: '16px'
|
||||
}}>
|
||||
<Form.Item label={<Text strong>{t.TOPIC}</Text>}>
|
||||
<Input value={producerNode.topic} readOnly />
|
||||
<Input value={producerNode.topic} readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Text strong>{t.PRODUCER_GROUP}</Text>}>
|
||||
<Input value={producerNode.groupName} readOnly />
|
||||
<Input value={producerNode.groupName} readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Text strong>{t.MESSAGE_KEY}</Text>}>
|
||||
<Input value={producerNode.keys} readOnly />
|
||||
<Input value={producerNode.keys} readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Text strong>{t.TAG}</Text>}>
|
||||
<Input value={producerNode.tags} readOnly />
|
||||
<Input value={producerNode.tags} readOnly/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={<Text strong>{t.BEGIN_TIMESTAMP}</Text>}>
|
||||
<Input value={moment(producerNode.traceNode.beginTimestamp).format('YYYY-MM-DD HH:mm:ss.SSS')} readOnly />
|
||||
<Input
|
||||
value={moment(producerNode.traceNode.beginTimestamp).format('YYYY-MM-DD HH:mm:ss.SSS')}
|
||||
readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Text strong>{t.END_TIMESTAMP}</Text>}>
|
||||
<Input value={moment(producerNode.traceNode.endTimestamp).format('YYYY-MM-DD HH:mm:ss.SSS')} readOnly />
|
||||
<Input
|
||||
value={moment(producerNode.traceNode.endTimestamp).format('YYYY-MM-DD HH:mm:ss.SSS')}
|
||||
readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Text strong>{t.COST_TIME}</Text>}>
|
||||
<Input value={`${producerNode.traceNode.costTime === 0 ? '<1' : producerNode.traceNode.costTime}ms`} readOnly />
|
||||
<Input
|
||||
value={`${producerNode.traceNode.costTime === 0 ? '<1' : producerNode.traceNode.costTime}ms`}
|
||||
readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Text strong>{t.MSG_TYPE}</Text>}>
|
||||
<Input value={producerNode.traceNode.msgType} readOnly />
|
||||
<Input value={producerNode.traceNode.msgType} readOnly/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={<Text strong>{t.CLIENT_HOST}</Text>}>
|
||||
<Input value={producerNode.traceNode.clientHost} readOnly />
|
||||
<Input value={producerNode.traceNode.clientHost} readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Text strong>{t.STORE_HOST}</Text>}>
|
||||
<Input value={producerNode.traceNode.storeHost} readOnly />
|
||||
<Input value={producerNode.traceNode.storeHost} readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Text strong>{t.RETRY_TIMES}</Text>}>
|
||||
<Input value={producerNode.traceNode.retryTimes} readOnly />
|
||||
<Input value={producerNode.traceNode.retryTimes} readOnly/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Text strong>{t.OFFSET_MSG_ID}</Text>}>
|
||||
<Input value={producerNode.offSetMsgId} readOnly />
|
||||
<Input value={producerNode.offSetMsgId} readOnly/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
{producerNode.transactionNodeList && producerNode.transactionNodeList.length > 0 && (
|
||||
<div style={{ marginTop: '30px' }}>
|
||||
<Typography.Title level={4} style={{ marginBottom: '15px' }}>{t.CHECK_TRANSACTION_INFO}:</Typography.Title>
|
||||
<div style={{marginTop: '30px'}}>
|
||||
<Typography.Title level={4}
|
||||
style={{marginBottom: '15px'}}>{t.CHECK_TRANSACTION_INFO}:</Typography.Title>
|
||||
<Table
|
||||
columns={transactionColumns}
|
||||
dataSource={producerNode.transactionNodeList}
|
||||
@@ -418,7 +506,7 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
|
||||
bordered
|
||||
pagination={false}
|
||||
size="middle"
|
||||
scroll={{ x: 'max-content' }}
|
||||
scroll={{x: 'max-content'}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -428,22 +516,31 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
|
||||
</Collapse>
|
||||
</div>
|
||||
|
||||
<div style={{ borderRadius: '8px', overflow: 'hidden', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)' }}>
|
||||
<div style={{borderRadius: '8px', overflow: 'hidden', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)'}}>
|
||||
<Collapse defaultActiveKey={['consumeMessageTrace']} expandIconPosition="right">
|
||||
<Panel header={<Typography.Title level={3} style={{ margin: 0, color: '#333' }}>{t.CONSUME_MESSAGE_TRACE}</Typography.Title>} key="consumeMessageTrace">
|
||||
<Panel header={<Typography.Title level={3} style={{
|
||||
margin: 0,
|
||||
color: '#333'
|
||||
}}>{t.CONSUME_MESSAGE_TRACE}</Typography.Title>} key="consumeMessageTrace">
|
||||
{subscriptionNodeList.length === 0 ? (
|
||||
<Paragraph style={{ padding: '16px', textAlign: 'center', color: '#666' }}>{t.NO_CONSUMER_TRACE_DATA}</Paragraph>
|
||||
<Paragraph style={{
|
||||
padding: '16px',
|
||||
textAlign: 'center',
|
||||
color: '#666'
|
||||
}}>{t.NO_CONSUMER_TRACE_DATA}</Paragraph>
|
||||
) : (
|
||||
<div style={{ padding: '16px', backgroundColor: '#fff' }}>
|
||||
<div style={{padding: '16px', backgroundColor: '#fff'}}>
|
||||
{subscriptionNodeList.map(subscriptionNode => (
|
||||
<Collapse
|
||||
key={subscriptionNode.subscriptionGroup}
|
||||
style={{ marginBottom: '10px', border: '1px solid #e0e0e0', borderRadius: '4px' }}
|
||||
style={{marginBottom: '10px', border: '1px solid #e0e0e0', borderRadius: '4px'}}
|
||||
defaultActiveKey={[subscriptionNode.subscriptionGroup]}
|
||||
ghost
|
||||
>
|
||||
<Panel
|
||||
header={<Typography.Title level={4} style={{ margin: 0 }}>{t.SUBSCRIPTION_GROUP}: <Text strong>{subscriptionNode.subscriptionGroup}</Text></Typography.Title>}
|
||||
header={<Typography.Title level={4}
|
||||
style={{margin: 0}}>{t.SUBSCRIPTION_GROUP}: <Text
|
||||
strong>{subscriptionNode.subscriptionGroup}</Text></Typography.Title>}
|
||||
key={subscriptionNode.subscriptionGroup}
|
||||
>
|
||||
<Table
|
||||
@@ -453,7 +550,7 @@ const MessageTraceDetailViewDialog = ({ ngDialogData }) => {
|
||||
bordered
|
||||
pagination={false}
|
||||
size="middle"
|
||||
scroll={{ x: 'max-content' }}
|
||||
scroll={{x: 'max-content'}}
|
||||
/>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
|
@@ -16,29 +16,29 @@
|
||||
*/
|
||||
|
||||
import React, {useEffect, 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 {Button, Drawer, Dropdown, Grid, Layout, Menu, Space} from 'antd';
|
||||
import {BgColorsOutlined, DownOutlined, GlobalOutlined, MenuOutlined, UserOutlined} 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 {Header} = Layout;
|
||||
const {useBreakpoint} = Grid; // Used to determine screen breakpoints
|
||||
|
||||
const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
const Navbar = ({rmqVersion = true, showAcl = true}) => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { lang, setLang, t } = useLanguage();
|
||||
const {lang, setLang, t} = useLanguage();
|
||||
const screens = useBreakpoint(); // Get current screen size breakpoints
|
||||
const { currentThemeName, setCurrentThemeName } = useTheme();
|
||||
const {currentThemeName, setCurrentThemeName} = useTheme();
|
||||
const [userName, setUserName] = useState(null);
|
||||
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 }) => {
|
||||
const handleMenuClick = ({key}) => {
|
||||
navigate(`/${key}`);
|
||||
setDrawerVisible(false); // Close drawer after clicking a menu item
|
||||
};
|
||||
@@ -63,13 +63,13 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
const storedUsername = window.localStorage.getItem("username");
|
||||
if (storedUsername) {
|
||||
setUserName(storedUsername);
|
||||
}else {
|
||||
} else {
|
||||
setUserName(null);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const langMenu = (
|
||||
<Menu onClick={({ key }) => setLang(key)}>
|
||||
<Menu onClick={({key}) => setLang(key)}>
|
||||
<Menu.Item key="en">{t.ENGLISH}</Menu.Item>
|
||||
<Menu.Item key="zh">{t.CHINESE}</Menu.Item>
|
||||
</Menu>
|
||||
@@ -82,7 +82,7 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
);
|
||||
|
||||
const themeMenu = (
|
||||
<Menu onClick={({ key }) => setCurrentThemeName(key)}>
|
||||
<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>
|
||||
@@ -92,17 +92,17 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
|
||||
// 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 }] : []),
|
||||
{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.ACL_MANAGEMENT}] : []),
|
||||
];
|
||||
|
||||
// Determine if it's a small screen (e.g., less than md)
|
||||
@@ -120,7 +120,7 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
padding: isExtraSmallScreen ? '0 16px' : '0 24px', // Smaller padding on extra small screens
|
||||
}}
|
||||
>
|
||||
<div className="navbar-left" style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<div className="navbar-left" style={{display: 'flex', alignItems: 'center'}}>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
@@ -141,33 +141,33 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
mode="horizontal"
|
||||
items={menuItems}
|
||||
theme="dark" // Use dark theme to match Header background
|
||||
style={{ flex: 1, minWidth: 0 }} // Allow menu items to adapt width
|
||||
style={{flex: 1, minWidth: 0}} // Allow menu items to adapt width
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Space size={isExtraSmallScreen ? 8 : 16} > {/* Adjust spacing for buttons */}
|
||||
<Space size={isExtraSmallScreen ? 8 : 16}> {/* Adjust spacing for buttons */}
|
||||
{/* Theme switch button */}
|
||||
<Dropdown overlay={themeMenu}>
|
||||
<Button icon={<BgColorsOutlined />} size="small">
|
||||
<Button icon={<BgColorsOutlined/>} size="small">
|
||||
{!isExtraSmallScreen && `${t.TOPIC}: ${currentThemeName}`}
|
||||
<DownOutlined />
|
||||
<DownOutlined/>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Dropdown overlay={langMenu}>
|
||||
<Button icon={<GlobalOutlined />} size="small">
|
||||
<Button icon={<GlobalOutlined/>} size="small">
|
||||
{!isExtraSmallScreen && t.CHANGE_LANG} {/* Hide text on extra small screens */}
|
||||
<DownOutlined />
|
||||
<DownOutlined/>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
|
||||
{userName && (
|
||||
<Dropdown overlay={userMenu}>
|
||||
{/* 使用一个可点击的元素作为 Dropdown 的唯一子元素 */}
|
||||
<a onClick={e => e.preventDefault()} style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<UserOutlined style={{ marginRight: 8 }} /> {/* 添加一些间距 */}
|
||||
<a onClick={e => e.preventDefault()} style={{display: 'flex', alignItems: 'center'}}>
|
||||
<UserOutlined style={{marginRight: 8}}/> {/* 添加一些间距 */}
|
||||
{userName}
|
||||
<DownOutlined style={{ marginLeft: 8 }} />
|
||||
<DownOutlined style={{marginLeft: 8}}/>
|
||||
</a>
|
||||
</Dropdown>
|
||||
)}
|
||||
@@ -175,9 +175,9 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
{isSmallScreen && ( // Display hamburger icon on small screens
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<MenuOutlined />}
|
||||
icon={<MenuOutlined/>}
|
||||
onClick={() => setDrawerVisible(true)}
|
||||
style={{ marginLeft: isExtraSmallScreen ? 8 : 16 }} // Adjust margin for hamburger icon
|
||||
style={{marginLeft: isExtraSmallScreen ? 8 : 16}} // Adjust margin for hamburger icon
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
@@ -192,7 +192,7 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
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
|
||||
bodyStyle={{padding: 0, backgroundColor: '#1c324a'}} // Set Drawer body background to dark
|
||||
width={200} // Set drawer width
|
||||
>
|
||||
<Menu
|
||||
@@ -201,7 +201,7 @@ const Navbar = ({ rmqVersion = true, showAcl = true}) => {
|
||||
mode="inline" // Use vertical menu in drawer
|
||||
items={menuItems}
|
||||
theme="dark"
|
||||
style={{ height: '100%', borderRight: 0 }} // Ensure menu fills the drawer
|
||||
style={{height: '100%', borderRight: 0}} // Ensure menu fills the drawer
|
||||
/>
|
||||
</Drawer>
|
||||
</Header>
|
||||
|
@@ -15,23 +15,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Input, Select, Tag, Space } from 'antd';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import React, { useState } from 'react';
|
||||
import {Input, Select, Space, Tag} from 'antd';
|
||||
import {PlusOutlined} from '@ant-design/icons';
|
||||
import React, {useState} from 'react';
|
||||
|
||||
const { Option } = Select;
|
||||
const {Option} = Select;
|
||||
|
||||
// 资源类型枚举
|
||||
const resourceTypes = [
|
||||
{ value: 0, label: 'Unknown', prefix: 'UNKNOWN' },
|
||||
{ value: 1, label: 'Any', prefix: 'ANY' },
|
||||
{ value: 2, label: 'Cluster', prefix: 'CLUSTER' },
|
||||
{ value: 3, label: 'Namespace', prefix: 'NAMESPACE' },
|
||||
{ value: 4, label: 'Topic', prefix: 'TOPIC' },
|
||||
{ value: 5, label: 'Group', prefix: 'GROUP' },
|
||||
{value: 0, label: 'Unknown', prefix: 'UNKNOWN'},
|
||||
{value: 1, label: 'Any', prefix: 'ANY'},
|
||||
{value: 2, label: 'Cluster', prefix: 'CLUSTER'},
|
||||
{value: 3, label: 'Namespace', prefix: 'NAMESPACE'},
|
||||
{value: 4, label: 'Topic', prefix: 'TOPIC'},
|
||||
{value: 5, label: 'Group', prefix: 'GROUP'},
|
||||
];
|
||||
|
||||
const ResourceInput = ({ value = [], onChange }) => {
|
||||
const ResourceInput = ({value = [], onChange}) => {
|
||||
// 确保 value 始终是数组
|
||||
const safeValue = Array.isArray(value) ? value : [];
|
||||
|
||||
@@ -96,7 +96,7 @@ const ResourceInput = ({ value = [], onChange }) => {
|
||||
<Space>
|
||||
<Select
|
||||
value={selectedType}
|
||||
style={{ width: 120 }}
|
||||
style={{width: 120}}
|
||||
onChange={handleTypeChange}
|
||||
>
|
||||
{resourceTypes.map(type => (
|
||||
@@ -107,7 +107,7 @@ const ResourceInput = ({ value = [], onChange }) => {
|
||||
</Select>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
style={{ width: 180 }}
|
||||
style={{width: 180}}
|
||||
value={resourceName}
|
||||
onChange={handleNameChange}
|
||||
onPressEnter={handleAddResource}
|
||||
@@ -116,8 +116,8 @@ const ResourceInput = ({ value = [], onChange }) => {
|
||||
/>
|
||||
</Space>
|
||||
) : (
|
||||
<Tag onClick={showInput} style={{ background: '#fff', borderStyle: 'dashed' }}>
|
||||
<PlusOutlined /> 添加资源
|
||||
<Tag onClick={showInput} style={{background: '#fff', borderStyle: 'dashed'}}>
|
||||
<PlusOutlined/> 添加资源
|
||||
</Tag>
|
||||
)}
|
||||
</Space>
|
||||
|
@@ -15,27 +15,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Input, Select } from 'antd';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {Input, Select} from 'antd';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
|
||||
const { Option } = Select;
|
||||
const {Option} = Select;
|
||||
|
||||
// Subject 类型枚举
|
||||
const subjectTypes = [
|
||||
{ value: 'User', label: 'User' },
|
||||
{value: 'User', label: 'User'},
|
||||
];
|
||||
|
||||
const SubjectInput = ({ value, onChange, disabled }) => {
|
||||
const SubjectInput = ({value, onChange, disabled}) => {
|
||||
// 解析传入的 value,将其拆分为 type 和 name
|
||||
const parseValue = (val) => {
|
||||
if (!val || typeof val !== 'string') {
|
||||
return { type: subjectTypes[0].value, name: '' }; // 默认值
|
||||
return {type: subjectTypes[0].value, name: ''}; // 默认值
|
||||
}
|
||||
const parts = val.split(':');
|
||||
if (parts.length === 2 && subjectTypes.some(t => t.value === parts[0])) {
|
||||
return { type: parts[0], name: parts[1] };
|
||||
return {type: parts[0], name: parts[1]};
|
||||
}
|
||||
return { type: subjectTypes[0].value, name: val }; // 如果格式不匹配,将整个值作为 name,类型设为默认
|
||||
return {type: subjectTypes[0].value, name: val}; // 如果格式不匹配,将整个值作为 name,类型设为默认
|
||||
};
|
||||
|
||||
const [currentType, setCurrentType] = useState(() => parseValue(value).type);
|
||||
@@ -76,7 +76,7 @@ const SubjectInput = ({ value, onChange, disabled }) => {
|
||||
return (
|
||||
<Input.Group compact>
|
||||
<Select
|
||||
style={{ width: '30%' }}
|
||||
style={{width: '30%'}}
|
||||
value={currentType}
|
||||
onChange={onTypeChange}
|
||||
disabled={disabled}
|
||||
@@ -88,7 +88,7 @@ const SubjectInput = ({ value, onChange, disabled }) => {
|
||||
))}
|
||||
</Select>
|
||||
<Input
|
||||
style={{ width: '70%' }}
|
||||
style={{width: '70%'}}
|
||||
value={currentName}
|
||||
onChange={onNameChange}
|
||||
placeholder="请输入名称 (例如: yourUsername)"
|
||||
|
@@ -15,13 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Table, Spin } from 'antd';
|
||||
import { remoteApi } from '../../api/remoteApi/remoteApi';
|
||||
import { useLanguage } from '../../i18n/LanguageContext';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Modal, Spin, Table} from 'antd';
|
||||
import {remoteApi} from '../../api/remoteApi/remoteApi';
|
||||
import {useLanguage} from '../../i18n/LanguageContext';
|
||||
|
||||
const ClientInfoModal = ({ visible, group, address, onCancel }) => {
|
||||
const { t } = useLanguage();
|
||||
const ClientInfoModal = ({visible, group, address, onCancel}) => {
|
||||
const {t} = useLanguage();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [connectionData, setConnectionData] = useState(null);
|
||||
const [subscriptionData, setSubscriptionData] = useState(null);
|
||||
@@ -46,15 +46,15 @@ const ClientInfoModal = ({ visible, group, address, onCancel }) => {
|
||||
}, [visible, group, address]);
|
||||
|
||||
const connectionColumns = [
|
||||
{ title: 'ClientId', dataIndex: 'clientId' },
|
||||
{ title: 'ClientAddr', dataIndex: 'clientAddr' },
|
||||
{ title: 'Language', dataIndex: 'language' },
|
||||
{ title: 'Version', dataIndex: 'versionDesc' },
|
||||
{title: 'ClientId', dataIndex: 'clientId'},
|
||||
{title: 'ClientAddr', dataIndex: 'clientAddr'},
|
||||
{title: 'Language', dataIndex: 'language'},
|
||||
{title: 'Version', dataIndex: 'versionDesc'},
|
||||
];
|
||||
|
||||
const subscriptionColumns = [
|
||||
{ title: 'Topic', dataIndex: 'topic' },
|
||||
{ title: 'SubExpression', dataIndex: 'subString' },
|
||||
{title: 'Topic', dataIndex: 'topic'},
|
||||
{title: 'SubExpression', dataIndex: 'subString'},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -88,7 +88,7 @@ const ClientInfoModal = ({ visible, group, address, onCancel }) => {
|
||||
rowKey="topic"
|
||||
pagination={false}
|
||||
locale={{
|
||||
emptyText: loading ? <Spin size="small" /> : t.NO_DATA
|
||||
emptyText: loading ? <Spin size="small"/> : t.NO_DATA
|
||||
}}
|
||||
/>
|
||||
<p>ConsumeType: {connectionData.consumeType}</p>
|
||||
|
@@ -15,13 +15,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Descriptions, Form, Input, Select, Switch, message } from 'antd';
|
||||
import { remoteApi } from '../../api/remoteApi/remoteApi'; // 确保路径正确
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Descriptions, Form, Input, message, Select, Switch} from 'antd';
|
||||
import {remoteApi} from '../../api/remoteApi/remoteApi'; // 确保路径正确
|
||||
|
||||
const { Option } = Select;
|
||||
const {Option} = Select;
|
||||
|
||||
const ConsumerConfigItem = ({ initialConfig, isAddConfig, group, brokerName, allBrokerList, allClusterNames,onCancel, onSuccess, t }) => {
|
||||
const ConsumerConfigItem = ({
|
||||
initialConfig,
|
||||
isAddConfig,
|
||||
group,
|
||||
brokerName,
|
||||
allBrokerList,
|
||||
allClusterNames,
|
||||
onCancel,
|
||||
onSuccess,
|
||||
t
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [currentBrokerName, setCurrentBrokerName] = useState(brokerName);
|
||||
|
||||
|
@@ -34,7 +34,7 @@ const ConsumerConfigModal = ({visible, isAddConfig, group, onCancel, setIsAddCon
|
||||
setLoading(true);
|
||||
try {
|
||||
// Fetch cluster list for broker names and cluster names
|
||||
if(isAddConfig) {
|
||||
if (isAddConfig) {
|
||||
const clusterResponse = await remoteApi.getClusterList();
|
||||
if (clusterResponse.status === 0 && clusterResponse.data) {
|
||||
const clusterInfo = clusterResponse.data.clusterInfo;
|
||||
|
@@ -15,13 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Table, Spin } from 'antd';
|
||||
import { remoteApi } from '../../api/remoteApi/remoteApi';
|
||||
import { useLanguage } from '../../i18n/LanguageContext';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Modal, Spin, Table} from 'antd';
|
||||
import {remoteApi} from '../../api/remoteApi/remoteApi';
|
||||
import {useLanguage} from '../../i18n/LanguageContext';
|
||||
|
||||
const ConsumerDetailModal = ({ visible, group, address, onCancel }) => {
|
||||
const { t } = useLanguage();
|
||||
const ConsumerDetailModal = ({visible, group, address, onCancel}) => {
|
||||
const {t} = useLanguage();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [details, setDetails] = useState([]);
|
||||
|
||||
@@ -44,12 +44,12 @@ const ConsumerDetailModal = ({ visible, group, address, onCancel }) => {
|
||||
}, [visible, group, address]);
|
||||
|
||||
const queueColumns = [
|
||||
{ title: 'Broker', dataIndex: 'brokerName' },
|
||||
{ title: 'Queue', dataIndex: 'queueId' },
|
||||
{ title: 'BrokerOffset', dataIndex: 'brokerOffset' },
|
||||
{ title: 'ConsumerOffset', dataIndex: 'consumerOffset' },
|
||||
{ title: 'DiffTotal', dataIndex: 'diffTotal' },
|
||||
{ title: 'LastTimestamp', dataIndex: 'lastTimestamp' },
|
||||
{title: 'Broker', dataIndex: 'brokerName'},
|
||||
{title: 'Queue', dataIndex: 'queueId'},
|
||||
{title: 'BrokerOffset', dataIndex: 'brokerOffset'},
|
||||
{title: 'ConsumerOffset', dataIndex: 'consumerOffset'},
|
||||
{title: 'DiffTotal', dataIndex: 'diffTotal'},
|
||||
{title: 'LastTimestamp', dataIndex: 'lastTimestamp'},
|
||||
];
|
||||
|
||||
return (
|
||||
|
@@ -15,13 +15,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Spin, Checkbox, Button, notification } from 'antd';
|
||||
import { remoteApi } from '../../api/remoteApi/remoteApi';
|
||||
import { useLanguage } from '../../i18n/LanguageContext';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Checkbox, Modal, notification, Spin} from 'antd';
|
||||
import {remoteApi} from '../../api/remoteApi/remoteApi';
|
||||
import {useLanguage} from '../../i18n/LanguageContext';
|
||||
|
||||
const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => {
|
||||
const { t } = useLanguage();
|
||||
const DeleteConsumerModal = ({visible, group, onCancel, onSuccess}) => {
|
||||
const {t} = useLanguage();
|
||||
const [brokerList, setBrokerList] = useState([]);
|
||||
const [selectedBrokers, setSelectedBrokers] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -48,7 +48,7 @@ const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => {
|
||||
// 处理删除提交
|
||||
const handleDelete = async () => {
|
||||
if (selectedBrokers.length === 0) {
|
||||
notification.warning({ message: t.PLEASE_SELECT_BROKER });
|
||||
notification.warning({message: t.PLEASE_SELECT_BROKER});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => {
|
||||
);
|
||||
|
||||
if (response.status === 0) {
|
||||
notification.success({ message: t.DELETE_SUCCESS });
|
||||
notification.success({message: t.DELETE_SUCCESS});
|
||||
onSuccess();
|
||||
onCancel();
|
||||
}
|
||||
@@ -90,9 +90,9 @@ const DeleteConsumerModal = ({ visible, group, onCancel, onSuccess }) => {
|
||||
]}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<div style={{ marginBottom: 16 }}>{t.SELECT_DELETE_BROKERS}:</div>
|
||||
<div style={{marginBottom: 16}}>{t.SELECT_DELETE_BROKERS}:</div>
|
||||
<Checkbox.Group
|
||||
style={{ width: '100%' }}
|
||||
style={{width: '100%'}}
|
||||
value={selectedBrokers}
|
||||
onChange={values => setSelectedBrokers(values)}
|
||||
>
|
||||
|
@@ -15,10 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, DatePicker, Form, Modal, Select } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {Button, DatePicker, Form, Modal, Select} from "antd";
|
||||
import React, {useEffect, useState} from "react";
|
||||
|
||||
const ConsumerResetOffsetDialog = ({ visible, onClose, topic, allConsumerGroupList, handleResetOffset, t }) => {
|
||||
const ConsumerResetOffsetDialog = ({visible, onClose, topic, allConsumerGroupList, handleResetOffset, t}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [selectedConsumerGroup, setSelectedConsumerGroup] = useState([]);
|
||||
const [selectedTime, setSelectedTime] = useState(null);
|
||||
@@ -49,14 +49,14 @@ const ConsumerResetOffsetDialog = ({ visible, onClose, topic, allConsumerGroupLi
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Form form={form} layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
|
||||
<Form form={form} layout="horizontal" labelCol={{span: 6}} wrapperCol={{span: 18}}>
|
||||
<Form.Item label={t.SUBSCRIPTION_GROUP} required>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder={t.SELECT_CONSUMER_GROUP}
|
||||
value={selectedConsumerGroup}
|
||||
onChange={setSelectedConsumerGroup}
|
||||
options={allConsumerGroupList.map(group => ({ value: group, label: group }))}
|
||||
options={allConsumerGroupList.map(group => ({value: group, label: group}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t.TIME} required>
|
||||
@@ -65,7 +65,7 @@ const ConsumerResetOffsetDialog = ({ visible, onClose, topic, allConsumerGroupLi
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value={selectedTime}
|
||||
onChange={setSelectedTime}
|
||||
style={{ width: '100%' }}
|
||||
style={{width: '100%'}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
@@ -19,13 +19,13 @@ import moment from "moment/moment";
|
||||
import {Button, Modal, Table} from "antd";
|
||||
import React from "react";
|
||||
|
||||
const ConsumerViewDialog = ({ visible, onClose, topic, consumerData, consumerGroupCount, t }) => {
|
||||
const ConsumerViewDialog = ({visible, onClose, topic, consumerData, consumerGroupCount, t}) => {
|
||||
const columns = [
|
||||
{ title: t.BROKER, dataIndex: 'brokerName', key: 'brokerName', align: 'center' },
|
||||
{ title: t.QUEUE, dataIndex: 'queueId', key: 'queueId', align: 'center' },
|
||||
{ title: t.CONSUMER_CLIENT, dataIndex: 'clientInfo', key: 'clientInfo', align: 'center' },
|
||||
{ title: t.BROKER_OFFSET, dataIndex: 'brokerOffset', key: 'brokerOffset', align: 'center' },
|
||||
{ title: t.CONSUMER_OFFSET, dataIndex: 'consumerOffset', key: 'consumerOffset', align: 'center' },
|
||||
{title: t.BROKER, dataIndex: 'brokerName', key: 'brokerName', align: 'center'},
|
||||
{title: t.QUEUE, dataIndex: 'queueId', key: 'queueId', align: 'center'},
|
||||
{title: t.CONSUMER_CLIENT, dataIndex: 'clientInfo', key: 'clientInfo', align: 'center'},
|
||||
{title: t.BROKER_OFFSET, dataIndex: 'brokerOffset', key: 'brokerOffset', align: 'center'},
|
||||
{title: t.CONSUMER_OFFSET, dataIndex: 'consumerOffset', key: 'consumerOffset', align: 'center'},
|
||||
{
|
||||
title: t.DIFF_TOTAL,
|
||||
dataIndex: 'diffTotal',
|
||||
@@ -58,15 +58,19 @@ const ConsumerViewDialog = ({ visible, onClose, topic, consumerData, consumerGro
|
||||
<div>{t.NO_DATA} {t.SUBSCRIPTION_GROUP}</div>
|
||||
) : (
|
||||
consumerData && Object.entries(consumerData).map(([consumerGroup, consumeDetail]) => (
|
||||
<div key={consumerGroup} style={{ marginBottom: '24px' }}>
|
||||
<div key={consumerGroup} style={{marginBottom: '24px'}}>
|
||||
<Table
|
||||
bordered
|
||||
pagination={false}
|
||||
showHeader={false}
|
||||
dataSource={[{ consumerGroup, diffTotal: consumeDetail.diffTotal, lastTimestamp: consumeDetail.lastTimestamp }]}
|
||||
dataSource={[{
|
||||
consumerGroup,
|
||||
diffTotal: consumeDetail.diffTotal,
|
||||
lastTimestamp: consumeDetail.lastTimestamp
|
||||
}]}
|
||||
columns={[
|
||||
{ title: t.SUBSCRIPTION_GROUP, dataIndex: 'consumerGroup', key: 'consumerGroup' },
|
||||
{ title: t.DELAY, dataIndex: 'diffTotal', key: 'diffTotal' },
|
||||
{title: t.SUBSCRIPTION_GROUP, dataIndex: 'consumerGroup', key: 'consumerGroup'},
|
||||
{title: t.DELAY, dataIndex: 'diffTotal', key: 'diffTotal'},
|
||||
{
|
||||
title: t.LAST_CONSUME_TIME,
|
||||
dataIndex: 'lastTimestamp',
|
||||
@@ -76,7 +80,7 @@ const ConsumerViewDialog = ({ visible, onClose, topic, consumerData, consumerGro
|
||||
]}
|
||||
rowKey="consumerGroup"
|
||||
size="small"
|
||||
style={{ marginBottom: '12px' }}
|
||||
style={{marginBottom: '12px'}}
|
||||
/>
|
||||
<Table
|
||||
bordered
|
||||
|
@@ -18,7 +18,7 @@
|
||||
import {Button, Modal, Table} from "antd";
|
||||
import React from "react";
|
||||
|
||||
const ResetOffsetResultDialog = ({ visible, onClose, result, t }) => {
|
||||
const ResetOffsetResultDialog = ({visible, onClose, result, t}) => {
|
||||
return (
|
||||
<Modal
|
||||
title="ResetResult"
|
||||
@@ -31,12 +31,12 @@ const ResetOffsetResultDialog = ({ visible, onClose, result, t }) => {
|
||||
]}
|
||||
>
|
||||
{result && Object.entries(result).map(([groupName, groupData]) => (
|
||||
<div key={groupName} style={{ marginBottom: '16px', border: '1px solid #f0f0f0', padding: '10px' }}>
|
||||
<div key={groupName} style={{marginBottom: '16px', border: '1px solid #f0f0f0', padding: '10px'}}>
|
||||
<Table
|
||||
dataSource={[{ groupName, status: groupData.status }]}
|
||||
dataSource={[{groupName, status: groupData.status}]}
|
||||
columns={[
|
||||
{ title: 'GroupName', dataIndex: 'groupName', key: 'groupName' },
|
||||
{ title: 'State', dataIndex: 'status', key: 'status' },
|
||||
{title: 'GroupName', dataIndex: 'groupName', key: 'groupName'},
|
||||
{title: 'State', dataIndex: 'status', key: 'status'},
|
||||
]}
|
||||
pagination={false}
|
||||
rowKey="groupName"
|
||||
@@ -47,8 +47,8 @@ const ResetOffsetResultDialog = ({ visible, onClose, result, t }) => {
|
||||
<div>You Should Check It Yourself</div>
|
||||
) : (
|
||||
<Table
|
||||
dataSource={groupData.rollbackStatsList.map((item, index) => ({ key: index, item }))}
|
||||
columns={[{ dataIndex: 'item', key: 'item' }]}
|
||||
dataSource={groupData.rollbackStatsList.map((item, index) => ({key: index, item}))}
|
||||
columns={[{dataIndex: 'item', key: 'item'}]}
|
||||
pagination={false}
|
||||
rowKey="key"
|
||||
size="small"
|
||||
|
@@ -15,10 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Modal, Table } from "antd";
|
||||
import {Button, Modal, Table} from "antd";
|
||||
import React from "react";
|
||||
|
||||
const RouterViewDialog = ({ visible, onClose, topic, routeData, t }) => {
|
||||
const RouterViewDialog = ({visible, onClose, topic, routeData, t}) => {
|
||||
const brokerColumns = [
|
||||
{
|
||||
title: 'Broker',
|
||||
@@ -30,10 +30,14 @@ const RouterViewDialog = ({ visible, onClose, topic, routeData, t }) => {
|
||||
key: 'brokerAddrs',
|
||||
render: (_, record) => (
|
||||
<Table
|
||||
dataSource={Object.entries(record.brokerAddrs || []).map(([key, value]) => ({ key, idx: key, address: value }))}
|
||||
dataSource={Object.entries(record.brokerAddrs || []).map(([key, value]) => ({
|
||||
key,
|
||||
idx: key,
|
||||
address: value
|
||||
}))}
|
||||
columns={[
|
||||
{ title: 'Index', dataIndex: 'idx', key: 'idx' },
|
||||
{ title: 'Address', dataIndex: 'address', key: 'address' },
|
||||
{title: 'Index', dataIndex: 'idx', key: 'idx'},
|
||||
{title: 'Address', dataIndex: 'address', key: 'address'},
|
||||
]}
|
||||
pagination={false}
|
||||
bordered
|
||||
@@ -82,7 +86,7 @@ const RouterViewDialog = ({ visible, onClose, topic, routeData, t }) => {
|
||||
<div>
|
||||
<h3>Broker Datas:</h3>
|
||||
{routeData?.brokerDatas?.map((item, index) => (
|
||||
<div key={index} style={{ marginBottom: '15px', border: '1px solid #d9d9d9', padding: '10px' }}>
|
||||
<div key={index} style={{marginBottom: '15px', border: '1px solid #d9d9d9', padding: '10px'}}>
|
||||
<Table
|
||||
dataSource={[item]}
|
||||
columns={brokerColumns}
|
||||
@@ -93,7 +97,7 @@ const RouterViewDialog = ({ visible, onClose, topic, routeData, t }) => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ marginTop: '20px' }}>
|
||||
<div style={{marginTop: '20px'}}>
|
||||
<h3>{t.QUEUE_DATAS}:</h3>
|
||||
<Table
|
||||
dataSource={routeData?.queueDatas || []}
|
||||
|
@@ -18,7 +18,7 @@
|
||||
import {Button, Form, Modal, Table} from "antd";
|
||||
import React from "react";
|
||||
|
||||
const SendResultDialog = ({ visible, onClose, result, t }) => {
|
||||
const SendResultDialog = ({visible, onClose, result, t}) => {
|
||||
return (
|
||||
<Modal
|
||||
title="SendResult"
|
||||
@@ -43,11 +43,11 @@ const SendResultDialog = ({ visible, onClose, result, t }) => {
|
||||
: []
|
||||
}
|
||||
columns={[
|
||||
{ dataIndex: 'label', key: 'label' },
|
||||
{dataIndex: 'label', key: 'label'},
|
||||
{
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
render: (text) => <pre style={{ whiteSpace: 'pre-wrap', margin: 0 }}>{text}</pre>,
|
||||
render: (text) => <pre style={{whiteSpace: 'pre-wrap', margin: 0}}>{text}</pre>,
|
||||
},
|
||||
]}
|
||||
pagination={false}
|
||||
@@ -61,5 +61,4 @@ const SendResultDialog = ({ visible, onClose, result, t }) => {
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default SendResultDialog;
|
||||
|
@@ -76,24 +76,24 @@ const SendTopicMessageDialog = ({
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Form form={form} layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
|
||||
<Form form={form} layout="horizontal" labelCol={{span: 6}} wrapperCol={{span: 18}}>
|
||||
<Form.Item label={t.TOPIC} name="topic">
|
||||
<Input disabled />
|
||||
<Input disabled/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t.TAG} name="tag">
|
||||
<Input />
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t.KEY} name="key">
|
||||
<Input />
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t.MESSAGE_BODY} name="messageBody" rules={[{ required: true, message: t.REQUIRED }]}>
|
||||
<Form.Item label={t.MESSAGE_BODY} name="messageBody" rules={[{required: true, message: t.REQUIRED}]}>
|
||||
<Input.TextArea
|
||||
style={{ maxHeight: '200px', minHeight: '200px', resize: 'none' }}
|
||||
style={{maxHeight: '200px', minHeight: '200px', resize: 'none'}}
|
||||
rows={8}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t.ENABLE_MESSAGE_TRACE} name="traceEnabled" valuePropName="checked">
|
||||
<Checkbox />
|
||||
<Checkbox/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
@@ -15,10 +15,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Form, message, Modal, Select } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {Button, Form, message, Modal, Select} from "antd";
|
||||
import React, {useEffect, useState} from "react";
|
||||
|
||||
const SkipMessageAccumulateDialog = ({ visible, onClose, topic, allConsumerGroupList, handleSkipMessageAccumulate, t }) => {
|
||||
const SkipMessageAccumulateDialog = ({
|
||||
visible,
|
||||
onClose,
|
||||
topic,
|
||||
allConsumerGroupList,
|
||||
handleSkipMessageAccumulate,
|
||||
t
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [selectedConsumerGroup, setSelectedConsumerGroup] = useState([]);
|
||||
|
||||
@@ -52,14 +59,14 @@ const SkipMessageAccumulateDialog = ({ visible, onClose, topic, allConsumerGroup
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Form form={form} layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
|
||||
<Form form={form} layout="horizontal" labelCol={{span: 6}} wrapperCol={{span: 18}}>
|
||||
<Form.Item label={t.SUBSCRIPTION_GROUP} required>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder={t.SELECT_CONSUMER_GROUP}
|
||||
value={selectedConsumerGroup}
|
||||
onChange={setSelectedConsumerGroup}
|
||||
options={allConsumerGroupList.map(group => ({ value: group, label: group }))}
|
||||
options={allConsumerGroupList.map(group => ({value: group, label: group}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
@@ -19,11 +19,11 @@ import moment from "moment/moment";
|
||||
import {Button, Modal, Table} from "antd";
|
||||
import React from "react";
|
||||
|
||||
const StatsViewDialog = ({ visible, onClose, topic, statsData, t }) => {
|
||||
const StatsViewDialog = ({visible, onClose, topic, statsData, t}) => {
|
||||
const columns = [
|
||||
{ title: t.QUEUE, dataIndex: 'queue', key: 'queue', align: 'center' },
|
||||
{ title: t.MIN_OFFSET, dataIndex: 'minOffset', key: 'minOffset', align: 'center' },
|
||||
{ title: t.MAX_OFFSET, dataIndex: 'maxOffset', key: 'maxOffset', align: 'center' },
|
||||
{title: t.QUEUE, dataIndex: 'queue', key: 'queue', align: 'center'},
|
||||
{title: t.MIN_OFFSET, dataIndex: 'minOffset', key: 'minOffset', align: 'center'},
|
||||
{title: t.MAX_OFFSET, dataIndex: 'maxOffset', key: 'maxOffset', align: 'center'},
|
||||
{
|
||||
title: t.LAST_UPDATE_TIME_STAMP,
|
||||
dataIndex: 'lastUpdateTimestamp',
|
||||
|
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
// TopicModifyDialog.js
|
||||
import { Button, Modal } from "antd";
|
||||
import {Button, Modal} from "antd";
|
||||
import React from "react";
|
||||
import TopicSingleModifyForm from './TopicSingleModifyForm';
|
||||
|
||||
@@ -43,7 +43,7 @@ const TopicModifyDialog = ({
|
||||
{t.CLOSE}
|
||||
</Button>,
|
||||
]}
|
||||
Style={{ maxHeight: '70vh', overflowY: 'auto' }}
|
||||
Style={{maxHeight: '70vh', overflowY: 'auto'}}
|
||||
>
|
||||
{initialData.map((data, index) => (
|
||||
<TopicSingleModifyForm
|
||||
|
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
|
||||
// TopicSingleModifyForm.js
|
||||
import React, { useEffect } from "react";
|
||||
import {Button, Form, Input, Select, Divider, Row, Col} from "antd";
|
||||
import React, {useEffect} from "react";
|
||||
import {Button, Col, Divider, Form, Input, Row, Select} from "antd";
|
||||
|
||||
const TopicSingleModifyForm = ({
|
||||
initialData,
|
||||
@@ -42,9 +42,9 @@ const TopicSingleModifyForm = ({
|
||||
const handleFormSubmit = () => {
|
||||
form.validateFields()
|
||||
.then(values => {
|
||||
const updatedValues = { ...values };
|
||||
const updatedValues = {...values};
|
||||
// 提交时,如果 clusterNameList 或 brokerNameList 为空,则填充所有可用的名称
|
||||
if(!bIsUpdate){
|
||||
if (!bIsUpdate) {
|
||||
if (!updatedValues.clusterNameList || updatedValues.clusterNameList.length === 0) {
|
||||
updatedValues.clusterNameList = allClusterNameList;
|
||||
}
|
||||
@@ -60,84 +60,85 @@ const TopicSingleModifyForm = ({
|
||||
};
|
||||
|
||||
const messageTypeOptions = [
|
||||
{ value: 'TRANSACTION', label: 'TRANSACTION' },
|
||||
{ value: 'FIFO', label: 'FIFO' },
|
||||
{ value: 'DELAY', label: 'DELAY' },
|
||||
{ value: 'NORMAL', label: 'NORMAL' },
|
||||
{value: 'TRANSACTION', label: 'TRANSACTION'},
|
||||
{value: 'FIFO', label: 'FIFO'},
|
||||
{value: 'DELAY', label: 'DELAY'},
|
||||
{value: 'NORMAL', label: 'NORMAL'},
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ paddingBottom: 24 }}>
|
||||
{bIsUpdate && <Divider orientation="left">{`${t.TOPIC_CONFIG} - ${initialData.brokerNameList ? initialData.brokerNameList.join(', ') : t.UNKNOWN_BROKER}`}</Divider>}
|
||||
<Row justify="center"> {/* 使用 Row 居中内容 */}
|
||||
<Col span={16}> {/* 表单内容占据 12 栅格宽度,并自动居中 */}
|
||||
<Form
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
<div style={{paddingBottom: 24}}>
|
||||
{bIsUpdate && <Divider
|
||||
orientation="left">{`${t.TOPIC_CONFIG} - ${initialData.brokerNameList ? initialData.brokerNameList.join(', ') : t.UNKNOWN_BROKER}`}</Divider>}
|
||||
<Row justify="center"> {/* 使用 Row 居中内容 */}
|
||||
<Col span={16}> {/* 表单内容占据 12 栅格宽度,并自动居中 */}
|
||||
<Form
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
labelCol={{span: 8}}
|
||||
wrapperCol={{span: 16}}
|
||||
>
|
||||
<Form.Item label={t.CLUSTER_NAME} name="clusterNameList">
|
||||
<Select
|
||||
mode="multiple"
|
||||
disabled={bIsUpdate}
|
||||
placeholder={t.SELECT_CLUSTER_NAME}
|
||||
options={allClusterNameList.map(name => ({value: name, label: name}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="BROKER_NAME" name="brokerNameList">
|
||||
<Select
|
||||
mode="multiple"
|
||||
disabled={bIsUpdate}
|
||||
placeholder={t.SELECT_BROKER_NAME}
|
||||
options={allBrokerNameList.map(name => ({value: name, label: name}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t.TOPIC_NAME}
|
||||
name="topicName"
|
||||
rules={[{required: true, message: `${t.TOPIC_NAME}${t.CANNOT_BE_EMPTY}`}]}
|
||||
>
|
||||
<Form.Item label={t.CLUSTER_NAME} name="clusterNameList">
|
||||
<Select
|
||||
mode="multiple"
|
||||
disabled={bIsUpdate}
|
||||
placeholder={t.SELECT_CLUSTER_NAME}
|
||||
options={allClusterNameList.map(name => ({ value: name, label: name }))}
|
||||
/>
|
||||
<Input disabled={bIsUpdate}/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t.MESSAGE_TYPE} name="messageType">
|
||||
<Select
|
||||
disabled={bIsUpdate}
|
||||
options={messageTypeOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t.WRITE_QUEUE_NUMS}
|
||||
name="writeQueueNums"
|
||||
rules={[{required: true, message: `${t.WRITE_QUEUE_NUMS}${t.CANNOT_BE_EMPTY}`}]}
|
||||
>
|
||||
<Input disabled={!writeOperationEnabled}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t.READ_QUEUE_NUMS}
|
||||
name="readQueueNums"
|
||||
rules={[{required: true, message: `${t.READ_QUEUE_NUMS}${t.CANNOT_BE_EMPTY}`}]}
|
||||
>
|
||||
<Input disabled={!writeOperationEnabled}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t.PERM}
|
||||
name="perm"
|
||||
rules={[{required: true, message: `${t.PERM}${t.CANNOT_BE_EMPTY}`}]}
|
||||
>
|
||||
<Input disabled={!writeOperationEnabled}/>
|
||||
</Form.Item>
|
||||
{!initialData.sysFlag && writeOperationEnabled && (
|
||||
<Form.Item wrapperCol={{offset: 8, span: 16}}>
|
||||
<Button type="primary" onClick={handleFormSubmit}>
|
||||
{t.COMMIT}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
<Form.Item label="BROKER_NAME" name="brokerNameList">
|
||||
<Select
|
||||
mode="multiple"
|
||||
disabled={bIsUpdate}
|
||||
placeholder={t.SELECT_BROKER_NAME}
|
||||
options={allBrokerNameList.map(name => ({ value: name, label: name }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t.TOPIC_NAME}
|
||||
name="topicName"
|
||||
rules={[{ required: true, message: `${t.TOPIC_NAME}${t.CANNOT_BE_EMPTY}` }]}
|
||||
>
|
||||
<Input disabled={bIsUpdate} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t.MESSAGE_TYPE} name="messageType">
|
||||
<Select
|
||||
disabled={bIsUpdate}
|
||||
options={messageTypeOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t.WRITE_QUEUE_NUMS}
|
||||
name="writeQueueNums"
|
||||
rules={[{ required: true, message: `${t.WRITE_QUEUE_NUMS}${t.CANNOT_BE_EMPTY}` }]}
|
||||
>
|
||||
<Input disabled={!writeOperationEnabled} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t.READ_QUEUE_NUMS}
|
||||
name="readQueueNums"
|
||||
rules={[{ required: true, message: `${t.READ_QUEUE_NUMS}${t.CANNOT_BE_EMPTY}` }]}
|
||||
>
|
||||
<Input disabled={!writeOperationEnabled} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t.PERM}
|
||||
name="perm"
|
||||
rules={[{ required: true, message: `${t.PERM}${t.CANNOT_BE_EMPTY}` }]}
|
||||
>
|
||||
<Input disabled={!writeOperationEnabled} />
|
||||
</Form.Item>
|
||||
{!initialData.sysFlag && writeOperationEnabled && (
|
||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
||||
<Button type="primary" onClick={handleFormSubmit}>
|
||||
{t.COMMIT}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -15,20 +15,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createContext, useState, useContext } from 'react';
|
||||
import { translations } from '../i18n';
|
||||
import React, {createContext, useContext, useState} from 'react';
|
||||
import {translations} from '../i18n';
|
||||
|
||||
const LanguageContext = createContext({
|
||||
lang: 'en',
|
||||
setLang: () => {},
|
||||
setLang: () => {
|
||||
},
|
||||
t: translations['en'], // 当前语言的文本资源
|
||||
});
|
||||
|
||||
export const LanguageProvider = ({ children }) => {
|
||||
export const LanguageProvider = ({children}) => {
|
||||
const [lang, setLang] = useState('en');
|
||||
const t = translations[lang] || translations['en'];
|
||||
return (
|
||||
<LanguageContext.Provider value={{ lang, setLang, t }}>
|
||||
<LanguageContext.Provider value={{lang, setLang, t}}>
|
||||
{children}
|
||||
</LanguageContext.Provider>
|
||||
);
|
||||
|
@@ -47,10 +47,10 @@ export const translations = {
|
||||
"FETCH_TOPIC_FAILED": "获取主题列表失败",
|
||||
"CONFIRM_DELETE": "确认删除",
|
||||
"CANCEL": "取消",
|
||||
"SELECT_DELETE_BROKERS":"请选择在哪个Broker删除消费者组",
|
||||
"DELETE_CONSUMER_GROUP":"删除消费者组",
|
||||
"SELECT_DELETE_BROKERS": "请选择在哪个Broker删除消费者组",
|
||||
"DELETE_CONSUMER_GROUP": "删除消费者组",
|
||||
"ENGLISH": "英文",
|
||||
"ADD_CONSUMER":"添加消费者",
|
||||
"ADD_CONSUMER": "添加消费者",
|
||||
"CHINESE": "简体中文",
|
||||
"CANNOT_BE_EMPTY": "不能为空",
|
||||
"TITLE": "RocketMQ仪表板",
|
||||
@@ -70,16 +70,16 @@ export const translations = {
|
||||
"CLUSTER_DETAIL": "集群详情",
|
||||
"COMMIT": "提交",
|
||||
"TOPIC": "主题",
|
||||
"SUBSCRIPTION_GROUP":"订阅组",
|
||||
"PRODUCER_GROUP":"生产组",
|
||||
"CONSUMER":"消费者",
|
||||
"PRODUCER":"生产者",
|
||||
"MESSAGE":"消息",
|
||||
"MESSAGE_DETAIL":"消息详情",
|
||||
"RESEND_MESSAGE":"重新发送",
|
||||
"VIEW_EXCEPTION":"查看异常",
|
||||
"DLQ_MESSAGE":"死信消息",
|
||||
"MESSAGETRACE":"消息轨迹",
|
||||
"SUBSCRIPTION_GROUP": "订阅组",
|
||||
"PRODUCER_GROUP": "生产组",
|
||||
"CONSUMER": "消费者",
|
||||
"PRODUCER": "生产者",
|
||||
"MESSAGE": "消息",
|
||||
"MESSAGE_DETAIL": "消息详情",
|
||||
"RESEND_MESSAGE": "重新发送",
|
||||
"VIEW_EXCEPTION": "查看异常",
|
||||
"DLQ_MESSAGE": "死信消息",
|
||||
"MESSAGETRACE": "消息轨迹",
|
||||
"OPERATION": "操作",
|
||||
"ADD": "新增",
|
||||
"UPDATE": "更新",
|
||||
@@ -89,7 +89,7 @@ export const translations = {
|
||||
"CONFIG": "配置",
|
||||
"SEND_MSG": "发送消息",
|
||||
"RESET_CUS_OFFSET": "重置消费位点",
|
||||
"SKIP_MESSAGE_ACCUMULATE":"跳过堆积",
|
||||
"SKIP_MESSAGE_ACCUMULATE": "跳过堆积",
|
||||
"DELETE": "删除",
|
||||
"CHANGE_LANG": "更换语言",
|
||||
"CHANGE_VERSION": "更换版本",
|
||||
@@ -100,73 +100,72 @@ export const translations = {
|
||||
"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":"选择消息轨迹主题",
|
||||
"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":"消息类型",
|
||||
"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": "顺序消息",
|
||||
@@ -294,7 +293,7 @@ export const translations = {
|
||||
"SELECT_TOPIC_PLACEHOLDER": "Please select topic",
|
||||
"MESSAGE_ID_TOPIC_HINT": "Message ID Topic",
|
||||
"TOPIC_ADD": "Add Topic",
|
||||
"SKIP_MESSAGE_ACCUMULATE":"Skip Message Accumulate",
|
||||
"SKIP_MESSAGE_ACCUMULATE": "Skip Message Accumulate",
|
||||
"OPERATION_FAILED": "Operation Failed",
|
||||
"FORM_VALIDATION_FAILED": "Form Validation Failed",
|
||||
"ADD_CONSUMER": "Add Consumer",
|
||||
@@ -325,7 +324,7 @@ export const translations = {
|
||||
"ADDRESS": "Address",
|
||||
"VERSION": "Version",
|
||||
"PRO_MSG_TPS": "Produce Message TPS",
|
||||
"CUS_MSG_TPS": "Consume 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",
|
||||
@@ -335,16 +334,16 @@ export const translations = {
|
||||
"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",
|
||||
"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",
|
||||
@@ -365,73 +364,72 @@ export const translations = {
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
|
@@ -16,15 +16,15 @@
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import { App as AntdApp } from 'antd';
|
||||
import {App as AntdApp} from 'antd';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import {LanguageProvider} from "./i18n/LanguageContext";
|
||||
import {Provider} from "react-redux";
|
||||
@@ -27,17 +27,15 @@ import store from './store';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
|
||||
<LanguageProvider>
|
||||
<React.StrictMode>
|
||||
<AntdApp>
|
||||
<Provider store={store}>
|
||||
<App/>
|
||||
<App/>
|
||||
</Provider>
|
||||
</AntdApp>
|
||||
</React.StrictMode>
|
||||
</LanguageProvider>
|
||||
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
|
@@ -44,7 +44,7 @@ const ConsumerGroupList = () => {
|
||||
const [isAddConfig, setIsAddConfig] = useState(false);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [messageApi, msgContextHolder] = message.useMessage();
|
||||
const [notificationApi,notificationContextHolder] = notification.useNotification();
|
||||
const [notificationApi, notificationContextHolder] = notification.useNotification();
|
||||
|
||||
const [paginationConf, setPaginationConf] = useState({
|
||||
current: 1,
|
||||
@@ -63,9 +63,9 @@ const ConsumerGroupList = () => {
|
||||
const response = await remoteApi.queryConsumerGroupList(false);
|
||||
if (response.status === 0) {
|
||||
setAllConsumerGroupList(response.data);
|
||||
if(currentPage!=null){
|
||||
if (currentPage != null) {
|
||||
filterList(currentPage, response.data);
|
||||
}else{
|
||||
} else {
|
||||
filterList(1, response.data);
|
||||
}
|
||||
} else {
|
||||
@@ -380,7 +380,7 @@ const ConsumerGroupList = () => {
|
||||
filterList(pagination.current, allConsumerGroupList);
|
||||
};
|
||||
|
||||
const closeConfigModal = () =>{
|
||||
const closeConfigModal = () => {
|
||||
setShowConfig(false);
|
||||
setIsAddConfig(false);
|
||||
}
|
||||
|
@@ -16,17 +16,17 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Form, Input, Button, message, Typography } from 'antd';
|
||||
import {Button, Form, Input, message, Typography} from 'antd';
|
||||
import {remoteApi} from "../../api/remoteApi/remoteApi";
|
||||
|
||||
const { Title } = Typography;
|
||||
const {Title} = Typography;
|
||||
|
||||
const Login = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [messageApi, msgContextHolder] = message.useMessage();
|
||||
|
||||
const onFinish = async (values) => {
|
||||
const { username, password } = values;
|
||||
const {username, password} = values;
|
||||
remoteApi.login(username, password).then((res) => {
|
||||
if (res.status === 0) {
|
||||
messageApi.success('登录成功');
|
||||
|
@@ -15,12 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Select, Button, Switch, Input, Typography, Space, message } from 'antd';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Input, message, Select, Space, Switch, Typography} from 'antd';
|
||||
import {remoteApi} from '../../api/remoteApi/remoteApi';
|
||||
|
||||
const { Title } = Typography;
|
||||
const { Option } = Select;
|
||||
const {Title} = Typography;
|
||||
const {Option} = Select;
|
||||
|
||||
const Ops = () => {
|
||||
const [namesrvAddrList, setNamesrvAddrList] = useState([]);
|
||||
|
@@ -15,16 +15,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, Button, Select, Input, Card, Row, Col, notification, Spin } from 'antd';
|
||||
import { useLanguage } from '../../i18n/LanguageContext';
|
||||
import { remoteApi } from "../../api/remoteApi/remoteApi";
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Card, Col, Input, Modal, notification, Row, Select, Spin} from 'antd';
|
||||
import {useLanguage} from '../../i18n/LanguageContext';
|
||||
import {remoteApi} from "../../api/remoteApi/remoteApi";
|
||||
|
||||
|
||||
const { Option } = Select;
|
||||
const {Option} = Select;
|
||||
|
||||
const ProxyManager = () => {
|
||||
const { t } = useLanguage();
|
||||
const {t} = useLanguage();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [proxyAddrList, setProxyAddrList] = useState([]);
|
||||
@@ -47,7 +47,7 @@ const ProxyManager = () => {
|
||||
remoteApi.queryProxyHomePage((resp) => {
|
||||
setLoading(false);
|
||||
if (resp.status === 0) {
|
||||
const { proxyAddrList, currentProxyAddr } = resp.data;
|
||||
const {proxyAddrList, currentProxyAddr} = resp.data;
|
||||
setProxyAddrList(proxyAddrList || []);
|
||||
setSelectedProxy(currentProxyAddr || (proxyAddrList && proxyAddrList.length > 0 ? proxyAddrList[0] : ''));
|
||||
|
||||
@@ -58,7 +58,7 @@ const ProxyManager = () => {
|
||||
}
|
||||
|
||||
} else {
|
||||
notificationApi.error({ message: resp.errMsg || t.FETCH_PROXY_LIST_FAILED, duration: 2 });
|
||||
notificationApi.error({message: resp.errMsg || t.FETCH_PROXY_LIST_FAILED, duration: 2});
|
||||
}
|
||||
});
|
||||
}, [t]);
|
||||
@@ -71,7 +71,10 @@ const ProxyManager = () => {
|
||||
|
||||
const handleAddProxyAddr = () => {
|
||||
if (!newProxyAddr.trim()) {
|
||||
notificationApi.warning({ message: t.INPUT_PROXY_ADDR_REQUIRED || "Please input a new proxy address.", duration: 2 });
|
||||
notificationApi.warning({
|
||||
message: t.INPUT_PROXY_ADDR_REQUIRED || "Please input a new proxy address.",
|
||||
duration: 2
|
||||
});
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
@@ -82,28 +85,28 @@ const ProxyManager = () => {
|
||||
setProxyAddrList(prevList => [...prevList, newProxyAddr.trim()]);
|
||||
}
|
||||
setNewProxyAddr('');
|
||||
notificationApi.info({ message: t.SUCCESS || "SUCCESS", duration: 2 });
|
||||
notificationApi.info({message: t.SUCCESS || "SUCCESS", duration: 2});
|
||||
} else {
|
||||
notificationApi.error({ message: resp.errMsg || t.ADD_PROXY_FAILED, duration: 2 });
|
||||
notificationApi.error({message: resp.errMsg || t.ADD_PROXY_FAILED, duration: 2});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Spin spinning={loading} tip={t.LOADING}>
|
||||
<div className="container-fluid" style={{ padding: '24px' }} id="deployHistoryList">
|
||||
<div className="container-fluid" style={{padding: '24px'}} id="deployHistoryList">
|
||||
<Card
|
||||
title={
|
||||
<div style={{ fontSize: '20px', fontWeight: 'bold' }}>
|
||||
<div style={{fontSize: '20px', fontWeight: 'bold'}}>
|
||||
ProxyServerAddressList
|
||||
</div>
|
||||
}
|
||||
bordered={false}
|
||||
>
|
||||
<Row gutter={[16, 16]} align="middle">
|
||||
<Col flex="auto" style={{ minWidth: 300, maxWidth: 500 }}>
|
||||
<Col flex="auto" style={{minWidth: 300, maxWidth: 500}}>
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
style={{width: '100%'}}
|
||||
value={selectedProxy}
|
||||
onChange={handleSelectChange}
|
||||
placeholder={t.SELECT}
|
||||
@@ -122,14 +125,14 @@ const ProxyManager = () => {
|
||||
</Row>
|
||||
|
||||
{writeOperationEnabled && (
|
||||
<Row gutter={[16, 16]} align="middle" style={{ marginTop: 16 }}>
|
||||
<Row gutter={[16, 16]} align="middle" style={{marginTop: 16}}>
|
||||
<Col>
|
||||
<label htmlFor="newProxyAddrInput">ProxyAddr:</label>
|
||||
</Col>
|
||||
<Col>
|
||||
<Input
|
||||
id="newProxyAddrInput"
|
||||
style={{ width: 300 }}
|
||||
style={{width: 300}}
|
||||
value={newProxyAddr}
|
||||
onChange={(e) => setNewProxyAddr(e.target.value)}
|
||||
placeholder={t.INPUT_PROXY_ADDR}
|
||||
@@ -149,25 +152,26 @@ const ProxyManager = () => {
|
||||
onCancel={() => setShowModal(false)}
|
||||
title={`${t.PROXY_CONFIG} [${selectedProxy}]`}
|
||||
footer={
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<Button onClick={() => setShowModal(false)}>{t.CLOSE}</Button>
|
||||
</div>
|
||||
}
|
||||
width={800}
|
||||
bodyStyle={{ maxHeight: '60vh', overflowY: 'auto' }}
|
||||
bodyStyle={{maxHeight: '60vh', overflowY: 'auto'}}
|
||||
>
|
||||
<table className="table table-bordered" style={{ width: '100%' }}>
|
||||
<table className="table table-bordered" style={{width: '100%'}}>
|
||||
<tbody>
|
||||
{Object.entries(allProxyConfig).length > 0 ? (
|
||||
Object.entries(allProxyConfig).map(([key, value]) => (
|
||||
<tr key={key}>
|
||||
<td style={{ fontWeight: 500, width: '30%' }}>{key}</td>
|
||||
<td style={{fontWeight: 500, width: '30%'}}>{key}</td>
|
||||
<td>{value}</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="2" style={{ textAlign: 'center' }}>{t.NO_CONFIG_DATA || "No configuration data available."}</td>
|
||||
<td colSpan="2"
|
||||
style={{textAlign: 'center'}}>{t.NO_CONFIG_DATA || "No configuration data available."}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
|
@@ -173,7 +173,6 @@ const DeployHistoryList = () => {
|
||||
};
|
||||
|
||||
|
||||
|
||||
const filterList = (currentPage) => {
|
||||
const lowExceptStr = filterStr.toLowerCase();
|
||||
const canShowList = allTopicList.filter((topic, index) => {
|
||||
@@ -223,7 +222,7 @@ const DeployHistoryList = () => {
|
||||
|
||||
try {
|
||||
if (isUpdate) {
|
||||
// topic 已经是字符串
|
||||
// topic 已经是字符串
|
||||
const configResult = await remoteApi.getTopicConfig(topic);
|
||||
if (configResult.status === 0) {
|
||||
const dataToSet = Array.isArray(configResult.data) ? configResult.data : [configResult.data];
|
||||
@@ -257,7 +256,7 @@ const DeployHistoryList = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!isUpdate){
|
||||
if (!isUpdate) {
|
||||
const clusterResult = await remoteApi.getClusterList();
|
||||
if (clusterResult.status === 0) {
|
||||
setAllClusterNameList(Object.keys(clusterResult.data.clusterInfo.clusterAddrTable));
|
||||
@@ -276,7 +275,7 @@ const DeployHistoryList = () => {
|
||||
if (result.status === 0) {
|
||||
messageApi.success(t.TOPIC_OPERATION_SUCCESS);
|
||||
closeAddUpdateDialog();
|
||||
if(!isUpdateMode) {
|
||||
if (!isUpdateMode) {
|
||||
await getTopicList()
|
||||
}
|
||||
} else {
|
||||
|
@@ -16,15 +16,15 @@
|
||||
*/
|
||||
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
|
@@ -64,7 +64,7 @@ const AppRouter = () => {
|
||||
|
||||
useEffect(() => {
|
||||
remoteApi.setRedirectHandler(() => {
|
||||
navigate('/login', { replace: true });
|
||||
navigate('/login', {replace: true});
|
||||
});
|
||||
}, [navigate]);
|
||||
|
||||
|
@@ -14,10 +14,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useEffect } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { themes, defaultTheme } from '../../assets/styles/theme';
|
||||
import { setTheme } from '../actions/themeActions';
|
||||
import {useEffect} from 'react';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
import {defaultTheme, themes} from '../../assets/styles/theme';
|
||||
import {setTheme} from '../actions/themeActions';
|
||||
|
||||
export const useTheme = () => {
|
||||
// 从 Redux store 中取出 currentThemeName
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createStore,combineReducers } from 'redux';
|
||||
import {combineReducers, createStore} from 'redux';
|
||||
import themeReducer from './reducers/themeReducer';
|
||||
|
||||
// 组合所有的 reducers
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SET_THEME } from '../actions/themeActions';
|
||||
import {SET_THEME} from '../actions/themeActions';
|
||||
|
||||
const getInitialTheme = () => {
|
||||
return localStorage.getItem('appTheme') || 'default';
|
||||
|
Reference in New Issue
Block a user