/* * 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) => ( ), }, ]; 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) => ( ), }, ]; return ( <> {notificationContextHolder}
{t.TRACE_TOPIC}:}> ({t.TRACE_TOPIC_HINT})
{t.ONLY_RETURN_64_MESSAGES}
setKey(e.target.value)} required />
{t.MESSAGE_ID_TOPIC_HINT}
setMessageId(e.target.value)} required />
{/* MessageTraceDetailViewDialog as a child component */} {isTraceDetailViewOpen && traceDetailData && (
{t.MESSAGE_TRACE_DETAIL}
)} ); }; export default MessageTraceQueryPage;