You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rocketmq.apache.org by ji...@apache.org on 2025/06/16 05:51:22 UTC

(rocketmq-dashboard) branch master updated: [GSOC][RIP-78][ISSUES#308] Add part of refactored front-end files (#312)

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

jinrongtong pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new 3cbff60  [GSOC][RIP-78][ISSUES#308] Add part of refactored front-end files (#312)
3cbff60 is described below

commit 3cbff604e6a552662a472b921ba033fed012a357
Author: Crazylychee <11...@users.noreply.github.com>
AuthorDate: Mon Jun 16 13:51:18 2025 +0800

    [GSOC][RIP-78][ISSUES#308] Add part of refactored front-end files (#312)
---
 frontend-new/src/pages/Acl/acl.jsx                 | 676 ++++++++++++++++++++
 frontend-new/src/pages/Cluster/cluster.jsx         | 303 +++++++++
 frontend-new/src/pages/Consumer/consumer.jsx       | 480 ++++++++++++++
 frontend-new/src/pages/Dashboard/DashboardPage.jsx | 455 +++++++++++++
 frontend-new/src/pages/DlqMessage/dlqmessage.jsx   | 703 +++++++++++++++++++++
 frontend-new/src/pages/Login/login.jsx             |  90 +++
 frontend-new/src/pages/Message/message.jsx         | 478 ++++++++++++++
 .../src/pages/MessageTrace/messagetrace.jsx        | 429 +++++++++++++
 frontend-new/src/pages/Ops/ops.jsx                 | 183 ++++++
 9 files changed, 3797 insertions(+)

diff --git a/frontend-new/src/pages/Acl/acl.jsx b/frontend-new/src/pages/Acl/acl.jsx
new file mode 100644
index 0000000..06aa87a
--- /dev/null
+++ b/frontend-new/src/pages/Acl/acl.jsx
@@ -0,0 +1,676 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState, useEffect } from 'react';
+import {
+    Table,
+    Button,
+    Input,
+    Tabs,
+    Modal,
+    Form,
+    message,
+    Space,
+    Tag,
+    Popconfirm,
+    Select
+} from 'antd';
+import {
+    EditOutlined,
+    DeleteOutlined,
+    EyeOutlined,
+    EyeInvisibleOutlined
+} from '@ant-design/icons';
+import {remoteApi} from "../../api/remoteApi/remoteApi";
+import ResourceInput from '../../components/acl/ResourceInput';
+import SubjectInput from "../../components/acl/SubjectInput";
+import {useLanguage} from "../../i18n/LanguageContext";
+const { TabPane } = Tabs;
+const { Search } = Input;
+
+const Acl = () => {
+    const [activeTab, setActiveTab] = useState('users');
+    const [userListData, setUserListData] = useState([]);
+    const [aclListData, setAclListData] = useState([]);
+    const [loading, setLoading] = useState(false);
+    const [searchText, setSearchText] = useState('');
+
+    const [isUserModalVisible, setIsUserModalVisible] = useState(false);
+    const [currentUser, setCurrentUser] = useState(null);
+    const [userForm] = Form.useForm();
+    const [showPassword, setShowPassword] = useState(false);
+
+    const [isAclModalVisible, setIsAclModalVisible] = useState(false);
+    const [currentAcl, setCurrentAcl] = useState(null);
+    const [aclForm] = Form.useForm();
+    const [messageApi, msgContextHolder] = message.useMessage();
+    const [isUpdate, setIsUpdate] = useState(false);
+    const [ips, setIps] = useState([]);
+    const {t} = useLanguage();
+    // 校验IP地址的正则表达式
+    const ipRegex =
+        /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^((?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}|(?:[0-9A-Fa-f]{1,4}:){6}[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,1}|(?:[0-9A-Fa-f]{1,4}:){5}[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,2}|(?:[0-9A-Fa-f]{1,4}:){4}[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,3}|(?:[0-9A-Fa-f]{1,4}:){3}[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,4}|(?:[0-9A-Fa-f]{1,4}:){2}[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,5}|(?:[0-9A-Fa-f [...]
+    // 支持 IPv4 和 IPv6,包括 CIDR 表示法
+
+    const handleIpChange = value => {
+        // 过滤掉重复的IP地址
+        const uniqueIps = Array.from(new Set(value));
+        setIps(uniqueIps);
+    };
+
+    const handleIpDeselect = value => {
+        // 移除被取消选择的IP
+        setIps(ips.filter(ip => ip !== value));
+    };
+
+    const validateIp = (rule, value) => {
+        if (!value || value.length === 0) {
+            return Promise.resolve(); // Allow empty
+        }
+        const invalidIps = value.filter(ip => !ipRegex.test(ip));
+        if (invalidIps.length > 0) {
+            return Promise.reject(t.INVALID_IP_ADDRESSES +"ips:" + invalidIps.join(', '));
+        }
+        return Promise.resolve();
+    };
+
+// --- Data Loading Functions ---
+    const fetchUsers = async () => {
+        setLoading(true);
+        try {
+            const result = await remoteApi.listUsers();
+            if (result && result.status === 0 && result.data) {
+                const formattedUsers = result.data.map(user => ({
+                    ...user,
+                    key: user.username, // Table needs key
+                    userStatus: user.userStatus === 'enable' ? t.ENABLED : t.DISABLED // Format status
+                }));
+                setUserListData(formattedUsers);
+            } else {
+                messageApi.error(t.GET_USERS_FAILED+result?.errMsg);
+            }
+        } catch (error) {
+            console.error("Failed to fetch users:", error);
+            messageApi.error(t.GET_USERS_EXCEPTION);
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const fetchAcls = async (value) => {
+        setLoading(true);
+        try {
+            const result = await remoteApi.listAcls(null, value);
+            if (result && result.status === 0) {
+                const formattedAcls = [];
+
+                if (result && result.data && Array.isArray(result.data)) {
+                    result.data.forEach((acl, aclIndex) => {
+                        const subject = acl.subject;
+
+                        if (acl.policies && Array.isArray(acl.policies)) {
+                            acl.policies.forEach((policy, policyIndex) => {
+                                const policyType = policy.policyType;
+
+                                if (policy.entries && Array.isArray(policy.entries)) {
+                                    policy.entries.forEach((entry, entryIndex) => {
+                                        const resources = Array.isArray(entry.resource) ? entry.resource : (entry.resource ? [entry.resource] : []);
+
+                                        resources.forEach((singleResource, resourceIndex) => {
+                                            console.log(singleResource)
+                                            formattedAcls.push({
+                                                key: `acl-${aclIndex}-policy-${policyIndex}-entry-${entryIndex}-resource-${singleResource}`,
+                                                subject: subject,
+                                                policyType: policyType,
+                                                resource: singleResource || t.N_A,
+                                                actions: (entry.actions && Array.isArray(entry.actions)) ? entry.actions.join(', ') : '',
+                                                sourceIps: (entry.sourceIps && Array.isArray(entry.sourceIps)) ? entry.sourceIps.join(', ') : t.N_A,
+                                                decision: entry.decision || t.N_A
+                                            });
+                                        });
+                                    });
+                                }
+                            });
+                        }
+                    });
+                } else {
+                    console.warn(t.INVALID_OR_EMPTY_ACL_DATA);
+                }
+                setAclListData(formattedAcls);
+            } else {
+                messageApi.error(t.GET_ACLS_FAILED + result?.errMsg);
+            }
+        } catch (error) {
+            console.error("Failed to fetch ACLs:", error);
+            messageApi.error(t.GET_ACLS_EXCEPTION);
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    useEffect(() => {
+        if (activeTab === 'users') {
+            fetchUsers();
+        } else {
+            fetchAcls();
+        }
+    }, [activeTab]);
+
+// --- User Management Logic ---
+
+    const handleAddUser = () => {
+        setCurrentUser(null);
+        userForm.resetFields();
+        setShowPassword(false);
+        setIsUserModalVisible(true);
+    };
+
+    const handleEditUser = (record) => {
+        setCurrentUser(record);
+        userForm.setFieldsValue({
+            username: record.username,
+            password: record.password,
+            userType: record.userType,
+            userStatus: record.userStatus === t.ENABLED ? 'enable' : 'disable'
+        });
+        setShowPassword(false);
+        setIsUserModalVisible(true);
+    };
+
+    const handleDeleteUser = async (username) => {
+        setLoading(true);
+        try {
+            const result = await remoteApi.deleteUser(null, username);
+            if (result.status === 0) {
+                messageApi.success(t.USER_DELETE_SUCCESS);
+                fetchUsers();
+            } else {
+                messageApi.error(t.USER_DELETE_FAILED + result.errMsg);
+            }
+        } catch (error) {
+            console.error("Failed to delete user:", error);
+            messageApi.error(t.USER_DELETE_EXCEPTION);
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handleUserModalOk = async () => {
+        try {
+            const values = await userForm.validateFields();
+            setLoading(true);
+            let result;
+
+            const userInfoParam = {
+                username: values.username,
+                password: values.password,
+                userType: values.userType,
+                userStatus: values.userStatus,
+            };
+
+            if (currentUser) {
+                result = await remoteApi.updateUser(null, userInfoParam);
+                if (result.status === 0) {
+                    messageApi.success(t.USER_UPDATE_SUCCESS);
+                } else {
+                    messageApi.error(result.errMsg);
+                }
+            } else {
+                result = await remoteApi.createUser(null, userInfoParam);
+                if (result.status === 0) {
+                    messageApi.success(t.USER_CREATE_SUCCESS);
+                } else {
+                    messageApi.error(result.errMsg);
+                }
+            }
+            setIsUserModalVisible(false);
+            fetchUsers();
+        } catch (error) {
+            console.error("Failed to save user:", error);
+            messageApi.error(t.SAVE_USER_FAILED);
+        } finally {
+            setLoading(false);
+        }
+    };
+
+// --- ACL Permission Management Logic ---
+    const handleAddAcl = () => {
+        setCurrentAcl(null);
+        setIsUpdate(false)
+        aclForm.resetFields();
+        setIsAclModalVisible(true);
+    };
+
+    const handleEditAcl = (record) => {
+        setCurrentAcl(record);
+        setIsUpdate(true);
+        aclForm.setFieldsValue({
+            subject: record.subject,
+            policyType: record.policyType,
+            resource: record.resource,
+            actions: record.actions ? record.actions.split(', ') : [],
+            sourceIps: record.sourceIps ? record.sourceIps.split(', ') : [],
+            decision: record.decision
+        });
+        setIsAclModalVisible(true);
+    };
+
+    const handleDeleteAcl = async (subject, resource) => {
+        setLoading(true);
+        try {
+            const result = await remoteApi.deleteAcl(null, subject, resource);
+            if (result.status === 0) {
+                messageApi.success(t.ACL_DELETE_SUCCESS);
+                fetchAcls();
+            } else {
+                messageApi.error(t.ACL_DELETE_FAILED+result.errMsg);
+            }
+        } catch (error) {
+            console.error("Failed to delete ACL:", error);
+            messageApi.error(t.ACL_DELETE_EXCEPTION);
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handleAclModalOk = async () => {
+        try {
+            const values = await aclForm.validateFields();
+            setLoading(true);
+            let result;
+
+            const policiesParam = [
+                {
+                    policyType: values.policyType,
+                    entries: [
+                        {
+                            resource: isUpdate ? [values.resource] : values.resource,
+                            actions: values.actions,
+                            sourceIps: values.sourceIps,
+                            decision: values.decision
+                        }
+                    ]
+                }
+            ];
+
+            if (isUpdate) { // This condition seems reversed for update/create based on the current logic.
+                result = await remoteApi.updateAcl(null, values.subject, policiesParam);
+                if (result.status === 0) {
+                    messageApi.success(t.ACL_UPDATE_SUCCESS);
+                    setIsAclModalVisible(false);
+                    fetchAcls();
+                } else {
+                    messageApi.error(t.ACL_UPDATE_FAILED+result.errMsg);
+                }
+                setIsUpdate(false)
+            } else {
+                result = await remoteApi.createAcl(null, values.subject, policiesParam);
+                console.log(result)
+                if (result.status === 0) {
+                    messageApi.success(t.ACL_CREATE_SUCCESS);
+                    setIsAclModalVisible(false);
+                    fetchAcls();
+                } else {
+                    messageApi.error(t.ACL_CREATE_FAILED+result.errMsg);
+                }
+            }
+
+        } catch (error) {
+            console.error("Failed to save ACL:", error);
+            messageApi.error(t.SAVE_ACL_FAILED);
+        } finally {
+            setLoading(false);
+        }
+    };
+
+// --- Search Functionality ---
+
+    const handleSearch = (value) => {
+        if (activeTab === 'users') {
+            const filteredData = userListData.filter(item =>
+                Object.values(item).some(val =>
+                    String(val).toLowerCase().includes(value.toLowerCase())
+                )
+            );
+            if (value === '') {
+                fetchUsers();
+            } else {
+                setUserListData(filteredData);
+            }
+        } else {
+            fetchAcls(value);
+        }
+    };
+
+
+    // --- User Table Column Definitions ---
+    const userColumns = [
+        {
+            title: t.USERNAME,
+            dataIndex: 'username',
+            key: 'username',
+        },
+        {
+            title: t.PASSWORD,
+            dataIndex: 'password',
+            key: 'password',
+            render: (text) => (
+                <span>
+                {showPassword ? text : '********'}
+                    <Button
+                        type="link"
+                        icon={showPassword ? <EyeInvisibleOutlined /> : <EyeOutlined />}
+                        onClick={() => setShowPassword(!showPassword)}
+                        style={{ marginLeft: 8 }}
+                    >
+                    {showPassword ? t.HIDE : t.VIEW}
+                </Button>
+            </span>
+            ),
+        },
+        {
+            title: t.USER_TYPE,
+            dataIndex: 'userType',
+            key: 'userType',
+        },
+        {
+            title: t.USER_STATUS,
+            dataIndex: 'userStatus',
+            key: 'userStatus',
+            render: (status) => (
+                <Tag color={status=== 'enable' ? 'red' : 'green'}>{status}</Tag>
+            ),
+        },
+        {
+            title: t.OPERATION,
+            key: 'action',
+            render: (_, record) => (
+                <Space size="middle">
+                    <Button icon={<EditOutlined />} onClick={() => handleEditUser(record)}>{t.MODIFY}</Button>
+                    <Popconfirm
+                        title={t.CONFIRM_DELETE_USER}
+                        onConfirm={() => handleDeleteUser(record.username)}
+                        okText={t.YES}
+                        cancelText={t.NO}
+                    >
+                        <Button icon={<DeleteOutlined />} danger>{t.DELETE}</Button>
+                    </Popconfirm>
+                </Space>
+            ),
+        },
+    ];
+
+// --- ACL Permission Table Column Definitions ---
+    const aclColumns = [
+        {
+            title: t.USERNAME_SUBJECT,
+            dataIndex: 'subject',
+            key: 'subject',
+        },
+        {
+            title: t.POLICY_TYPE,
+            dataIndex: 'policyType',
+            key: 'policyType',
+        },
+        {
+            title: t.RESOURCE_NAME,
+            dataIndex: 'resource',
+            key: 'resource',
+        },
+        {
+            title: t.OPERATION_TYPE,
+            dataIndex: 'actions',
+            key: 'actions',
+            render: (text) => text ? text.split(', ').map((action, index) => (
+                <Tag key={index} color="blue">{action}</Tag>
+            )) : null,
+        },
+        {
+            title: t.SOURCE_IP,
+            dataIndex: 'sourceIps',
+            key: 'sourceIps',
+        },
+        {
+            title: t.DECISION,
+            dataIndex: 'decision',
+            key: 'decision',
+            render: (text) => (
+                <Tag color={text === 'Allow' ? 'green' : 'red'}>{text}</Tag>
+            ),
+        },
+        {
+            title: t.OPERATION,
+            key: 'action',
+            render: (_, record) => (
+                <Space size="middle">
+                    <Button icon={<EditOutlined />} onClick={() => handleEditAcl(record)}>{t.MODIFY}</Button>
+                    <Popconfirm
+                        title={t.CONFIRM_DELETE_ACL}
+                        onConfirm={() => handleDeleteAcl(record.subject, record.resource)}
+                        okText={t.YES}
+                        cancelText={t.NO}
+                    >
+                        <Button icon={<DeleteOutlined />} danger>{t.DELETE}</Button>
+                    </Popconfirm>
+                </Space>
+            ),
+        },
+    ];
+
+return (
+    <>
+        {msgContextHolder}
+        <div style={{ padding: 24 }}>
+            <h2>{t.ACL_MANAGEMENT}</h2>
+            <Tabs activeKey={activeTab} onChange={setActiveTab}>
+                <TabPane tab={t.ACL_USERS} key="users" />
+                <TabPane tab={t.ACL_PERMISSIONS} key="acls" />
+            </Tabs>
+
+            <div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
+                <Button type="primary" onClick={activeTab === 'users' ? handleAddUser : handleAddAcl}>
+                    {activeTab === 'users' ? t.ADD_USER : t.ADD_ACL_PERMISSION}
+                </Button>
+                <Search
+                    placeholder={t.SEARCH_PLACEHOLDER}
+                    allowClear
+                    onSearch={handleSearch}
+                    style={{ width: 300 }}
+                />
+            </div>
+
+            {activeTab === 'users' && (
+                <Table
+                    columns={userColumns}
+                    dataSource={userListData}
+                    loading={loading}
+                    pagination={{ pageSize: 10 }}
+                    rowKey="username"
+                />
+            )}
+
+            {activeTab === 'acls' && (
+                <Table
+                    columns={aclColumns}
+                    dataSource={aclListData}
+                    loading={loading}
+                    pagination={{ pageSize: 10 }}
+                    rowKey="key"
+                />
+            )}
+
+            {/* User Management Modal */}
+            <Modal
+                title={currentUser ? t.EDIT_USER : t.ADD_USER}
+                visible={isUserModalVisible}
+                onOk={handleUserModalOk}
+                onCancel={() => setIsUserModalVisible(false)}
+                confirmLoading={loading}
+                footer={[
+                    <Button key="cancel" onClick={() => setIsUserModalVisible(false)}>
+                        {t.CANCEL}
+                    </Button>,
+                    <Button key="submit" type="primary" onClick={handleUserModalOk} loading={loading}>
+                        {t.CONFIRM}
+                    </Button>,
+                ]}
+            >
+                <Form
+                    form={userForm}
+                    layout="vertical"
+                    name="user_form"
+                    initialValues={{ userStatus: 'enable' }}
+                >
+                    <Form.Item
+                        name="username"
+                        label={t.USERNAME}
+                        rules={[{ required: true, message: t.PLEASE_ENTER_USERNAME }]}
+                    >
+                        <Input disabled={!!currentUser} />
+                    </Form.Item>
+                    <Form.Item
+                        name="password"
+                        label={t.PASSWORD}
+                        rules={[{ required: !currentUser, message: t.PLEASE_ENTER_PASSWORD }]}
+                    >
+                        <Input.Password
+                            placeholder={t.PASSWORD}
+                            iconRender={visible => (visible ? <EyeOutlined /> : <EyeInvisibleOutlined />)}
+                        />
+                    </Form.Item>
+                    <Form.Item
+                        name="userType"
+                        label={t.USER_TYPE}
+                        rules={[{ required: true, message: t.PLEASE_SELECT_USER_TYPE }]}
+                    >
+                        <Select mode="single" placeholder="Super, Normal" style={{ width: '100%' }}>
+                            <Select.Option value="Super">Super</Select.Option>
+                            <Select.Option value="Normal">Normal</Select.Option>
+                        </Select>
+                    </Form.Item>
+                    <Form.Item
+                        name="userStatus"
+                        label={t.USER_STATUS}
+                        rules={[{ required: true, message: t.PLEASE_SELECT_USER_STATUS }]}
+                    >
+                        <Select mode="single" placeholder="enable, disable" style={{ width: '100%' }}>
+                            <Select.Option value="enable">enable</Select.Option>
+                            <Select.Option value="disable">disable</Select.Option>
+                        </Select>
+                    </Form.Item>
+                </Form>
+            </Modal>
+
+            {/* ACL Permission Management Modal */}
+            <Modal
+                title={currentAcl ? t.EDIT_ACL_PERMISSION : t.ADD_ACL_PERMISSION}
+                visible={isAclModalVisible}
+                onOk={handleAclModalOk}
+                onCancel={() => setIsAclModalVisible(false)}
+                confirmLoading={loading}
+            >
+                <Form
+                    form={aclForm}
+                    layout="vertical"
+                    name="acl_form"
+                >
+                    <Form.Item
+                        name="subject"
+                        label={t.SUBJECT_LABEL}
+                        rules={[{ required: true, message: t.PLEASE_ENTER_SUBJECT }]}
+                    >
+                        <SubjectInput disabled={!!currentAcl} />
+                    </Form.Item>
+
+                    <Form.Item
+                        name="policyType"
+                        label={t.POLICY_TYPE}
+                        rules={[{ required: true, message: t.PLEASE_ENTER_POLICY_TYPE }]}
+                    >
+                        <Select mode="single" disabled={isUpdate} placeholder="policyType" style={{ width: '100%' }}>
+                            <Select.Option value="Custom">Custom</Select.Option>
+                            <Select.Option value="Default">Default</Select.Option>
+                        </Select>
+                    </Form.Item>
+
+                    <Form.Item
+                        name="resource"
+                        label={t.RESOURCE}
+                        rules={[{ required: true, message: t.PLEASE_ADD_RESOURCE }]}
+                    >
+                        {isUpdate ? (
+                            <Input disabled={isUpdate} />
+                        ) : (
+                            <ResourceInput />
+                        )}
+                    </Form.Item>
+
+                    <Form.Item
+                        name="actions"
+                        label={t.OPERATION_TYPE}
+                    >
+                        <Select mode="multiple" placeholder="action" style={{ width: '100%' }}>
+                            <Select.Option value="All">All</Select.Option>
+                            <Select.Option value="Pub">Pub</Select.Option>
+                            <Select.Option value="Sub">Sub</Select.Option>
+                            <Select.Option value="Create">Create</Select.Option>
+                            <Select.Option value="Update">Update</Select.Option>
+                            <Select.Option value="Delete">Delete</Select.Option>
+                            <Select.Option value="Get">Get</Select.Option>
+                            <Select.Option value="List">List</Select.Option>
+                        </Select>
+                    </Form.Item>
+                    <Form.Item
+                        name="sourceIps"
+                        label={t.SOURCE_IP}
+                        rules={[
+                            {
+                                validator: validateIp,
+                            },
+                        ]}
+                    >
+                        <Select
+                            mode="tags"
+                            style={{ width: '100%' }}
+                            placeholder={t.ENTER_IP_HINT}
+                            onChange={handleIpChange}
+                            onDeselect={handleIpDeselect}
+                            value={ips}
+                            tokenSeparators={[',', ' ']}
+                        >
+                            <Select.Option value="192.168.1.1">192.168.1.1</Select.Option>
+                            <Select.Option value="0.0.0.0">0.0.0.0</Select.Option>
+                            <Select.Option value="127.0.0.1">127.0.0.1</Select.Option>
+                        </Select>
+                    </Form.Item>
+                    <Form.Item
+                        name="decision"
+                        label={t.DECISION}
+                        rules={[{ required: true, message: t.PLEASE_ENTER_DECISION }]}
+                    >
+                        <Select mode="single" placeholder="Allow, Deny" style={{ width: '100%' }}>
+                            <Select.Option value="Allow">Allow</Select.Option>
+                            <Select.Option value="Deny">Deny</Select.Option>
+                        </Select>
+                    </Form.Item>
+                </Form>
+            </Modal>
+        </div>
+    </>
+);}
+
+export default Acl;
diff --git a/frontend-new/src/pages/Cluster/cluster.jsx b/frontend-new/src/pages/Cluster/cluster.jsx
new file mode 100644
index 0000000..26d2c24
--- /dev/null
+++ b/frontend-new/src/pages/Cluster/cluster.jsx
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, {useCallback, useEffect, useState} from 'react';
+import {Button, Modal, notification, Select, Spin, Table} from 'antd';
+import {useLanguage} from "../../i18n/LanguageContext";
+import {remoteApi, tools} from "../../api/remoteApi/remoteApi"; // 确保路径正确
+
+const {Option} = Select;
+
+const Cluster = () => {
+    const {t} = useLanguage();
+
+    const [loading, setLoading] = useState(false);
+    const [clusterNames, setClusterNames] = useState([]);
+    const [selectedCluster, setSelectedCluster] = useState('');
+    const [instances, setInstances] = useState([]);
+    const [allBrokersData, setAllBrokersData] = useState({});
+
+    const [detailModalVisible, setDetailModalVisible] = useState(false);
+    const [configModalVisible, setConfigModalVisible] = useState(false);
+    const [currentDetail, setCurrentDetail] = useState({});
+    const [currentConfig, setCurrentConfig] = useState({});
+    const [currentBrokerName, setCurrentBrokerName] = useState('');
+    const [currentIndex, setCurrentIndex] = useState(null); // 对应 brokerId
+    const [currentBrokerAddress, setCurrentBrokerAddress] = useState('');
+    const [api, contextHolder] = notification.useNotification();
+
+    const switchCluster = useCallback((clusterName) => {
+        if (allBrokersData[clusterName]) {
+            setInstances(allBrokersData[clusterName]);
+        } else {
+            setInstances([]);
+        }
+    }, [allBrokersData]);
+
+    const handleChangeCluster = (value) => {
+        setSelectedCluster(value);
+        switchCluster(value);
+    };
+
+    useEffect(() => {
+        setLoading(true);
+        remoteApi.queryClusterList((resp) => {
+            setLoading(false);
+            if (resp.status === 0) {
+                const {clusterInfo, brokerServer} = resp.data;
+                const {clusterAddrTable, brokerAddrTable} = clusterInfo;
+
+                const generatedBrokers = tools.generateBrokerMap(brokerServer, clusterAddrTable, brokerAddrTable);
+                setAllBrokersData(generatedBrokers);
+
+                const names = Object.keys(clusterAddrTable);
+                setClusterNames(names);
+
+                if (names.length > 0) {
+                    const defaultCluster = names[0];
+                    setSelectedCluster(defaultCluster);
+                    if (generatedBrokers[defaultCluster]) {
+                        setInstances(generatedBrokers[defaultCluster]);
+                    } else {
+                        setInstances([]);
+                    }
+                }
+
+            } else {
+                api.error({message: resp.errMsg || t.QUERY_CLUSTER_LIST_FAILED, duration: 2});
+            }
+        });
+    }, []);
+
+    const showDetail = (brokerName, brokerId, record) => { // 传入 record 整个对象,方便直接显示
+        setCurrentBrokerName(brokerName);
+        setCurrentIndex(brokerId);
+        setCurrentDetail(record); // 直接使用 record 作为详情
+        setDetailModalVisible(true);
+    };
+
+    const showConfig = (brokerAddress, brokerName, brokerId) => { // 保持一致,传入 brokerId
+        setCurrentBrokerName(brokerName);
+        setCurrentIndex(brokerId);
+        setCurrentBrokerAddress(brokerAddress);
+
+        setLoading(true);
+        remoteApi.queryBrokerConfig(brokerAddress, (resp) => {
+            setLoading(false);
+            if (resp.status === 0) {
+                // ✨ 确保 resp.data 是一个对象,如果后端返回的不是对象,这里需要处理
+                if (typeof resp.data === 'object' && resp.data !== null) {
+                    setCurrentConfig(resp.data);
+                    setConfigModalVisible(true);
+                } else {
+                    api.error({message: t.INVALID_CONFIG_DATA || 'Invalid config data received', duration: 2});
+                    setCurrentConfig({}); // 清空配置,避免显示错误
+                }
+            } else {
+                api.error({message: resp.errMsg || t.QUERY_BROKER_CONFIG_FAILED, duration: 2});
+            }
+        });
+    };
+
+    const columns = [
+        {
+            title: t.SPLIT,
+            dataIndex: 'brokerName', // 直接使用 brokerId
+            key: 'split',
+            align: 'center'
+        },
+        {
+            title: t.NO,
+            dataIndex: 'brokerId', // 直接使用 brokerId
+            key: 'no',
+            align: 'center',
+            render: (brokerId) => `${brokerId}${brokerId === 0 ? `(${t.MASTER})` : `(${t.SLAVE})`}`,
+        },
+        {
+            title: t.ADDRESS,
+            dataIndex: 'address', // 确保 generateBrokerMap 返回的数据有 address 字段
+            key: 'address',
+            align: 'center',
+        },
+        {
+            title: t.VERSION,
+            dataIndex: 'brokerVersionDesc',
+            key: 'version',
+            align: 'center',
+        },
+        {
+            title: t.PRO_MSG_TPS,
+            dataIndex: 'putTps',
+            key: 'putTps',
+            align: 'center',
+            render: (text) => {
+                const tpsValue = text ? Number(String(text).split(' ')[0]) : 0; // 确保text是字符串
+                return tpsValue.toFixed(2);
+            },
+        },
+        {
+            title: t.CUS_MSG_TPS,
+            key: 'cusMsgTps',
+            align: 'center',
+            render: (_, record) => {
+                // 根据你提供的数据结构,这里可能是 getTransferredTps
+                const val = record.getTransferedTps?.trim() ? record.getTransferedTps : record.getTransferredTps;
+                const tpsValue = val ? Number(String(val).split(' ')[0]) : 0; // 确保val是字符串
+                return tpsValue.toFixed(2);
+            },
+        },
+        {
+            title: t.YESTERDAY_PRO_COUNT,
+            key: 'yesterdayProCount',
+            align: 'center',
+            render: (_, record) => {
+                const putTotalTodayMorning = parseFloat(record.msgPutTotalTodayMorning || 0);
+                const putTotalYesterdayMorning = parseFloat(record.msgPutTotalYesterdayMorning || 0);
+                return (putTotalTodayMorning - putTotalYesterdayMorning).toLocaleString();
+            }
+        },
+        {
+            title: t.YESTERDAY_CUS_COUNT,
+            key: 'yesterdayCusCount',
+            align: 'center',
+            render: (_, record) => {
+                const getTotalTodayMorning = parseFloat(record.msgGetTotalTodayMorning || 0);
+                const getTotalYesterdayMorning = parseFloat(record.msgGetTotalYesterdayMorning || 0);
+                return (getTotalTodayMorning - getTotalYesterdayMorning).toLocaleString();
+            }
+        },
+        {
+            title: t.TODAY_PRO_COUNT,
+            key: 'todayProCount',
+            align: 'center',
+            render: (_, record) => {
+                const putTotalTodayNow = parseFloat(record.msgPutTotalTodayNow || 0);
+                const putTotalTodayMorning = parseFloat(record.msgPutTotalTodayMorning || 0);
+                return (putTotalTodayNow - putTotalTodayMorning).toLocaleString();
+            }
+        },
+        {
+            title: t.TODAY_CUS_COUNT,
+            key: 'todayCusCount',
+            align: 'center',
+            render: (_, record) => {
+                const getTotalTodayNow = parseFloat(record.msgGetTotalTodayNow || 0);
+                const getTotalTodayMorning = parseFloat(record.msgGetTotalTodayMorning || 0);
+                return (getTotalTodayNow - getTotalTodayMorning).toLocaleString();
+            }
+        },
+        {
+            title: t.OPERATION,
+            key: 'operation',
+            align: 'center',
+            render: (_, record) => (
+                <>
+                    <Button size="small" type="primary"
+                            onClick={() => showDetail(record.brokerName, record.brokerId, record)}
+                            style={{marginRight: 8}}>
+                        {t.STATUS}
+                    </Button>
+                    {/* 传入 record.address */}
+                    <Button size="small" type="primary"
+                            onClick={() => showConfig(record.address, record.brokerName, record.brokerId)}>
+                        {t.CONFIG}
+                    </Button>
+                </>
+            ),
+        },
+    ];
+
+    return (
+        <>
+            {contextHolder}
+            <Spin spinning={loading} tip={t.LOADING}>
+                <div style={{padding: 24}}>
+                    <div style={{marginBottom: 16, display: 'flex', alignItems: 'center'}}>
+                        <label style={{marginRight: 8}}>{t.CLUSTER}:</label>
+                        <Select
+                            style={{width: 300}}
+                            placeholder={t.SELECT_CLUSTER || "Please select a cluster"}
+                            value={selectedCluster}
+                            onChange={handleChangeCluster}
+                            allowClear
+                        >
+                            {clusterNames.map((name) => (
+                                <Option key={name} value={name}>
+                                    {name}
+                                </Option>
+                            ))}
+                        </Select>
+                    </div>
+
+                    <Table
+                        dataSource={instances}
+                        columns={columns}
+                        rowKey={(record) => `${record.brokerName}-${record.brokerId}`}
+                        pagination={false}
+                        bordered
+                        size="middle"
+                    />
+
+                    <Modal
+                        title={`${t.BROKER} [${currentBrokerName}][${currentIndex}]`}
+                        open={detailModalVisible}
+                        footer={null}
+                        onCancel={() => setDetailModalVisible(false)}
+                        width={800}
+                        bodyStyle={{maxHeight: '60vh', overflowY: 'auto'}}
+                    >
+                        <Table
+                            dataSource={Object.entries(currentDetail).map(([key, value]) => ({key, value}))}
+                            columns={[
+                                {title: t.KEY || 'Key', dataIndex: 'key', key: 'key'},
+                                {title: t.VALUE || 'Value', dataIndex: 'value', key: 'value'},
+                            ]}
+                            pagination={false}
+                            size="small"
+                            bordered
+                            rowKey="key"
+                        />
+                    </Modal>
+
+                    <Modal
+                        title={`${t.BROKER} [${currentBrokerName}][${currentIndex}]`}
+                        open={configModalVisible}
+                        footer={null}
+                        onCancel={() => setConfigModalVisible(false)}
+                        width={800}
+                        bodyStyle={{maxHeight: '60vh', overflowY: 'auto'}}
+                    >
+                        <Table
+                            dataSource={Object.entries(currentConfig).map(([key, value]) => ({key, value}))}
+                            columns={[
+                                {title: t.KEY || 'Key', dataIndex: 'key', key: 'key'},
+                                {title: t.VALUE || 'Value', dataIndex: 'value', key: 'value'},
+                            ]}
+                            pagination={false}
+                            size="small"
+                            bordered
+                            rowKey="key"
+                        />
+                    </Modal>
+                </div>
+            </Spin>
+        </>
+
+    );
+};
+
+export default Cluster;
diff --git a/frontend-new/src/pages/Consumer/consumer.jsx b/frontend-new/src/pages/Consumer/consumer.jsx
new file mode 100644
index 0000000..d67efa8
--- /dev/null
+++ b/frontend-new/src/pages/Consumer/consumer.jsx
@@ -0,0 +1,480 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, {useCallback, useEffect, useState} from 'react';
+import {Button, Checkbox, Input, message, notification, Spin, Table} from 'antd';
+import {useLanguage} from '../../i18n/LanguageContext';
+import {remoteApi} from '../../api/remoteApi/remoteApi';
+import ClientInfoModal from "../../components/consumer/ClientInfoModal";
+import ConsumerDetailModal from "../../components/consumer/ConsumerDetailModal";
+import ConsumerConfigModal from "../../components/consumer/ConsumerConfigModal";
+import DeleteConsumerModal from "../../components/consumer/DeleteConsumerModal";
+
+const ConsumerGroupList = () => {
+    const {t} = useLanguage();
+    const [filterStr, setFilterStr] = useState('');
+    const [filterNormal, setFilterNormal] = useState(true);
+    const [filterFIFO, setFilterFIFO] = useState(false);
+    const [filterSystem, setFilterSystem] = useState(false);
+    const [rmqVersion, setRmqVersion] = useState(true);
+    const [writeOperationEnabled, setWriteOperationEnabled] = useState(true);
+    const [intervalProcessSwitch, setIntervalProcessSwitch] = useState(false);
+    const [loading, setLoading] = useState(false);
+    const [consumerGroupShowList, setConsumerGroupShowList] = useState([]);
+    const [allConsumerGroupList, setAllConsumerGroupList] = useState([]);
+    const [selectedGroup, setSelectedGroup] = useState(null);
+    const [selectedAddress, setSelectedAddress] = useState(null);
+    const [showClientInfo, setShowClientInfo] = useState(false);
+    const [showConsumeDetail, setShowConsumeDetail] = useState(false);
+    const [showConfig, setShowConfig] = useState(false);
+    const [isAddConfig, setIsAddConfig] = useState(false);
+    const [showDeleteModal, setShowDeleteModal] = useState(false);
+    const [messageApi, msgContextHolder] = message.useMessage();
+    const [notificationApi,notificationContextHolder] = notification.useNotification();
+
+    const [paginationConf, setPaginationConf] = useState({
+        current: 1,
+        pageSize: 10,
+        total: 0,
+    });
+
+    const [sortConfig, setSortConfig] = useState({
+        sortKey: null,
+        sortOrder: 1,
+    });
+
+    const loadConsumerGroups = useCallback(async (currentPage) => {
+        setLoading(true);
+        try {
+            const response = await remoteApi.queryConsumerGroupList(false);
+            if (response.status === 0) {
+                setAllConsumerGroupList(response.data);
+                if(currentPage!=null){
+                    filterList(currentPage, response.data);
+                }else{
+                    filterList(1, response.data);
+                }
+            } else {
+                messageApi.error({title: t.ERROR, content: response.errMsg});
+            }
+        } catch (error) {
+            messageApi.error({title: t.ERROR, content: t.FAILED_TO_FETCH_DATA});
+            console.error("Error loading consumer groups:", error);
+        } finally {
+            setLoading(false);
+        }
+    }, [t]);
+
+    const filterByType = (str, type, version) => {
+        if (filterSystem && type === "SYSTEM") return true;
+        if (filterNormal && (type === "NORMAL" || (!version && type === "FIFO"))) return true;
+        if (filterFIFO && type === "FIFO") return true;
+        return false;
+    };
+
+    const filterList = useCallback((currentPage, data) => {
+        // 排序处理
+        let sortedData = [...data];
+        if (sortConfig.sortKey) {
+            sortedData.sort((a, b) => {
+                const aValue = a[sortConfig.sortKey];
+                const bValue = b[sortConfig.sortKey];
+                if (typeof aValue === 'string') {
+                    return sortConfig.sortOrder * aValue.localeCompare(bValue);
+                }
+                return sortConfig.sortOrder * (aValue > bValue ? 1 : -1);
+            });
+        }
+
+        // 过滤处理
+        const lowExceptStr = filterStr.toLowerCase();
+        const canShowList = sortedData.filter(element =>
+            filterByType(element.group, element.subGroupType, rmqVersion) &&
+            element.group.toLowerCase().includes(lowExceptStr)
+        );
+
+        // 更新分页和显示列表
+        const perPage = paginationConf.pageSize;
+        const from = (currentPage - 1) * perPage;
+        const to = from + perPage;
+
+        setPaginationConf(prev => ({
+            ...prev,
+            current: currentPage,
+            total: canShowList.length,
+        }));
+        setConsumerGroupShowList(canShowList.slice(from, to));
+    }, [filterStr, filterNormal, filterSystem, filterFIFO, rmqVersion, sortConfig, paginationConf.pageSize]);
+
+
+    const doSort = useCallback(() => {
+        const sortedList = [...allConsumerGroupList];
+
+        if (sortConfig.sortKey === 'diffTotal') {
+            sortedList.sort((a, b) => {
+                return (a.diffTotal > b.diffTotal) ? sortConfig.sortOrder :
+                    ((b.diffTotal > a.diffTotal) ? -sortConfig.sortOrder : 0);
+            });
+        }
+        if (sortConfig.sortKey === 'group') {
+            sortedList.sort((a, b) => {
+                return (a.group > b.group) ? sortConfig.sortOrder :
+                    ((b.group > a.group) ? -sortConfig.sortOrder : 0);
+            });
+        }
+        if (sortConfig.sortKey === 'count') {
+            sortedList.sort((a, b) => {
+                return (a.count > b.count) ? sortConfig.sortOrder :
+                    ((b.count > a.count) ? -sortConfig.sortOrder : 0);
+            });
+        }
+        if (sortConfig.sortKey === 'consumeTps') {
+            sortedList.sort((a, b) => {
+                return (a.consumeTps > b.consumeTps) ? sortConfig.sortOrder :
+                    ((b.consumeTps > a.consumeTps) ? -sortConfig.sortOrder : 0);
+            });
+        }
+
+        setAllConsumerGroupList(sortedList);
+        filterList(paginationConf.current, sortedList);
+    }, [sortConfig, allConsumerGroupList, paginationConf.current]);
+
+    useEffect(() => {
+        loadConsumerGroups();
+    }, [loadConsumerGroups]);
+
+    useEffect(() => {
+        let intervalId;
+        if (intervalProcessSwitch) {
+            intervalId = setInterval(loadConsumerGroups, 10000);
+        }
+        return () => clearInterval(intervalId);
+    }, [intervalProcessSwitch, loadConsumerGroups]);
+
+
+    useEffect(() => {
+        filterList(paginationConf.current, allConsumerGroupList);
+    }, [allConsumerGroupList, filterStr, filterNormal, filterSystem, filterFIFO, sortConfig, filterList, paginationConf.current]);
+
+    const handleFilterInputChange = (value) => {
+        setFilterStr(value);
+        setPaginationConf(prev => ({...prev, current: 1}));
+    };
+
+    const handleTypeFilterChange = (filterType, checked) => {
+        switch (filterType) {
+            case 'normal':
+                setFilterNormal(checked);
+                break;
+            case 'fifo':
+                setFilterFIFO(checked);
+                break;
+            case 'system':
+                setFilterSystem(checked);
+                break;
+            default:
+                break;
+        }
+        setPaginationConf(prev => ({...prev, current: 1}));
+    };
+
+    const handleRefreshConsumerData = async () => {
+        setLoading(true);
+        const refreshResult = await remoteApi.refreshAllConsumerGroup();
+        setLoading(false);
+
+        if (refreshResult && refreshResult.status === 0) {
+            notificationApi.success({message: t.REFRESH_SUCCESS, duration: 2});
+            loadConsumerGroups();
+        } else if (refreshResult && refreshResult.errMsg) {
+            notificationApi.error({message: t.REFRESH_FAILED + ": " + refreshResult.errMsg, duration: 2});
+        } else {
+            notificationApi.error({message: t.REFRESH_FAILED, duration: 2});
+        }
+    };
+
+    const handleOpenAddDialog = () => {
+        setIsAddConfig(true)
+        setShowConfig(true);
+    };
+
+    // 修改操作按钮的点击处理函数
+    const handleClient = (group, address) => {
+        setSelectedGroup(group);
+        setSelectedAddress(address);
+        setShowClientInfo(true);
+    };
+
+    const handleDetail = (group, address) => {
+        setSelectedGroup(group);
+        setSelectedAddress(address);
+        setShowConsumeDetail(true);
+    };
+
+    const handleUpdateConfigDialog = (group) => {
+        setSelectedGroup(group);
+        setShowConfig(true);
+    };
+
+
+    const handleDelete = (group) => {
+        setSelectedGroup(group);
+        setShowDeleteModal(true);
+    };
+
+    const handleRefreshConsumerGroup = async (group) => {
+        setLoading(true);
+        const response = await remoteApi.refreshConsumerGroup(group);
+        setLoading(false);
+        if (response.status === 0) {
+            messageApi.success({content: `${group} ${t.REFRESHED}`});
+            loadConsumerGroups(paginationConf.current);
+        } else {
+            messageApi.error({title: t.ERROR, content: response.errMsg});
+        }
+    };
+
+
+    const handleSort = (sortKey) => {
+        setSortConfig(prev => ({
+            sortKey,
+            sortOrder: prev.sortKey === sortKey ? -prev.sortOrder : 1,
+        }));
+        setPaginationConf(prev => ({...prev, current: 1}));
+    };
+
+    const columns = [
+        {
+            title: <a onClick={() => handleSort('group')}>{t.SUBSCRIPTION_GROUP}</a>,
+            dataIndex: 'group',
+            key: 'group',
+            align: 'center',
+            render: (text) => {
+                const sysFlag = text.startsWith('%SYS%');
+                return (
+                    <span style={{color: sysFlag ? 'red' : ''}}>
+                        {sysFlag ? text.substring(5) : text}
+                    </span>
+                );
+            },
+        },
+        {
+            title: <a onClick={() => handleSort('count')}>{t.QUANTITY}</a>,
+            dataIndex: 'count',
+            key: 'count',
+            align: 'center',
+        },
+        {
+            title: t.VERSION,
+            dataIndex: 'version',
+            key: 'version',
+            align: 'center',
+        },
+        {
+            title: t.TYPE,
+            dataIndex: 'consumeType',
+            key: 'consumeType',
+            align: 'center',
+        },
+        {
+            title: t.MODE,
+            dataIndex: 'messageModel',
+            key: 'messageModel',
+            align: 'center',
+        },
+        {
+            title: <a onClick={() => handleSort('consumeTps')}>TPS</a>,
+            dataIndex: 'consumeTps',
+            key: 'consumeTps',
+            align: 'center',
+        },
+        {
+            title: <a onClick={() => handleSort('diffTotal')}>{t.DELAY}</a>,
+            dataIndex: 'diffTotal',
+            key: 'diffTotal',
+            align: 'center',
+        },
+        {
+            title: t.UPDATE_TIME,
+            dataIndex: 'updateTime',
+            key: 'updateTime',
+            align: 'center',
+        },
+        {
+            title: t.OPERATION,
+            key: 'operation',
+            align: 'left',
+            render: (_, record) => {
+                const sysFlag = record.group.startsWith('%SYS%');
+                return (
+                    <>
+                        <Button
+                            type="primary"
+                            size="small"
+                            style={{marginRight: 8, marginBottom: 8}}
+                            onClick={() => handleClient(record.group, record.address)}
+                        >
+                            {t.CLIENT}
+                        </Button>
+                        <Button
+                            type="primary"
+                            size="small"
+                            style={{marginRight: 8, marginBottom: 8}}
+                            onClick={() => handleDetail(record.group, record.address)}
+                        >
+                            {t.CONSUME_DETAIL}
+                        </Button>
+                        <Button
+                            type="primary"
+                            size="small"
+                            style={{marginRight: 8, marginBottom: 8}}
+                            onClick={() => handleUpdateConfigDialog(record.group)}
+                        >
+                            {t.CONFIG}
+                        </Button>
+                        <Button
+                            type="primary"
+                            size="small"
+                            style={{marginRight: 8, marginBottom: 8}}
+                            onClick={() => handleRefreshConsumerGroup(record.group)}
+                        >
+                            {t.REFRESH}
+                        </Button>
+                        {!sysFlag && writeOperationEnabled && (
+                            <Button
+                                type="primary"
+                                danger
+                                size="small"
+                                style={{marginRight: 8, marginBottom: 8}}
+                                onClick={() => handleDelete(record.group)}
+                            >
+                                {t.DELETE}
+                            </Button>
+                        )}
+                    </>
+                );
+            },
+        },
+    ];
+
+    const handleTableChange = (pagination) => {
+        setPaginationConf(prev => ({
+            ...prev,
+            current: pagination.current,
+            pageSize: pagination.pageSize
+        }));
+        filterList(pagination.current, allConsumerGroupList);
+    };
+
+    const closeConfigModal = () =>{
+        setShowConfig(false);
+        setIsAddConfig(false);
+    }
+
+    return (
+        <>
+            {msgContextHolder}
+            {notificationContextHolder}
+            <div style={{padding: '20px'}}>
+                <Spin spinning={loading} tip={t.LOADING}>
+                    <div style={{marginBottom: '20px'}}>
+                        <div style={{display: 'flex', alignItems: 'center', gap: '15px'}}>
+                            <div style={{display: 'flex', alignItems: 'center'}}>
+                                <label style={{marginRight: '8px'}}>{t.SUBSCRIPTION_GROUP}:</label>
+                                <Input
+                                    style={{width: '200px'}}
+                                    value={filterStr}
+                                    onChange={(e) => handleFilterInputChange(e.target.value)}
+                                />
+                            </div>
+                            <Checkbox checked={filterNormal}
+                                      onChange={(e) => handleTypeFilterChange('normal', e.target.checked)}>
+                                {t.NORMAL}
+                            </Checkbox>
+                            {rmqVersion && (
+                                <Checkbox checked={filterFIFO}
+                                          onChange={(e) => handleTypeFilterChange('fifo', e.target.checked)}>
+                                    {t.FIFO}
+                                </Checkbox>
+                            )}
+                            <Checkbox checked={filterSystem}
+                                      onChange={(e) => handleTypeFilterChange('system', e.target.checked)}>
+                                {t.SYSTEM}
+                            </Checkbox>
+                            {writeOperationEnabled && (
+                                <Button type="primary" onClick={handleOpenAddDialog}>
+                                    {t.ADD} / {t.UPDATE}
+                                </Button>
+                            )}
+                            <Button type="primary" onClick={handleRefreshConsumerData}>
+                                {t.REFRESH}
+                            </Button>
+                            {/*<Switch*/}
+                            {/*    checked={intervalProcessSwitch}*/}
+                            {/*    onChange={(checked) => setIntervalProcessSwitch(checked)}*/}
+                            {/*    checkedChildren={t.AUTO_REFRESH}*/}
+                            {/*    unCheckedChildren={t.AUTO_REFRESH}*/}
+                            {/*/>*/}
+                        </div>
+                    </div>
+
+                    <Table
+                        dataSource={consumerGroupShowList}
+                        columns={columns}
+                        rowKey="group"
+                        bordered
+                        pagination={paginationConf}
+                        onChange={handleTableChange}
+                        sortDirections={['ascend', 'descend']}
+                    />
+                </Spin>
+
+                <ClientInfoModal
+                    visible={showClientInfo}
+                    group={selectedGroup}
+                    address={selectedAddress}
+                    onCancel={() => setShowClientInfo(false)}
+                />
+
+                <ConsumerDetailModal
+                    visible={showConsumeDetail}
+                    group={selectedGroup}
+                    address={selectedAddress}
+                    onCancel={() => setShowConsumeDetail(false)}
+                />
+
+                <ConsumerConfigModal
+                    visible={showConfig}
+                    isAddConfig={isAddConfig}
+                    group={selectedGroup}
+                    onCancel={closeConfigModal}
+                    setIsAddConfig={setIsAddConfig}
+                    onSuccess={loadConsumerGroups}
+                />
+
+                <DeleteConsumerModal
+                    visible={showDeleteModal}
+                    group={selectedGroup}
+                    onCancel={() => setShowDeleteModal(false)}
+                    onSuccess={loadConsumerGroups}
+                />
+            </div>
+        </>
+    );
+};
+
+export default ConsumerGroupList;
diff --git a/frontend-new/src/pages/Dashboard/DashboardPage.jsx b/frontend-new/src/pages/Dashboard/DashboardPage.jsx
new file mode 100644
index 0000000..8161a5d
--- /dev/null
+++ b/frontend-new/src/pages/Dashboard/DashboardPage.jsx
@@ -0,0 +1,455 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, {useCallback, useEffect, useRef, useState} from 'react';
+import {Card, Col, DatePicker, message, notification, Row, Select, Spin, Table} from 'antd';
+import * as echarts from 'echarts';
+import moment from 'moment';
+import {useLanguage} from '../../i18n/LanguageContext';
+import {remoteApi, tools} from '../../api/remoteApi/remoteApi';
+
+const {Option} = Select;
+
+const DashboardPage = () => {
+    const {t} = useLanguage();
+    const barChartRef = useRef(null);
+    const lineChartRef = useRef(null);
+    const topicBarChartRef = useRef(null);
+    const topicLineChartRef = useRef(null);
+
+    const [loading, setLoading] = useState(false);
+    const [date, setDate] = useState(moment());
+    const [topicNames, setTopicNames] = useState([]);
+    const [selectedTopic, setSelectedTopic] = useState(null);
+    const [brokerTableData, setBrokerTableData] = useState([]);
+
+
+    const barChartInstance = useRef(null);
+    const lineChartInstance = useRef(null);
+    const topicBarChartInstance = useRef(null);
+    const topicLineChartInstance = useRef(null);
+
+    const [messageApi, msgContextHolder] = message.useMessage();
+    const [notificationApi, notificationContextHolder] = notification.useNotification();
+
+    const initChart = useCallback((chartRef, titleText, isLine = false) => {
+        if (chartRef.current) {
+            const chart = echarts.init(chartRef.current);
+            let option = {
+                title: {text: titleText},
+                tooltip: {},
+                legend: {data: ['TotalMsg']},
+                axisPointer: {type: 'shadow'},
+                xAxis: {
+                    type: 'category',
+                    data: [],
+                    axisLabel: {
+                        inside: false,
+                        color: '#000000',
+                        rotate: 0,
+                        interval: 0
+                    },
+                    axisTick: {show: true},
+                    axisLine: {show: true},
+                    z: 10
+                },
+                yAxis: {
+                    type: 'value',
+                    boundaryGap: [0, '100%'],
+                    axisLabel: {formatter: (value) => value.toFixed(2)},
+                    splitLine: {show: true}
+                },
+                series: [{name: 'TotalMsg', type: 'bar', data: []}]
+            };
+
+            if (isLine) {
+                option = {
+                    title: {text: titleText},
+                    toolbox: {
+                        feature: {
+                            dataZoom: {yAxisIndex: 'none'},
+                            restore: {},
+                            saveAsImage: {}
+                        }
+                    },
+                    tooltip: {trigger: 'axis', axisPointer: {animation: false}},
+                    yAxis: {
+                        type: 'value',
+                        boundaryGap: [0, '80%'],
+                        axisLabel: {formatter: (value) => value.toFixed(2)},
+                        splitLine: {show: true}
+                    },
+                    dataZoom: [{
+                        type: 'inside', start: 90, end: 100
+                    }, {
+                        start: 0,
+                        end: 10,
+                        handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
+                        handleSize: '80%',
+                        handleStyle: {
+                            color: '#fff',
+                            shadowBlur: 3,
+                            shadowColor: 'rgba(0, 0, 0, 0.6)',
+                            shadowOffsetX: 2,
+                            shadowOffsetY: 2
+                        }
+                    }],
+                    legend: {data: [], top: 30},
+                    xAxis: {type: 'time', boundaryGap: false, data: []},
+                    series: []
+                };
+            }
+            chart.setOption(option);
+            return chart;
+        }
+        return null;
+    }, []);
+
+    useEffect(() => {
+        barChartInstance.current = initChart(barChartRef, t.BROKER + ' TOP 10');
+        lineChartInstance.current = initChart(lineChartRef, t.BROKER + ' 5min trend', true);
+        topicBarChartInstance.current = initChart(topicBarChartRef, t.TOPIC + ' TOP 10');
+        topicLineChartInstance.current = initChart(topicLineChartRef, t.TOPIC + ' 5min trend', true);
+
+        return () => {
+            barChartInstance.current?.dispose();
+            lineChartInstance.current?.dispose();
+            topicBarChartInstance.current?.dispose();
+            topicLineChartInstance.current?.dispose();
+        };
+    }, [t, initChart]);
+
+    const getBrokerBarChartOp = useCallback((xAxisData, data) => {
+        return {
+            xAxis: {data: xAxisData},
+            series: [{name: 'TotalMsg', data: data}]
+        };
+    }, []);
+
+    const getBrokerLineChartOp = useCallback((legend, data) => {
+        const series = [];
+        let xAxisData = [];
+        let isFirstSeries = true;
+
+        Object.entries(data).forEach(([key, values]) => {
+            const tpsValues = [];
+            values.forEach(tpsValue => {
+                const tpsArray = tpsValue.split(",");
+                if (isFirstSeries) {
+                    xAxisData.push(moment(parseInt(tpsArray[0])).format("HH:mm:ss"));
+                }
+                tpsValues.push(parseFloat(tpsArray[1]));
+            });
+            isFirstSeries = false;
+            series.push({
+                name: key,
+                type: 'line',
+                smooth: true,
+                symbol: 'none',
+                sampling: 'average',
+                data: tpsValues
+            });
+        });
+
+        return {
+            legend: {data: legend},
+            color: ["#FF0000", "#00BFFF", "#FF00FF", "#1ce322", "#000000", '#EE7942'],
+            xAxis: {type: 'category', boundaryGap: false, data: xAxisData},
+            series: series
+        };
+    }, []);
+
+    const getTopicLineChartOp = useCallback((legend, data) => {
+        const series = [];
+        let xAxisData = [];
+        let isFirstSeries = true;
+
+        Object.entries(data).forEach(([key, values]) => {
+            const tpsValues = [];
+            values.forEach(tpsValue => {
+                const tpsArray = tpsValue.split(",");
+                if (isFirstSeries) {
+                    xAxisData.push(moment(parseInt(tpsArray[0])).format("HH:mm:ss"));
+                }
+                tpsValues.push(parseFloat(tpsArray[2]));
+            });
+            isFirstSeries = false;
+            series.push({
+                name: key,
+                type: 'line',
+                smooth: true,
+                symbol: 'none',
+                sampling: 'average',
+                data: tpsValues
+            });
+        });
+
+        return {
+            legend: {data: legend},
+            xAxis: {type: 'category', boundaryGap: false, data: xAxisData},
+            series: series
+        };
+    }, []);
+
+    const queryLineData = useCallback(async () => {
+        const _date = date ? date.format("YYYY-MM-DD") : moment().format("YYYY-MM-DD");
+
+        lineChartInstance.current?.showLoading();
+        await remoteApi.queryBrokerHisData(_date, (resp) => {
+            lineChartInstance.current?.hideLoading();
+            if (resp.status === 0) {
+                const _data = {};
+                const _xAxisData = [];
+                Object.entries(resp.data).forEach(([address, values]) => {
+                    _data[address] = values;
+                    _xAxisData.push(address);
+                });
+                lineChartInstance.current?.setOption(getBrokerLineChartOp(_xAxisData, _data));
+            } else {
+                notificationApi.error({message: resp.errMsg || t.QUERY_BROKER_HISTORY_FAILED, duration: 2});
+            }
+        });
+
+        if (selectedTopic) {
+            topicLineChartInstance.current?.showLoading();
+            await remoteApi.queryTopicHisData(_date, selectedTopic, (resp) => {
+                topicLineChartInstance.current?.hideLoading();
+                if (resp.status === 0) {
+                    const _data = {};
+                    _data[selectedTopic] = resp.data;
+                    topicLineChartInstance.current?.setOption(getTopicLineChartOp([selectedTopic], _data));
+                } else {
+                    notificationApi.error({message: resp.errMsg || t.QUERY_TOPIC_HISTORY_FAILED, duration: 2});
+                }
+            });
+        }
+    }, [date, selectedTopic, getBrokerLineChartOp, getTopicLineChartOp, t]);
+
+    useEffect(() => {
+        setLoading(true);
+        barChartInstance.current?.showLoading();
+        remoteApi.queryClusterList((resp) => {
+            setLoading(false);
+            barChartInstance.current?.hideLoading();
+            if (resp.status === 0) {
+                const clusterAddrTable = resp.data.clusterInfo.clusterAddrTable;
+                const brokerAddrTable = resp.data.clusterInfo.brokerAddrTable; // Corrected to brokerAddrTable
+                const brokerDetail = resp.data.brokerServer;
+                const clusterMap = tools.generateBrokerMap(brokerDetail, clusterAddrTable, brokerAddrTable);
+
+                let brokerArray = [];
+                Object.values(clusterMap).forEach(brokersInCluster => {
+                    brokerArray = brokerArray.concat(brokersInCluster);
+                });
+
+                // Update broker table data
+                setBrokerTableData(brokerArray.map(broker => ({
+                    ...broker,
+                    key: broker.brokerName // Ant Design Table needs a unique key
+                })));
+
+                brokerArray.sort((firstBroker, lastBroker) => {
+                    const firstTotalMsg = parseFloat(firstBroker.msgGetTotalTodayNow || 0);
+                    const lastTotalMsg = parseFloat(lastBroker.msgGetTotalTodayNow || 0);
+                    return lastTotalMsg - firstTotalMsg;
+                });
+
+                const xAxisData = [];
+                const data = [];
+                brokerArray.slice(0, 10).forEach(broker => {
+                    xAxisData.push(`${broker.brokerName}:${broker.index}`);
+                    data.push(parseFloat(broker.msgGetTotalTodayNow || 0));
+                });
+                barChartInstance.current?.setOption(getBrokerBarChartOp(xAxisData, data));
+            } else {
+                notificationApi.error({message: resp.errMsg || t.QUERY_CLUSTER_LIST_FAILED, duration: 2});
+            }
+        });
+    }, [getBrokerBarChartOp, t]);
+
+    useEffect(() => {
+        topicBarChartInstance.current?.showLoading();
+        remoteApi.queryTopicCurrentData((resp) => {
+            topicBarChartInstance.current?.hideLoading();
+            if (resp.status === 0) {
+                const topicList = resp.data;
+                topicList.sort((first, last) => {
+                    const firstTotalMsg = parseFloat(first.split(",")[1] || 0);
+                    const lastTotalMsg = parseFloat(last.split(",")[1] || 0);
+                    return lastTotalMsg - firstTotalMsg;
+                });
+
+                const xAxisData = [];
+                const data = [];
+                const names = [];
+
+                topicList.forEach((currentData) => {
+                    const currentArray = currentData.split(",");
+                    names.push(currentArray[0]);
+                });
+                setTopicNames(names);
+
+                if (names.length > 0 && selectedTopic === null) {
+                    setSelectedTopic(names[0]);
+                }
+
+                topicList.slice(0, 10).forEach((currentData) => {
+                    const currentArray = currentData.split(",");
+                    xAxisData.push(currentArray[0]);
+                    data.push(parseFloat(currentArray[1] || 0));
+                });
+
+                const option = {
+                    xAxis: {
+                        data: xAxisData,
+                        axisLabel: {
+                            inside: false,
+                            color: '#000000',
+                            rotate: 60,
+                            interval: 0
+                        },
+                    },
+                    series: [{name: 'TotalMsg', data: data}]
+                };
+                topicBarChartInstance.current?.setOption(option);
+            } else {
+                notificationApi.error({message: resp.errMsg || t.QUERY_TOPIC_CURRENT_FAILED, duration: 2});
+            }
+        });
+    }, [selectedTopic, t]);
+
+    useEffect(() => {
+        if (barChartInstance.current && lineChartInstance.current && topicBarChartInstance.current && topicLineChartInstance.current) {
+            queryLineData();
+        }
+    }, [date, selectedTopic, queryLineData]);
+
+    useEffect(() => {
+        const intervalId = setInterval(queryLineData, tools.dashboardRefreshTime);
+        return () => {
+            clearInterval(intervalId);
+        };
+    }, [queryLineData]);
+
+    const brokerColumns = [
+        {title: t.BROKER_NAME, dataIndex: 'brokerName', key: 'brokerName'},
+        {title: t.BROKER_ADDR, dataIndex: 'brokerAddress', key: 'brokerAddress'},
+        {
+            title: t.TOTAL_MSG_RECEIVED_TODAY,
+            dataIndex: 'msgGetTotalTodayNow',
+            key: 'msgGetTotalTodayNow',
+            render: (text) => parseFloat(text || 0).toLocaleString(),
+            sorter: (a, b) => parseFloat(a.msgGetTotalTodayNow || 0) - parseFloat(b.msgGetTotalTodayNow || 0),
+        },
+        {
+            title: t.TODAY_PRO_COUNT,
+            key: 'todayProCount',
+            render: (_, record) => parseFloat(record.msgPutTotalTodayMorning || 0).toLocaleString(), // Assuming msgPutTotalTodayMorning is 'today pro count'
+        },
+        {
+            title: t.YESTERDAY_PRO_COUNT,
+            key: 'yesterdayProCount',
+            // This calculation (today morning - yesterday morning) might not be correct for 'yesterday pro count'.
+            // It depends on what msgPutTotalTodayMorning and msgPutTotalYesterdayMorning truly represent.
+            // If they are cumulative totals up to morning, then the difference is not accurate for yesterday's count.
+            // You might need a specific 'msgPutTotalYesterdayNow' from the backend.
+            render: (_, record) => (parseFloat(record.msgPutTotalTodayMorning || 0) - parseFloat(record.msgPutTotalYesterdayMorning || 0)).toLocaleString(),
+        },
+    ];
+
+    return (
+        <>
+            {msgContextHolder}
+            {notificationContextHolder}
+            <div style={{padding: '20px'}}>
+                <Spin spinning={loading} tip={t.LOADING}>
+                    <Row gutter={[16, 16]} style={{marginBottom: '20px'}}>
+                        <Col span={12}>
+                            <Card title={t.BROKER_OVERVIEW} bordered>
+                                <Table
+                                    columns={brokerColumns}
+                                    dataSource={brokerTableData}
+                                    rowKey="key"
+                                    pagination={false}
+                                    size="small"
+                                    bordered
+                                    scroll={{y: 240}}
+                                />
+                            </Card>
+                        </Col>
+                        <Col span={12}>
+                            <Card title={t.DASHBOARD_DATE_SELECTION} bordered>
+                                <DatePicker
+                                    format="YYYY-MM-DD"
+                                    value={date}
+                                    onChange={setDate}
+                                    allowClear
+                                    style={{width: '100%'}}
+                                />
+                            </Card>
+                        </Col>
+                    </Row>
+
+                    <Row gutter={[16, 16]} style={{marginBottom: '20px'}}>
+                        <Col span={12}>
+                            <Card title={`${t.BROKER} TOP 10`} bordered>
+                                <div ref={barChartRef} style={{height: 300}}/>
+                            </Card>
+                        </Col>
+                        <Col span={12}>
+                            <Card title={`${t.BROKER} 5min ${t.TREND}`} bordered>
+                                <div ref={lineChartRef} style={{height: 300}}/>
+                            </Card>
+                        </Col>
+                    </Row>
+
+                    <Row gutter={[16, 16]}>
+                        <Col span={12}>
+                            <Card title={`${t.TOPIC} TOP 10`} bordered>
+                                <div ref={topicBarChartRef} style={{height: 300}}/>
+                            </Card>
+                        </Col>
+                        <Col span={12}>
+                            <Card title={`${t.TOPIC} 5min ${t.TREND}`} bordered>
+                                <div style={{marginBottom: '10px'}}>
+                                    <Select
+                                        showSearch
+                                        style={{width: '100%'}}
+                                        placeholder={t.SELECT_TOPIC_PLACEHOLDER}
+                                        value={selectedTopic}
+                                        onChange={setSelectedTopic}
+                                        filterOption={(input, option) =>
+                                            option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                        }
+                                    >
+                                        {topicNames.map(topic => (
+                                            <Option key={topic} value={topic}>{topic}</Option>
+                                        ))}
+                                    </Select>
+                                </div>
+                                <div ref={topicLineChartRef} style={{height: 300}}/>
+                            </Card>
+                        </Col>
+                    </Row>
+                </Spin>
+            </div>
+        </>
+
+    );
+};
+
+export default DashboardPage;
diff --git a/frontend-new/src/pages/DlqMessage/dlqmessage.jsx b/frontend-new/src/pages/DlqMessage/dlqmessage.jsx
new file mode 100644
index 0000000..2591656
--- /dev/null
+++ b/frontend-new/src/pages/DlqMessage/dlqmessage.jsx
@@ -0,0 +1,703 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, {useCallback, useEffect, useState} from 'react';
+import {
+    Button,
+    Checkbox,
+    DatePicker,
+    Form,
+    Input,
+    Modal,
+    notification,
+    Select,
+    Spin,
+    Table,
+    Tabs,
+    Typography
+} from 'antd';
+import moment from 'moment';
+import {ExportOutlined, SearchOutlined, SendOutlined} from '@ant-design/icons';
+import DlqMessageDetailViewDialog from "../../components/DlqMessageDetailViewDialog"; // Ensure this path is correct
+import {useLanguage} from '../../i18n/LanguageContext'; // Ensure this path is correct
+import {remoteApi} from '../../api/remoteApi/remoteApi'; // Adjust the path to your remoteApi.js file
+
+const {TabPane} = Tabs;
+const {Option} = Select;
+const {Text, Paragraph} = Typography;
+
+const SYS_GROUP_TOPIC_PREFIX = "CID_RMQ_SYS_"; // Define this constant as in Angular
+const DLQ_GROUP_TOPIC_PREFIX = "%DLQ%"; // Define this constant
+
+const DlqMessageQueryPage = () => {
+    const {t} = useLanguage();
+    const [activeTab, setActiveTab] = useState('consumer');
+    const [form] = Form.useForm();
+    const [loading, setLoading] = useState(false);
+
+    // Consumer 查询状态
+    const [allConsumerGroupList, setAllConsumerGroupList] = useState([]);
+    const [selectedConsumerGroup, setSelectedConsumerGroup] = useState(null);
+    const [timepickerBegin, setTimepickerBegin] = useState(moment().subtract(3, 'hour')); // 默认三小时前
+    const [timepickerEnd, setTimepickerEnd] = useState(moment());
+    const [messageShowList, setMessageShowList] = useState([]);
+    const [paginationConf, setPaginationConf] = useState({
+        current: 1,
+        pageSize: 20, // Adjusted to 20 as per Angular code
+        total: 0,
+    });
+    const [checkedAll, setCheckedAll] = useState(false);
+    const [selectedMessageIds, setSelectedMessageIds] = useState(new Set()); // Stores msgId for selected messages
+    const [messageCheckedList, setMessageCheckedList] = useState([]); // Stores full message objects for checked items
+    const [taskId, setTaskId] = useState("");
+
+
+    // Message ID 查询状态
+    const [messageId, setMessageId] = useState('');
+    const [queryDlqMessageByMessageIdResult, setQueryDlqMessageByMessageIdResult] = useState([]);
+    const [modalApi, modalContextHolder] = Modal.useModal();
+    const [notificationApi, notificationContextHolder] = notification.useNotification();
+    // Fetch consumer group list on component mount
+    useEffect(() => {
+        const fetchConsumerGroups = async () => {
+            setLoading(true);
+            const resp = await remoteApi.queryConsumerGroupList();
+            if (resp.status === 0) {
+                const filteredGroups = resp.data
+                    .filter(consumerGroup => !consumerGroup.group.startsWith(SYS_GROUP_TOPIC_PREFIX))
+                    .map(consumerGroup => consumerGroup.group)
+                    .sort();
+                setAllConsumerGroupList(filteredGroups);
+            } else {
+                notificationApi.error({message: t.ERROR, description: resp.errMsg});
+            }
+            setLoading(false);
+        };
+        fetchConsumerGroups();
+    }, [t]);
+
+    // Effect to manage batch buttons' disabled state
+    useEffect(() => {
+        const batchResendBtn = document.getElementById('batchResendBtn');
+        const batchExportBtn = document.getElementById('batchExportBtn');
+        if (selectedMessageIds.size > 0) {
+            batchResendBtn?.classList.remove('disabled');
+            batchExportBtn?.classList.remove('disabled');
+        } else {
+            batchResendBtn?.classList.add('disabled');
+            batchExportBtn?.classList.add('disabled');
+        }
+    }, [selectedMessageIds]);
+
+    const onChangeQueryCondition = useCallback(() => {
+        // console.log("查询条件改变");
+        setTaskId(""); // Reset taskId when query conditions change
+        setPaginationConf(prev => ({...prev, currentPage: 1, totalItems: 0}));
+    }, []);
+
+    const queryDlqMessageByConsumerGroup = useCallback(async (page = paginationConf.current, pageSize = paginationConf.pageSize) => {
+        if (!selectedConsumerGroup) {
+            notificationApi.warning({
+                message: t.WARNING,
+                description: t.PLEASE_SELECT_CONSUMER_GROUP,
+            });
+            return;
+        }
+        if (moment(timepickerEnd).valueOf() < moment(timepickerBegin).valueOf()) {
+            notificationApi.error({message: t.END_TIME_LATER_THAN_BEGIN_TIME, delay: 2000});
+            return;
+        }
+
+        setLoading(true);
+        // console.log("根据消费者组查询DLQ消息:", { selectedConsumerGroup, timepickerBegin, timepickerEnd, page, pageSize, taskId });
+        try {
+            const resp = await remoteApi.queryDlqMessageByConsumerGroup(
+                selectedConsumerGroup,
+                moment(timepickerBegin).valueOf(),
+                moment(timepickerEnd).valueOf(),
+                page,
+                pageSize,
+                taskId
+            );
+
+            if (resp.status === 0) {
+                const fetchedMessages = resp.data.page.content.map(msg => ({...msg, checked: false}));
+                setMessageShowList(fetchedMessages);
+                if (fetchedMessages.length === 0) {
+                    notificationApi.info({
+                        message: t.NO_RESULT,
+                        description: t.NO_MATCH_RESULT,
+                    });
+                }
+                setPaginationConf(prev => ({
+                    ...prev,
+                    current: resp.data.page.number + 1,
+                    pageSize: pageSize,
+                    total: resp.data.page.totalElements,
+                }));
+                setTaskId(resp.data.taskId);
+                setSelectedMessageIds(new Set()); // Reset选中项
+                setCheckedAll(false); // Reset全选状态
+                setMessageCheckedList([]); // Clear checked list
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: resp.errMsg,
+                });
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: t.QUERY_FAILED,
+            });
+            console.error("查询失败:", error);
+        } finally {
+            setLoading(false);
+        }
+    }, [selectedConsumerGroup, timepickerBegin, timepickerEnd, paginationConf.current, paginationConf.pageSize, taskId, t]);
+
+    const queryDlqMessageByMessageId = useCallback(async () => {
+        if (!messageId || !selectedConsumerGroup) {
+            notificationApi.warning({
+                message: t.WARNING,
+                description: t.MESSAGE_ID_AND_CONSUMER_GROUP_REQUIRED,
+            });
+            return;
+        }
+        setLoading(true);
+        // console.log("根据Message ID查询DLQ消息:", { msgId: messageId, consumerGroup: selectedConsumerGroup });
+        try {
+            const resp = await remoteApi.viewMessage(messageId, DLQ_GROUP_TOPIC_PREFIX + selectedConsumerGroup);
+            if (resp.status === 0) {
+                setQueryDlqMessageByMessageIdResult(resp.data ? [resp.data] : []);
+                if (!resp.data) {
+                    notificationApi.info({
+                        message: t.NO_RESULT,
+                        description: t.NO_MATCH_RESULT,
+                    });
+                }
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: resp.errMsg,
+                });
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: t.QUERY_FAILED,
+            });
+            console.error("查询失败:", error);
+        } finally {
+            setLoading(false);
+        }
+    }, [messageId, selectedConsumerGroup, t]);
+
+    const queryDlqMessageDetail = useCallback(async (msgId, consumerGroup) => {
+        setLoading(true);
+        // console.log(`查询DLQ消息详情: ${msgId}, 消费者组: ${consumerGroup}`);
+        try {
+            const resp = await remoteApi.viewMessage(msgId, DLQ_GROUP_TOPIC_PREFIX + consumerGroup);
+            if (resp.status === 0) {
+                modalApi.info({
+                    title: t.MESSAGE_DETAIL,
+                    width: 800,
+                    content: (
+                        <DlqMessageDetailViewDialog
+                            ngDialogData={{messageView: resp.data}}
+                        />
+                    ),
+                    onOk: () => {
+                    },
+                    okText: t.CLOSE,
+                });
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: resp.errMsg,
+                });
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: t.QUERY_FAILED,
+            });
+            console.error("查询失败:", error);
+        } finally {
+            setLoading(false);
+        }
+    }, [t]);
+
+    const resendDlqMessage = useCallback(async (messageView, consumerGroup) => {
+        setLoading(true);
+        const topic = messageView.properties.RETRY_TOPIC;
+        const msgId = messageView.properties.ORIGIN_MESSAGE_ID;
+        // console.log(`重发DLQ消息: MsgId=${msgId}, Topic=${topic}, 消费者组=${consumerGroup}`);
+        try {
+            const resp = await remoteApi.resendDlqMessage(msgId, consumerGroup, topic);
+            if (resp.status === 0) {
+                notificationApi.success({
+                    message: t.SUCCESS,
+                    description: t.RESEND_SUCCESS,
+                });
+                modalApi.info({
+                    title: t.RESULT,
+                    content: resp.data,
+                });
+                // Refresh list
+                queryDlqMessageByConsumerGroup(paginationConf.current, paginationConf.pageSize);
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: resp.errMsg,
+                });
+                modalApi.error({
+                    title: t.RESULT,
+                    content: resp.errMsg,
+                });
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: t.RESEND_FAILED,
+            });
+            console.error("重发失败:", error);
+        } finally {
+            setLoading(false);
+        }
+    }, [paginationConf.current, paginationConf.pageSize, queryDlqMessageByConsumerGroup, t]);
+
+    const exportDlqMessage = useCallback(async (msgId, consumerGroup) => {
+        setLoading(true);
+        // console.log(`导出DLQ消息: MsgId=${msgId}, 消费者组=${consumerGroup}`);
+        try {
+            const resp = await remoteApi.exportDlqMessage(msgId, consumerGroup);
+            if (resp.status === 0) {
+                notificationApi.success({
+                    message: t.SUCCESS,
+                    description: t.EXPORT_SUCCESS,
+                });
+                // The actual file download is handled within remoteApi.js
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: resp.errMsg,
+                });
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: t.EXPORT_FAILED,
+            });
+            console.error("导出失败:", error);
+        } finally {
+            setLoading(false);
+        }
+    }, [t]);
+
+    const batchResendDlqMessage = useCallback(async () => {
+        if (selectedMessageIds.size === 0) {
+            notificationApi.warning({
+                message: t.WARNING,
+                description: t.PLEASE_SELECT_MESSAGE_TO_RESEND,
+            });
+            return;
+        }
+        setLoading(true);
+        const messagesToResend = messageCheckedList.map(message => ({
+            topic: message.properties.RETRY_TOPIC,
+            msgId: message.properties.ORIGIN_MESSAGE_ID,
+            consumerGroup: selectedConsumerGroup,
+        }));
+        // console.log(`批量重发DLQ消息到 ${selectedConsumerGroup}:`, messagesToResend);
+        try {
+            const resp = await remoteApi.batchResendDlqMessage(messagesToResend);
+            if (resp.status === 0) {
+                notificationApi.success({
+                    message: t.SUCCESS,
+                    description: t.BATCH_RESEND_SUCCESS,
+                });
+                modalApi.info({
+                    title: t.RESULT,
+                    content: resp.data,
+                });
+                // Refresh list and reset selected state
+                queryDlqMessageByConsumerGroup(paginationConf.current, paginationConf.pageSize);
+                setSelectedMessageIds(new Set());
+                setCheckedAll(false);
+                setMessageCheckedList([]);
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: resp.errMsg,
+                });
+                modalApi.error({
+                    title: t.RESULT,
+                    content: resp.errMsg,
+                });
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: t.BATCH_RESEND_FAILED,
+            });
+            console.error("批量重发失败:", error);
+        } finally {
+            setLoading(false);
+        }
+    }, [selectedMessageIds, messageCheckedList, selectedConsumerGroup, paginationConf.current, paginationConf.pageSize, queryDlqMessageByConsumerGroup, t]);
+
+    const batchExportDlqMessage = useCallback(async () => {
+        if (selectedMessageIds.size === 0) {
+            notificationApi.warning({
+                message: t.WARNING,
+                description: t.PLEASE_SELECT_MESSAGE_TO_EXPORT,
+            });
+            return;
+        }
+        setLoading(true);
+        const messagesToExport = messageCheckedList.map(message => ({
+            msgId: message.msgId,
+            consumerGroup: selectedConsumerGroup,
+        }));
+        // console.log(`批量导出DLQ消息从 ${selectedConsumerGroup}:`, messagesToExport);
+        try {
+            const resp = await remoteApi.batchExportDlqMessage(messagesToExport);
+            if (resp.status === 0) {
+                notificationApi.success({
+                    message: t.SUCCESS,
+                    description: t.BATCH_EXPORT_SUCCESS,
+                });
+                // The actual file download is handled within remoteApi.js
+                // Refresh list and reset selected state
+                queryDlqMessageByConsumerGroup(paginationConf.current, paginationConf.pageSize);
+                setSelectedMessageIds(new Set());
+                setCheckedAll(false);
+                setMessageCheckedList([]);
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: resp.errMsg,
+                });
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: t.BATCH_EXPORT_FAILED,
+            });
+            console.error("批量导出失败:", error);
+        } finally {
+            setLoading(false);
+        }
+    }, [selectedMessageIds, messageCheckedList, selectedConsumerGroup, paginationConf.current, paginationConf.pageSize, queryDlqMessageByConsumerGroup, t]);
+
+    const handleSelectAll = (e) => {
+        const checked = e.target.checked;
+        setCheckedAll(checked);
+        const newSelectedIds = new Set();
+        const newCheckedList = [];
+        const updatedList = messageShowList.map(item => {
+            if (checked) {
+                newSelectedIds.add(item.msgId);
+                newCheckedList.push(item);
+            }
+            return {...item, checked};
+        });
+        setMessageShowList(updatedList);
+        setSelectedMessageIds(newSelectedIds);
+        setMessageCheckedList(newCheckedList);
+    };
+
+    const handleSelectItem = (item, checked) => {
+        const newSelectedIds = new Set(selectedMessageIds);
+        const newCheckedList = [...messageCheckedList];
+
+        if (checked) {
+            newSelectedIds.add(item.msgId);
+            newCheckedList.push(item);
+        } else {
+            newSelectedIds.delete(item.msgId);
+            const index = newCheckedList.findIndex(msg => msg.msgId === item.msgId);
+            if (index > -1) {
+                newCheckedList.splice(index, 1);
+            }
+        }
+        setSelectedMessageIds(newSelectedIds);
+        setMessageCheckedList(newCheckedList);
+
+        // Update single item checked state in the displayed list
+        const updatedList = messageShowList.map(msg =>
+            msg.msgId === item.msgId ? {...msg, checked} : msg
+        );
+        setMessageShowList(updatedList);
+
+        // Check if all are selected
+        setCheckedAll(newSelectedIds.size === updatedList.length && updatedList.length > 0);
+    };
+
+
+    const consumerColumns = [
+        {
+            title: (
+                <Checkbox
+                    checked={checkedAll}
+                    onChange={handleSelectAll}
+                    disabled={messageShowList.length === 0}
+                />
+            ),
+            dataIndex: 'checked',
+            key: 'checkbox',
+            align: 'center',
+            render: (checked, record) => (
+                <Checkbox
+                    checked={checked}
+                    onChange={(e) => handleSelectItem(record, e.target.checked)}
+                />
+            ),
+        },
+        {title: 'Message ID', dataIndex: 'msgId', key: 'msgId', align: 'center'},
+        {
+            title: 'Tag', dataIndex: ['properties', 'TAGS'], key: 'tags', align: 'center',
+            render: (tags) => tags || '-' // Display '-' if tags are null or undefined
+        },
+        {
+            title: 'Key', dataIndex: ['properties', 'KEYS'], key: 'keys', align: 'center',
+            render: (keys) => keys || '-' // Display '-' if keys are null or undefined
+        },
+        {
+            title: 'StoreTime',
+            dataIndex: 'storeTimestamp',
+            key: 'storeTimestamp',
+            align: 'center',
+            render: (text) => moment(text).format("YYYY-MM-DD HH:mm:ss"),
+        },
+        {
+            title: 'Operation',
+            key: 'operation',
+            align: 'center',
+            render: (_, record) => (
+                <>
+                    <Button type="primary" size="small" style={{marginRight: 8, marginBottom: 8}}
+                            onClick={() => queryDlqMessageDetail(record.msgId, selectedConsumerGroup)}>
+                        {t.MESSAGE_DETAIL}
+                    </Button>
+                    <Button type="primary" size="small" style={{marginRight: 8, marginBottom: 8}}
+                            onClick={() => resendDlqMessage(record, selectedConsumerGroup)}>
+                        {t.RESEND_MESSAGE}
+                    </Button>
+                    <Button type="primary" size="small" style={{marginBottom: 8}}
+                            onClick={() => exportDlqMessage(record.msgId, selectedConsumerGroup)}>
+                        {t.EXPORT}
+                    </Button>
+                </>
+            ),
+        },
+    ];
+
+    const messageIdColumns = [
+        {title: 'Message ID', dataIndex: 'msgId', key: 'msgId', align: 'center'},
+        {
+            title: 'Tag', dataIndex: ['properties', 'TAGS'], key: 'tags', align: 'center',
+            render: (tags) => tags || '-'
+        },
+        {
+            title: 'Key', dataIndex: ['properties', 'KEYS'], key: 'keys', align: 'center',
+            render: (keys) => keys || '-'
+        },
+        {
+            title: 'StoreTime',
+            dataIndex: 'storeTimestamp',
+            key: 'storeTimestamp',
+            align: 'center',
+            render: (text) => moment(text).format("YYYY-MM-DD HH:mm:ss"),
+        },
+        {
+            title: 'Operation',
+            key: 'operation',
+            align: 'center',
+            render: (_, record) => (
+                <>
+                    <Button type="primary" size="small" style={{marginRight: 8, marginBottom: 8}}
+                            onClick={() => queryDlqMessageDetail(record.msgId, selectedConsumerGroup)}>
+                        {t.MESSAGE_DETAIL}
+                    </Button>
+                    <Button type="primary" size="small" style={{marginRight: 8, marginBottom: 8}}
+                            onClick={() => resendDlqMessage(record, selectedConsumerGroup)}>
+                        {t.RESEND_MESSAGE}
+                    </Button>
+                    <Button type="primary" size="small" style={{marginBottom: 8}}
+                            onClick={() => exportDlqMessage(record.msgId, selectedConsumerGroup)}>
+                        {t.EXPORT}
+                    </Button>
+                </>
+            ),
+        },
+    ];
+
+    return (
+        <>
+            {notificationContextHolder}
+            <div style={{padding: '20px'}}>
+                <Spin spinning={loading} tip="加载中...">
+                    <Tabs activeKey={activeTab} onChange={setActiveTab} centered>
+                        <TabPane tab={t.CONSUMER} key="consumer">
+                            <h5 style={{margin: '15px 0'}}>{t.TOTAL_MESSAGES}</h5>
+                            <div style={{padding: '20px', minHeight: '600px'}}>
+                                <Form layout="inline" form={form} style={{marginBottom: '20px'}}>
+                                    <Form.Item label={t.CONSUMER}>
+                                        <Select
+                                            showSearch
+                                            style={{width: 300}}
+                                            placeholder={t.SELECT_CONSUMER_GROUP_PLACEHOLDER}
+                                            value={selectedConsumerGroup}
+                                            onChange={(value) => {
+                                                setSelectedConsumerGroup(value);
+                                                onChangeQueryCondition();
+                                            }}
+                                            filterOption={(input, option) =>
+                                                option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                            }
+                                        >
+                                            {allConsumerGroupList.map(group => (
+                                                <Option key={group} value={group}>{group}</Option>
+                                            ))}
+                                        </Select>
+                                    </Form.Item>
+                                    <Form.Item label={t.BEGIN}>
+                                        <DatePicker
+                                            showTime
+                                            format="YYYY-MM-DD HH:mm:ss"
+                                            value={timepickerBegin}
+                                            onChange={(date) => {
+                                                setTimepickerBegin(date);
+                                                onChangeQueryCondition();
+                                            }}
+                                        />
+                                    </Form.Item>
+                                    <Form.Item label={t.END}>
+                                        <DatePicker
+                                            showTime
+                                            format="YYYY-MM-DD HH:mm:ss"
+                                            value={timepickerEnd}
+                                            onChange={(date) => {
+                                                setTimepickerEnd(date);
+                                                onChangeQueryCondition();
+                                            }}
+                                        />
+                                    </Form.Item>
+                                    <Form.Item>
+                                        <Button type="primary" icon={<SearchOutlined/>}
+                                                onClick={() => queryDlqMessageByConsumerGroup()}>
+                                            {t.SEARCH}
+                                        </Button>
+                                    </Form.Item>
+                                    <Form.Item>
+                                        <Button
+                                            id="batchResendBtn"
+                                            type="primary"
+                                            icon={<SendOutlined/>}
+                                            onClick={batchResendDlqMessage}
+                                            disabled={selectedMessageIds.size === 0}
+                                        >
+                                            {t.BATCH_RESEND}
+                                        </Button>
+                                    </Form.Item>
+                                    <Form.Item>
+                                        <Button
+                                            id="batchExportBtn"
+                                            type="primary"
+                                            icon={<ExportOutlined/>}
+                                            onClick={batchExportDlqMessage}
+                                            disabled={selectedMessageIds.size === 0}
+                                        >
+                                            {t.BATCH_EXPORT}
+                                        </Button>
+                                    </Form.Item>
+                                </Form>
+                                <Table
+                                    columns={consumerColumns}
+                                    dataSource={messageShowList}
+                                    rowKey="msgId"
+                                    bordered
+                                    pagination={{
+                                        current: paginationConf.current,
+                                        pageSize: paginationConf.pageSize,
+                                        total: paginationConf.total,
+                                        onChange: (page, pageSize) => queryDlqMessageByConsumerGroup(page, pageSize),
+                                        showSizeChanger: true, // Allow changing page size
+                                        pageSizeOptions: ['10', '20', '50', '100'], // Customizable page size options
+                                    }}
+                                    locale={{emptyText: t.NO_MATCH_RESULT}}
+                                />
+                            </div>
+                        </TabPane>
+                        <TabPane tab="Message ID" key="messageId">
+                            <h5 style={{margin: '15px 0'}}>
+                                {t.MESSAGE_ID_CONSUMER_GROUP_HINT}
+                            </h5>
+                            <div style={{padding: '20px', minHeight: '600px'}}>
+                                <Form layout="inline" style={{marginBottom: '20px'}}>
+                                    <Form.Item label={t.CONSUMER}>
+                                        <Select
+                                            showSearch
+                                            style={{width: 300}}
+                                            placeholder={t.SELECT_CONSUMER_GROUP_PLACEHOLDER}
+                                            value={selectedConsumerGroup}
+                                            onChange={setSelectedConsumerGroup}
+                                            filterOption={(input, option) =>
+                                                option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                            }
+                                        >
+                                            {allConsumerGroupList.map(group => (
+                                                <Option key={group} value={group}>{group}</Option>
+                                            ))}
+                                        </Select>
+                                    </Form.Item>
+                                    <Form.Item label="MessageId:">
+                                        <Input
+                                            style={{width: 450}}
+                                            value={messageId}
+                                            onChange={(e) => setMessageId(e.target.value)}
+                                        />
+                                    </Form.Item>
+                                    <Form.Item>
+                                        <Button type="primary" icon={<SearchOutlined/>}
+                                                onClick={queryDlqMessageByMessageId}>
+                                            {t.SEARCH}
+                                        </Button>
+                                    </Form.Item>
+                                </Form>
+                                <Table
+                                    columns={messageIdColumns}
+                                    dataSource={queryDlqMessageByMessageIdResult}
+                                    rowKey="msgId"
+                                    bordered
+                                    pagination={false}
+                                    locale={{emptyText: t.NO_MATCH_RESULT}}
+                                />
+                            </div>
+                        </TabPane>
+                        {modalContextHolder}
+                    </Tabs>
+                </Spin>
+            </div>
+        </>
+
+    );
+};
+
+export default DlqMessageQueryPage;
diff --git a/frontend-new/src/pages/Login/login.jsx b/frontend-new/src/pages/Login/login.jsx
new file mode 100644
index 0000000..befb025
--- /dev/null
+++ b/frontend-new/src/pages/Login/login.jsx
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Form, Input, Button, message, Typography } from 'antd';
+import {remoteApi} from "../../api/remoteApi/remoteApi";
+
+const { Title } = Typography;
+
+const Login = () => {
+    const [form] = Form.useForm();
+    const [messageApi, msgContextHolder] = message.useMessage();
+
+    const onFinish = async (values) => {
+        const { username, password } = values;
+        remoteApi.login(username, password).then((res) => {
+            if (res.status === 0) {
+                messageApi.success('登录成功');
+                window.sessionStorage.setItem("username", res.data.loginUserName);
+                window.sessionStorage.setItem("userrole", res.data.loginUserRole);
+                window.location.href = '/';
+            } else {
+                messageApi.error(res.message || '登录失败,请检查用户名和密码');
+            }
+        })
+    };
+
+    return (
+        <>
+            {msgContextHolder}
+            <div style={{
+                maxWidth: 400,
+                margin: '100px auto',
+                padding: 24,
+                boxShadow: '0 2px 8px #f0f1f2',
+                borderRadius: 8
+            }}>
+                <Title level={3} style={{textAlign: 'center', marginBottom: 24}}>
+                    WELCOME
+                </Title>
+                <Form
+                    form={form}
+                    name="login_form"
+                    layout="vertical"
+                    onFinish={onFinish}
+                    initialValues={{username: '', password: ''}}
+                >
+                    <Form.Item
+                        label="用户名"
+                        name="username"
+                        rules={[{required: true, message: '请输入用户名'}]}
+                    >
+                        <Input placeholder="请输入用户名"/>
+                    </Form.Item>
+
+                    <Form.Item
+                        label="密码"
+                        name="password"
+                        rules={[{required: true, message: '请输入密码'}]}
+                    >
+                        <Input.Password placeholder="请输入密码"/>
+                    </Form.Item>
+
+                    <Form.Item>
+                        <Button type="primary" htmlType="submit" block>
+                            登录
+                        </Button>
+                    </Form.Item>
+                </Form>
+            </div>
+        </>
+
+    );
+};
+
+export default Login;
diff --git a/frontend-new/src/pages/Message/message.jsx b/frontend-new/src/pages/Message/message.jsx
new file mode 100644
index 0000000..73d3baf
--- /dev/null
+++ b/frontend-new/src/pages/Message/message.jsx
@@ -0,0 +1,478 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, {useCallback, useEffect, useState} from 'react';
+import {Button, DatePicker, Form, Input, notification, Select, Spin, Table, Tabs, Typography} from 'antd';
+import moment from 'moment';
+import {SearchOutlined} from '@ant-design/icons';
+import {useLanguage} from '../../i18n/LanguageContext';
+import MessageDetailViewDialog from "../../components/MessageDetailViewDialog"; // Keep this path
+import {remoteApi} from '../../api/remoteApi/remoteApi'; // Keep this path
+
+const {TabPane} = Tabs;
+const {Option} = Select;
+const {Text, Paragraph} = Typography;
+
+const MessageQueryPage = () => {
+    const {t} = useLanguage();
+    const [activeTab, setActiveTab] = useState('topic');
+    const [form] = Form.useForm();
+    const [loading, setLoading] = useState(false);
+
+    // Topic 查询状态
+    const [allTopicList, setAllTopicList] = useState([]);
+    const [selectedTopic, setSelectedTopic] = useState(null);
+    const [timepickerBegin, setTimepickerBegin] = useState(moment().subtract(1, 'hour')); // 默认一小时前
+    const [timepickerEnd, setTimepickerEnd] = useState(moment());
+    const [messageShowList, setMessageShowList] = useState([]);
+    const [paginationConf, setPaginationConf] = useState({
+        current: 1,
+        pageSize: 10,
+        total: 0,
+    });
+    const [taskId, setTaskId] = useState("");
+
+    // Message Key 查询状态
+    const [key, setKey] = useState('');
+    const [queryMessageByTopicAndKeyResult, setQueryMessageByTopicAndKeyResult] = useState([]);
+
+    // Message ID 查询状态
+    const [messageId, setMessageId] = useState('');
+
+    // State for Message Detail Dialog
+    const [isMessageDetailModalVisible, setIsMessageDetailModalVisible] = useState(false);
+    const [currentMessageIdForDetail, setCurrentMessageIdForDetail] = useState(null);
+    const [currentTopicForDetail, setCurrentTopicForDetail] = useState(null);
+    const [notificationApi, notificationContextHolder] = notification.useNotification();
+
+    const fetchAllTopics = useCallback(async () => {
+        setLoading(true);
+        try {
+            const resp = await remoteApi.queryTopic(false);
+            if (resp.status === 0) {
+                setAllTopicList(resp.data.topicList.sort());
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: resp.errMsg || t.FETCH_TOPIC_FAILED,
+                });
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: t.FETCH_TOPIC_FAILED,
+            });
+            console.error("Error fetching topic list:", error);
+        } finally {
+            setLoading(false);
+        }
+    }, [t]);
+
+    useEffect(() => {
+        fetchAllTopics();
+    }, [fetchAllTopics]);
+
+    const onChangeQueryCondition = () => {
+        setTaskId("");
+        setPaginationConf(prev => ({
+            ...prev,
+            current: 1,
+            total: 0,
+        }));
+    };
+
+    const queryMessagePageByTopic = async (page = paginationConf.current, pageSize = paginationConf.pageSize) => {
+        if (!selectedTopic) {
+            notificationApi.warning({
+                message: t.WARNING,
+                description: t.PLEASE_SELECT_TOPIC,
+            });
+            return;
+        }
+        if (timepickerEnd.valueOf() < timepickerBegin.valueOf()) {
+            notificationApi.error({message: t.ERROR, description: t.END_TIME_EARLIER_THAN_BEGIN_TIME});
+            return;
+        }
+
+        setLoading(true);
+        try {
+            const resp = await remoteApi.queryMessagePageByTopic(
+                selectedTopic,
+                timepickerBegin.valueOf(),
+                timepickerEnd.valueOf(),
+                page,
+                pageSize,
+                taskId
+            );
+
+            if (resp.status === 0) {
+                setMessageShowList(resp.data.page.content);
+                setPaginationConf(prev => ({
+                    ...prev,
+                    current: resp.data.page.number + 1,
+                    total: resp.data.page.totalElements,
+                    pageSize: pageSize,
+                }));
+                setTaskId(resp.data.taskId);
+
+                if (resp.data.page.content.length === 0) {
+                    notificationApi.info({
+                        message: t.NO_RESULT,
+                        description: t.NO_MATCH_RESULT,
+                    });
+                }
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: resp.errMsg || t.QUERY_FAILED,
+                });
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: t.QUERY_FAILED,
+            });
+            console.error("查询失败:", error);
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const queryMessageByTopicAndKey = async () => {
+        if (!selectedTopic || !key) {
+            notificationApi.warning({
+                message: t.WARNING,
+                description: t.TOPIC_AND_KEY_REQUIRED,
+            });
+            return;
+        }
+        setLoading(true);
+        try {
+            const resp = await remoteApi.queryMessageByTopicAndKey(selectedTopic, key);
+            if (resp.status === 0) {
+                setQueryMessageByTopicAndKeyResult(resp.data);
+                if (resp.data.length === 0) {
+                    notificationApi.info({
+                        message: t.NO_RESULT,
+                        description: t.NO_MATCH_RESULT,
+                    });
+                }
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: resp.errMsg || t.QUERY_FAILED,
+                });
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: t.QUERY_FAILED,
+            });
+            console.error("查询失败:", error);
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    // Updated to open the dialog
+    const showMessageDetail = (msgIdToQuery, topicToQuery) => {
+        if (!msgIdToQuery) {
+            notificationApi.warning({
+                message: t.WARNING,
+                description: t.MESSAGE_ID_REQUIRED,
+            });
+            return;
+        }
+        setCurrentMessageIdForDetail(msgIdToQuery);
+        setCurrentTopicForDetail(topicToQuery);
+        setIsMessageDetailModalVisible(true);
+    };
+
+    const handleCloseMessageDetailModal = () => {
+        setIsMessageDetailModalVisible(false);
+        setCurrentMessageIdForDetail(null);
+        setCurrentTopicForDetail(null);
+    };
+
+    const handleResendMessage = async (messageView, consumerGroup) => {
+        setLoading(true); // Set loading for the main page as well, as the dialog itself can't control it
+        let topicToResend = messageView.topic;
+        let msgIdToResend = messageView.msgId;
+
+
+        if (topicToResend.startsWith('%DLQ%')) {
+            if (messageView.properties && messageView.properties.hasOwnProperty("RETRY_TOPIC")) {
+                topicToResend = messageView.properties.RETRY_TOPIC;
+            }
+            if (messageView.properties && messageView.properties.hasOwnProperty("ORIGIN_MESSAGE_ID")) {
+                msgIdToResend = messageView.properties.ORIGIN_MESSAGE_ID;
+            }
+        }
+
+        try {
+            const resp = await remoteApi.resendMessageDirectly(msgIdToResend, consumerGroup, topicToResend);
+            if (resp.status === 0) {
+                notificationApi.success({
+                    message: t.SUCCESS,
+                    description: t.RESEND_SUCCESS,
+                });
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: resp.errMsg || t.RESEND_FAILED,
+                });
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: t.RESEND_FAILED,
+            });
+            console.error("重发失败:", error);
+        } finally {
+            setLoading(false);
+            // Optionally, you might want to refresh the message detail after resend
+            // or close the modal if resend was successful and you don't need to see details immediately.
+            // For now, we'll keep the modal open and let the user close it.
+        }
+    };
+
+    const topicColumns = [
+        {
+            title: 'Message ID', dataIndex: 'msgId', key: 'msgId', align: 'center',
+            render: (text) => <Text copyable>{text}</Text>
+        },
+        {title: 'Tag', dataIndex: ['properties', 'TAGS'], key: 'tags', align: 'center'},
+        {title: 'Key', dataIndex: ['properties', 'KEYS'], key: 'keys', align: 'center'},
+        {
+            title: 'StoreTime',
+            dataIndex: 'storeTimestamp',
+            key: 'storeTimestamp',
+            align: 'center',
+            render: (text) => moment(text).format("YYYY-MM-DD HH:mm:ss"),
+        },
+        {
+            title: 'Operation',
+            key: 'operation',
+            align: 'center',
+            render: (_, record) => (
+                <Button type="primary" size="small" onClick={() => showMessageDetail(record.msgId, record.topic)}>
+                    {t.MESSAGE_DETAIL}
+                </Button>
+            ),
+        },
+    ];
+
+    const keyColumns = [
+        {
+            title: 'Message ID', dataIndex: 'msgId', key: 'msgId', align: 'center',
+            render: (text) => <Text copyable>{text}</Text>
+        },
+        {title: 'Tag', dataIndex: ['properties', 'TAGS'], key: 'tags', align: 'center'},
+        {title: 'Key', dataIndex: ['properties', 'KEYS'], key: 'keys', align: 'center'},
+        {
+            title: 'StoreTime',
+            dataIndex: 'storeTimestamp',
+            key: 'storeTimestamp',
+            align: 'center',
+            render: (text) => moment(text).format("YYYY-MM-DD HH:mm:ss"),
+        },
+        {
+            title: 'Operation',
+            key: 'operation',
+            align: 'center',
+            render: (_, record) => (
+                <Button type="primary" size="small" onClick={() => showMessageDetail(record.msgId, record.topic)}>
+                    {t.MESSAGE_DETAIL}
+                </Button>
+            ),
+        },
+    ];
+
+    return (
+        <>
+            {notificationContextHolder}
+            <div style={{padding: '20px'}}>
+                <Spin spinning={loading} tip={t.LOADING_DATA}>
+                    <Tabs activeKey={activeTab} onChange={setActiveTab} centered>
+                        <TabPane tab="Topic" key="topic">
+                            <h5 style={{margin: '15px 0'}}>{t.TOTAL_MESSAGES}</h5>
+                            <div style={{padding: '20px', minHeight: '600px'}}>
+                                <Form layout="inline" form={form} style={{marginBottom: '20px'}}>
+                                    <Form.Item label={t.TOPIC}>
+                                        <Select
+                                            showSearch
+                                            style={{width: 300}}
+                                            placeholder={t.SELECT_TOPIC_PLACEHOLDER}
+                                            value={selectedTopic}
+                                            onChange={(value) => {
+                                                setSelectedTopic(value);
+                                                onChangeQueryCondition();
+                                            }}
+                                            filterOption={(input, option) =>
+                                                option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                            }
+                                        >
+                                            {allTopicList.map(topic => (
+                                                <Option key={topic} value={topic}>{topic}</Option>
+                                            ))}
+                                        </Select>
+                                    </Form.Item>
+                                    <Form.Item label={t.BEGIN}>
+                                        <DatePicker
+                                            showTime
+                                            format="YYYY-MM-DD HH:mm:ss"
+                                            value={timepickerBegin}
+                                            onChange={(date) => {
+                                                setTimepickerBegin(date);
+                                                onChangeQueryCondition();
+                                            }}
+                                        />
+                                    </Form.Item>
+                                    <Form.Item label={t.END}>
+                                        <DatePicker
+                                            showTime
+                                            format="YYYY-MM-DD HH:mm:ss"
+                                            value={timepickerEnd}
+                                            onChange={(date) => {
+                                                setTimepickerEnd(date);
+                                                onChangeQueryCondition();
+                                            }}
+                                        />
+                                    </Form.Item>
+                                    <Form.Item>
+                                        <Button type="primary" icon={<SearchOutlined/>}
+                                                onClick={() => queryMessagePageByTopic()}>
+                                            {t.SEARCH}
+                                        </Button>
+                                    </Form.Item>
+                                </Form>
+                                <Table
+                                    columns={topicColumns}
+                                    dataSource={messageShowList}
+                                    rowKey="msgId"
+                                    bordered
+                                    pagination={{
+                                        current: paginationConf.current,
+                                        pageSize: paginationConf.pageSize,
+                                        total: paginationConf.total,
+                                        onChange: (page, pageSize) => queryMessagePageByTopic(page, pageSize),
+                                    }}
+                                    locale={{emptyText: t.NO_MATCH_RESULT}}
+                                />
+                            </div>
+                        </TabPane>
+                        <TabPane tab="Message Key" key="messageKey">
+                            <h5 style={{margin: '15px 0'}}>{t.ONLY_RETURN_64_MESSAGES}</h5>
+                            <div style={{padding: '20px', minHeight: '600px'}}>
+                                <Form layout="inline" style={{marginBottom: '20px'}}>
+                                    <Form.Item label="Topic:">
+                                        <Select
+                                            showSearch
+                                            style={{width: 300}}
+                                            placeholder={t.SELECT_TOPIC_PLACEHOLDER}
+                                            value={selectedTopic}
+                                            onChange={setSelectedTopic}
+                                            filterOption={(input, option) =>
+                                                option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                            }
+                                        >
+                                            {allTopicList.map(topic => (
+                                                <Option key={topic} value={topic}>{topic}</Option>
+                                            ))}
+                                        </Select>
+                                    </Form.Item>
+                                    <Form.Item label="Key:">
+                                        <Input
+                                            style={{width: 450}}
+                                            value={key}
+                                            onChange={(e) => setKey(e.target.value)}
+                                        />
+                                    </Form.Item>
+                                    <Form.Item>
+                                        <Button type="primary" icon={<SearchOutlined/>}
+                                                onClick={queryMessageByTopicAndKey}>
+                                            {t.SEARCH}
+                                        </Button>
+                                    </Form.Item>
+                                </Form>
+                                <Table
+                                    columns={keyColumns}
+                                    dataSource={queryMessageByTopicAndKeyResult}
+                                    rowKey="msgId"
+                                    bordered
+                                    pagination={false}
+                                    locale={{emptyText: t.NO_MATCH_RESULT}}
+                                />
+                            </div>
+                        </TabPane>
+                        <TabPane tab="Message ID" key="messageId">
+                            <h5 style={{margin: '15px 0'}}>
+                                {t.MESSAGE_ID_TOPIC_HINT}
+                            </h5>
+                            <div style={{padding: '20px', minHeight: '600px'}}>
+                                <Form layout="inline" style={{marginBottom: '20px'}}>
+                                    <Form.Item label="Topic:">
+                                        <Select
+                                            showSearch
+                                            style={{width: 300}}
+                                            placeholder={t.SELECT_TOPIC_PLACEHOLDER}
+                                            value={selectedTopic}
+                                            onChange={setSelectedTopic}
+                                            filterOption={(input, option) =>
+                                                option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                            }
+                                        >
+                                            {allTopicList.map(topic => (
+                                                <Option key={topic} value={topic}>{topic}</Option>
+                                            ))}
+                                        </Select>
+                                    </Form.Item>
+                                    <Form.Item label="MessageId:">
+                                        <Input
+                                            style={{width: 450}}
+                                            value={messageId}
+                                            onChange={(e) => setMessageId(e.target.value)}
+                                        />
+                                    </Form.Item>
+                                    <Form.Item>
+                                        <Button type="primary" icon={<SearchOutlined/>}
+                                                onClick={() => showMessageDetail(messageId, selectedTopic)}>
+                                            {t.SEARCH}
+                                        </Button>
+                                    </Form.Item>
+                                </Form>
+                                {/* Message ID 查询结果通常直接弹窗显示,这里不需要表格 */}
+                            </div>
+                        </TabPane>
+                    </Tabs>
+                </Spin>
+
+                {/* Message Detail Dialog Component */}
+                <MessageDetailViewDialog
+                    visible={isMessageDetailModalVisible}
+                    onCancel={handleCloseMessageDetailModal}
+                    messageId={currentMessageIdForDetail}
+                    topic={currentTopicForDetail}
+                    onResendMessage={handleResendMessage} // Pass the resend function
+                />
+            </div>
+        </>
+
+    );
+};
+
+export default MessageQueryPage;
diff --git a/frontend-new/src/pages/MessageTrace/messagetrace.jsx b/frontend-new/src/pages/MessageTrace/messagetrace.jsx
new file mode 100644
index 0000000..2562143
--- /dev/null
+++ b/frontend-new/src/pages/MessageTrace/messagetrace.jsx
@@ -0,0 +1,429 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, {useEffect, useState} from 'react';
+import {Button, Form, Input, notification, Select, Spin, Table, Tabs, Typography} from 'antd';
+import moment from 'moment';
+import {SearchOutlined} from '@ant-design/icons';
+import {useLanguage} from '../../i18n/LanguageContext';
+import MessageTraceDetailViewDialog from "../../components/MessageTraceDetailViewDialog";
+import {remoteApi} from '../../api/remoteApi/remoteApi'; // Import the remoteApi
+
+const {TabPane} = Tabs;
+const {Option} = Select;
+const {Text, Paragraph} = Typography;
+
+const MessageTraceQueryPage = () => {
+    const {t} = useLanguage();
+    const [activeTab, setActiveTab] = useState('messageKey');
+    const [form] = Form.useForm();
+    const [loading, setLoading] = useState(false);
+
+    // 轨迹主题选择
+    const [allTraceTopicList, setAllTraceTopicList] = useState([]);
+    const [selectedTraceTopic, setSelectedTraceTopic] = useState(null); // Initialize as null or a default trace topic if applicable
+
+    // Topic 查询状态
+    const [allTopicList, setAllTopicList] = useState([]);
+    const [selectedTopic, setSelectedTopic] = useState(null);
+    const [key, setKey] = useState('');
+    const [queryMessageByTopicAndKeyResult, setQueryMessageByTopicAndKeyResult] = useState([]);
+
+    // Message ID 查询状态
+    const [messageId, setMessageId] = useState('');
+    const [queryMessageByMessageIdResult, setQueryMessageByMessageIdResult] = useState([]);
+
+    // State for MessageTraceDetailViewDialog
+    const [isTraceDetailViewOpen, setIsTraceDetailViewOpen] = useState(false);
+    const [traceDetailData, setTraceDetailData] = useState(null);
+    const [notificationApi, notificationContextHolder] = notification.useNotification();
+
+    useEffect(() => {
+        const fetchTopics = async () => {
+            setLoading(true);
+            try {
+                const resp = await remoteApi.queryTopic(true);
+
+                if (resp.status === 0) {
+                    const topics = resp.data.topicList.sort();
+                    setAllTopicList(topics);
+
+                    const traceTopics = topics.filter(topic =>
+                        !topic.startsWith('%RETRY%') && !topic.startsWith('%DLQ%')
+                    );
+                    setAllTraceTopicList(traceTopics);
+                    // Optionally set a default trace topic if available, e.g., 'RMQ_SYS_TRACE_TOPIC'
+                    if (traceTopics.includes('RMQ_SYS_TRACE_TOPIC')) {
+                        setSelectedTraceTopic('RMQ_SYS_TRACE_TOPIC');
+                    } else if (traceTopics.length > 0) {
+                        setSelectedTraceTopic(traceTopics[0]); // Select the first one if no default
+                    }
+                } else {
+                    notificationApi.error({
+                        message: t.ERROR,
+                        description: resp.errMsg || t.QUERY_FAILED,
+                    });
+                }
+            } catch (error) {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: error.message || t.QUERY_FAILED,
+                });
+            } finally {
+                setLoading(false);
+            }
+        };
+
+        fetchTopics();
+    }, [t]);
+
+    const queryMessageByTopicAndKey = async () => {
+        if (!selectedTopic || !key) {
+            notificationApi.warning({
+                message: t.WARNING,
+                description: t.TOPIC_AND_KEY_REQUIRED,
+            });
+            return;
+        }
+        setLoading(true);
+
+        try {
+            const data = await remoteApi.queryMessageByTopicAndKey(selectedTopic, key);
+            if (data.status === 0) {
+                setQueryMessageByTopicAndKeyResult(data.data);
+                if (data.data.length === 0) {
+                    notificationApi.info({
+                        message: t.NO_RESULT,
+                        description: t.NO_MATCH_RESULT,
+                    });
+                }
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: data.errMsg || t.QUERY_FAILED,
+                });
+                setQueryMessageByTopicAndKeyResult([]); // Clear previous results on error
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: error.message || t.QUERY_FAILED,
+            });
+            setQueryMessageByTopicAndKeyResult([]); // Clear previous results on error
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const queryMessageByMessageId = async (msgIdToQuery, topicToQuery) => {
+        if (!msgIdToQuery) {
+            notificationApi.warning({
+                message: t.WARNING,
+                description: t.MESSAGE_ID_REQUIRED,
+            });
+            return;
+        }
+        setLoading(true);
+
+        try {
+            const res = await remoteApi.queryMessageByMessageId(msgIdToQuery, topicToQuery);
+            if (res.status === 0) {
+                // 确保 data.data.messageView 存在,并将其包装成数组
+                setQueryMessageByMessageIdResult(res.data && res.data.messageView ? [res.data.messageView] : []);
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: res.errMsg || t.QUERY_FAILED,
+                });
+                setQueryMessageByMessageIdResult([]); // 清除错误时的旧数据
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: error.message || t.QUERY_FAILED,
+            });
+            setQueryMessageByMessageIdResult([]); // 清除错误时的旧数据
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const queryMessageTraceByMessageId = async (msgId, traceTopic) => {
+        if (!msgId) {
+            notificationApi.warning({
+                message: t.WARNING,
+                description: t.MESSAGE_ID_REQUIRED,
+            });
+            return;
+        }
+        setLoading(true);
+
+        try {
+            const data = await remoteApi.queryMessageTraceByMessageId(msgId, traceTopic || 'RMQ_SYS_TRACE_TOPIC');
+            if (data.status === 0) {
+                setTraceDetailData(data.data);
+                setIsTraceDetailViewOpen(true);
+            } else {
+                notificationApi.error({
+                    message: t.ERROR,
+                    description: data.errMsg || t.QUERY_FAILED,
+                });
+                setTraceDetailData(null); // Clear previous trace data on error
+                setIsTraceDetailViewOpen(false); // Do not open dialog if data is not available
+            }
+        } catch (error) {
+            notificationApi.error({
+                message: t.ERROR,
+                description: error.message || t.QUERY_FAILED,
+            });
+            setTraceDetailData(null); // Clear previous trace data on error
+            setIsTraceDetailViewOpen(false); // Do not open dialog if data is not available
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handleCloseTraceDetailView = () => {
+        setIsTraceDetailViewOpen(false);
+        setTraceDetailData(null);
+    };
+
+    const keyColumns = [
+        {title: 'Message ID', dataIndex: 'msgId', key: 'msgId', align: 'center'},
+        {title: 'Tag', dataIndex: ['properties', 'TAGS'], key: 'tags', align: 'center'},
+        {title: 'Message Key', dataIndex: ['properties', 'KEYS'], key: 'keys', align: 'center'},
+        {
+            title: 'StoreTime',
+            dataIndex: 'storeTimestamp',
+            key: 'storeTimestamp',
+            align: 'center',
+            render: (text) => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+        },
+        {
+            title: 'Operation',
+            key: 'operation',
+            align: 'center',
+            render: (_, record) => (
+                <Button type="primary" size="small"
+                        onClick={() => queryMessageTraceByMessageId(record.msgId, selectedTraceTopic)}>
+                    {t.MESSAGE_TRACE_DETAIL}
+                </Button>
+            ),
+        },
+    ];
+
+    const messageIdColumns = [
+        {title: 'Message ID', dataIndex: 'msgId', key: 'msgId', align: 'center'},
+        // 注意:这里的 dataIndex 直接指向了 messageView 内部的属性
+        {title: 'Tag', dataIndex: ['properties', 'TAGS'], key: 'tags', align: 'center'},
+        {title: 'Message Key', dataIndex: ['properties', 'KEYS'], key: 'keys', align: 'center'},
+        {
+            title: 'StoreTime',
+            dataIndex: 'storeTimestamp',
+            key: 'storeTimestamp',
+            align: 'center',
+            render: (text) => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+        },
+        {
+            title: 'Operation',
+            key: 'operation',
+            align: 'center',
+            render: (_, record) => (
+                <Button type="primary" size="small"
+                        onClick={() => queryMessageTraceByMessageId(record.msgId, selectedTraceTopic)}>
+                    {t.MESSAGE_TRACE_DETAIL}
+                </Button>
+            ),
+        },
+    ];
+
+    return (
+        <>
+            {notificationContextHolder}
+            <div style={{padding: '20px'}}>
+                <Spin spinning={loading} tip="加载中...">
+                    <div style={{marginBottom: '20px', borderBottom: '1px solid #f0f0f0', paddingBottom: '15px'}}>
+                        <Form layout="inline">
+                            <Form.Item label={<Text strong>{t.TRACE_TOPIC}:</Text>}>
+                                <Select
+                                    showSearch
+                                    style={{minWidth: 300}}
+                                    placeholder={t.SELECT_TRACE_TOPIC_PLACEHOLDER}
+                                    value={selectedTraceTopic}
+                                    onChange={setSelectedTraceTopic}
+                                    filterOption={(input, option) =>
+                                        option.children && option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                    }
+                                >
+                                    {allTraceTopicList.map(topic => (
+                                        <Option key={topic} value={topic}>{topic}</Option>
+                                    ))}
+                                </Select>
+                            </Form.Item>
+                            <Text type="secondary" style={{marginLeft: 10}}>({t.TRACE_TOPIC_HINT})</Text>
+                        </Form>
+                    </div>
+
+                    <Tabs activeKey={activeTab} onChange={setActiveTab} centered>
+                        <TabPane tab="Message Key" key="messageKey">
+                            <h5 style={{margin: '15px 0'}}>{t.ONLY_RETURN_64_MESSAGES}</h5>
+                            <div style={{padding: '20px', minHeight: '600px'}}>
+                                <Form layout="inline" form={form} style={{marginBottom: '20px'}}>
+                                    <Form.Item label="Topic:">
+                                        <Select
+                                            showSearch
+                                            style={{width: 300}}
+                                            placeholder={t.SELECT_TOPIC_PLACEHOLDER}
+                                            value={selectedTopic}
+                                            onChange={setSelectedTopic}
+                                            required
+                                            filterOption={(input, option) =>
+                                                option.children && option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                                            }
+                                        >
+                                            <Option value="">{t.SELECT_TOPIC_PLACEHOLDER}</Option>
+                                            {allTopicList.map(topic => (
+                                                <Option key={topic} value={topic}>{topic}</Option>
+                                            ))}
+                                        </Select>
+                                    </Form.Item>
+                                    <Form.Item label="Key:">
+                                        <Input
+                                            style={{width: 450}}
+                                            value={key}
+                                            onChange={(e) => setKey(e.target.value)}
+                                            required
+                                        />
+                                    </Form.Item>
+                                    <Form.Item>
+                                        <Button type="primary" icon={<SearchOutlined/>}
+                                                onClick={queryMessageByTopicAndKey}>
+                                            {t.SEARCH}
+                                        </Button>
+                                    </Form.Item>
+                                </Form>
+                                <Table
+                                    columns={keyColumns}
+                                    dataSource={queryMessageByTopicAndKeyResult}
+                                    rowKey="msgId"
+                                    bordered
+                                    pagination={false}
+                                    locale={{emptyText: t.NO_MATCH_RESULT}}
+                                />
+                            </div>
+                        </TabPane>
+                        <TabPane tab="Message ID" key="messageId">
+                            <h5 style={{margin: '15px 0'}}>{t.MESSAGE_ID_TOPIC_HINT}</h5>
+                            <div style={{padding: '20px', minHeight: '600px'}}>
+                                <Form layout="inline" style={{marginBottom: '20px'}}>
+                                    <Form.Item label="Topic:">
+                                        <Select
+                                            showSearch
+                                            style={{width: 300}}
+                                            placeholder={t.SELECT_TOPIC_PLACEHOLDER}
+                                            value={selectedTopic}
+                                            onChange={setSelectedTopic}
+                                            required
+                                            filterOption={(input, option) => {
+                                                if (option.children && typeof option.children === 'string') {
+                                                    return option.children.toLowerCase().includes(input.toLowerCase());
+                                                }
+                                                return false;
+                                            }}
+                                        >
+                                            <Option value="">{t.SELECT_TOPIC_PLACEHOLDER}</Option>
+                                            {allTopicList.map(topic => (
+                                                <Option key={topic} value={topic}>{topic}</Option>
+                                            ))}
+                                        </Select>
+                                    </Form.Item>
+                                    <Form.Item label="MessageId:">
+                                        <Input
+                                            style={{width: 450}}
+                                            value={messageId}
+                                            onChange={(e) => setMessageId(e.target.value)}
+                                            required
+                                        />
+                                    </Form.Item>
+                                    <Form.Item>
+                                        <Button type="primary" icon={<SearchOutlined/>}
+                                                onClick={() => queryMessageByMessageId(messageId, selectedTopic)}>
+                                            {t.SEARCH}
+                                        </Button>
+                                    </Form.Item>
+                                </Form>
+                                <Table
+                                    columns={messageIdColumns}
+                                    dataSource={queryMessageByMessageIdResult}
+                                    rowKey="msgId"
+                                    bordered
+                                    pagination={false}
+                                    locale={{emptyText: t.NO_MATCH_RESULT}}
+                                />
+                            </div>
+                        </TabPane>
+                    </Tabs>
+                </Spin>
+
+                {/* MessageTraceDetailViewDialog as a child component */}
+                {isTraceDetailViewOpen && traceDetailData && (
+                    <div style={{
+                        position: 'fixed',
+                        top: 0,
+                        left: 0,
+                        right: 0,
+                        bottom: 0,
+                        backgroundColor: 'rgba(0, 0, 0, 0.5)',
+                        display: 'flex',
+                        justifyContent: 'center',
+                        alignItems: 'center',
+                        zIndex: 1000,
+                    }}>
+                        <div style={{
+                            backgroundColor: '#fff',
+                            padding: '20px',
+                            borderRadius: '8px',
+                            width: '80%',
+                            maxHeight: '90%',
+                            overflowY: 'auto',
+                            position: 'relative'
+                        }}>
+                            <Typography.Title level={4}
+                                              style={{marginBottom: '20px'}}>{t.MESSAGE_TRACE_DETAIL}</Typography.Title>
+                            <Button
+                                onClick={handleCloseTraceDetailView}
+                                style={{
+                                    position: 'absolute',
+                                    top: '20px',
+                                    right: '20px',
+                                }}
+                            >
+                                {t.CLOSE}
+                            </Button>
+                            <MessageTraceDetailViewDialog
+                                ngDialogData={traceDetailData}
+                            />
+                        </div>
+                    </div>
+                )}
+            </div>
+        </>
+
+    );
+};
+
+export default MessageTraceQueryPage;
diff --git a/frontend-new/src/pages/Ops/ops.jsx b/frontend-new/src/pages/Ops/ops.jsx
new file mode 100644
index 0000000..246c590
--- /dev/null
+++ b/frontend-new/src/pages/Ops/ops.jsx
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React, { useState, useEffect } from 'react';
+import { Select, Button, Switch, Input, Typography, Space, message } from 'antd';
+import {remoteApi} from '../../api/remoteApi/remoteApi';
+
+const { Title } = Typography;
+const { Option } = Select;
+
+const Ops = () => {
+    const [namesrvAddrList, setNamesrvAddrList] = useState([]);
+    const [selectedNamesrv, setSelectedNamesrv] = useState('');
+    const [newNamesrvAddr, setNewNamesrvAddr] = useState('');
+    const [useVIPChannel, setUseVIPChannel] = useState(false);
+    const [useTLS, setUseTLS] = useState(false);
+    const [writeOperationEnabled, setWriteOperationEnabled] = useState(true); // Default to true
+    const [messageApi, msgContextHolder] = message.useMessage();
+    useEffect(() => {
+        const fetchOpsData = async () => {
+            const userRole = sessionStorage.getItem("userrole");
+            setWriteOperationEnabled(userRole === null || userRole === "1"); // Assuming "1" means write access
+
+            const resp = await remoteApi.queryOpsHomePage();
+            if (resp.status === 0) {
+                setNamesrvAddrList(resp.data.namesvrAddrList);
+                setUseVIPChannel(resp.data.useVIPChannel);
+                setUseTLS(resp.data.useTLS);
+                setSelectedNamesrv(resp.data.currentNamesrv);
+            } else {
+                messageApi.error(resp.errMsg);
+            }
+        };
+        fetchOpsData();
+    }, []);
+
+    const handleUpdateNameSvrAddr = async () => {
+        if (!selectedNamesrv) {
+            messageApi.warning('请选择一个 NameServer 地址');
+            return;
+        }
+        const resp = await remoteApi.updateNameSvrAddr(selectedNamesrv);
+        if (resp.status === 0) {
+            messageApi.info('UPDATE SUCCESS');
+        } else {
+            messageApi.error(resp.errMsg);
+        }
+    };
+
+    const handleAddNameSvrAddr = async () => {
+        if (!newNamesrvAddr.trim()) {
+            messageApi.warning('请输入新的 NameServer 地址');
+            return;
+        }
+        const resp = await remoteApi.addNameSvrAddr(newNamesrvAddr.trim());
+        if (resp.status === 0) {
+            if (!namesrvAddrList.includes(newNamesrvAddr.trim())) {
+                setNamesrvAddrList([...namesrvAddrList, newNamesrvAddr.trim()]);
+            }
+            setNewNamesrvAddr('');
+            messageApi.info('ADD SUCCESS');
+        } else {
+            messageApi.error(resp.errMsg);
+        }
+    };
+
+    const handleUpdateIsVIPChannel = async (checked) => {
+        setUseVIPChannel(checked); // Optimistic update
+        const resp = await remoteApi.updateIsVIPChannel(checked);
+        if (resp.status === 0) {
+            messageApi.info('UPDATE SUCCESS');
+        } else {
+            messageApi.error(resp.errMsg);
+            setUseVIPChannel(!checked); // Revert on error
+        }
+    };
+
+    const handleUpdateUseTLS = async (checked) => {
+        setUseTLS(checked); // Optimistic update
+        const resp = await remoteApi.updateUseTLS(checked);
+        if (resp.status === 0) {
+            messageApi.info('UPDATE SUCCESS');
+        } else {
+            messageApi.error(resp.errMsg);
+            setUseTLS(!checked); // Revert on error
+        }
+    };
+
+    return (
+        <>
+            {msgContextHolder}
+            <div style={{padding: 24}}>
+                <div style={{marginBottom: 24}}>
+                    <Title level={4}>NameServerAddressList</Title>
+                    <Space wrap align="start">
+                        <Select
+                            style={{minWidth: 400, maxWidth: 500}}
+                            value={selectedNamesrv}
+                            onChange={setSelectedNamesrv}
+                            disabled={!writeOperationEnabled}
+                            placeholder="请选择 NameServer 地址"
+                        >
+                            {namesrvAddrList.map((addr) => (
+                                <Option key={addr} value={addr}>
+                                    {addr}
+                                </Option>
+                            ))}
+                        </Select>
+
+                        {writeOperationEnabled && (
+                            <Button type="primary" onClick={handleUpdateNameSvrAddr}>
+                                UPDATE
+                            </Button>
+                        )}
+
+                        {writeOperationEnabled && (
+                            <Input.Group compact style={{minWidth: 400}}>
+                                <Input
+                                    style={{width: 300}}
+                                    placeholder="NamesrvAddr"
+                                    value={newNamesrvAddr}
+                                    onChange={(e) => setNewNamesrvAddr(e.target.value)}
+                                />
+                                <Button type="primary" onClick={handleAddNameSvrAddr}>
+                                    ADD
+                                </Button>
+                            </Input.Group>
+                        )}
+                    </Space>
+                </div>
+
+                <div style={{marginBottom: 24}}>
+                    <Title level={4}>IsUseVIPChannel</Title>
+                    <Space align="center">
+                        <Switch
+                            checked={useVIPChannel}
+                            onChange={handleUpdateIsVIPChannel}
+                            disabled={!writeOperationEnabled}
+                        />
+                        {writeOperationEnabled && (
+                            <Button type="primary" onClick={() => handleUpdateIsVIPChannel(useVIPChannel)}>
+                                UPDATE
+                            </Button>
+                        )}
+                    </Space>
+                </div>
+
+                <div style={{marginBottom: 24}}>
+                    <Title level={4}>useTLS</Title>
+                    <Space align="center">
+                        <Switch
+                            checked={useTLS}
+                            onChange={handleUpdateUseTLS}
+                            disabled={!writeOperationEnabled}
+                        />
+                        {writeOperationEnabled && (
+                            <Button type="primary" onClick={() => handleUpdateUseTLS(useTLS)}>
+                                UPDATE
+                            </Button>
+                        )}
+                    </Space>
+                </div>
+            </div>
+        </>
+
+    );
+};
+
+export default Ops;