You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@inlong.apache.org by do...@apache.org on 2021/07/13 04:38:26 UTC

[incubator-inlong] 06/10: feat(broker):topic detail page complete

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

dockerzhang pushed a commit to branch new-web-client
in repository https://gitbox.apache.org/repos/asf/incubator-inlong.git

commit 9cd056f1b90914456f37ff136d1f5f772218f42f
Author: zakwu <12...@qq.com>
AuthorDate: Mon Jun 29 18:09:04 2020 +0800

    feat(broker):topic detail page complete
---
 web/src/components/Layout/index.tsx     |   2 +-
 web/src/components/TitleWrap/index.less |   4 +
 web/src/components/TitleWrap/index.tsx  |   5 +-
 web/src/constants/broker.ts             |   4 +
 web/src/constants/person.ts             |   4 +
 web/src/constants/topic.ts              |   3 +-
 web/src/pages/Broker/commonModal.tsx    |   3 +-
 web/src/pages/Broker/detail.tsx         |   2 +-
 web/src/pages/Broker/index.less         |   2 +-
 web/src/pages/Topic/commonModal.tsx     |  26 +-
 web/src/pages/Topic/detail.tsx          | 413 +++++++++++++++++++++++++++++++-
 web/src/pages/Topic/index.less          |   4 +-
 web/src/pages/Topic/index.tsx           |   1 -
 web/src/pages/Topic/query.tsx           |  64 +++--
 web/src/routes/index.tsx                |   8 +-
 web/src/setupProxy.js                   |   4 +-
 16 files changed, 501 insertions(+), 48 deletions(-)

diff --git a/web/src/components/Layout/index.tsx b/web/src/components/Layout/index.tsx
index 58ebb20..a4a4ddd 100644
--- a/web/src/components/Layout/index.tsx
+++ b/web/src/components/Layout/index.tsx
@@ -38,7 +38,7 @@ const BasicLayout: React.FC = props => {
     <>
       <ProBasicLayout
         title="TubeMQ"
-        logo="logo192.png"
+        logo="/logo192.png"
         menuDataRender={() => menuData}
         menuItemRender={(menuItemProps, defaultDom) => {
           if (menuItemProps.isUrl || !menuItemProps.path) {
diff --git a/web/src/components/TitleWrap/index.less b/web/src/components/TitleWrap/index.less
index c44787f..b64cf1b 100644
--- a/web/src/components/TitleWrap/index.less
+++ b/web/src/components/TitleWrap/index.less
@@ -5,3 +5,7 @@
   margin-bottom: 15px;
   position: relative;
 }
+
+.split-border {
+  border-top: 1px solid #eee;
+}
diff --git a/web/src/components/TitleWrap/index.tsx b/web/src/components/TitleWrap/index.tsx
index bcf125f..cf2c3f3 100644
--- a/web/src/components/TitleWrap/index.tsx
+++ b/web/src/components/TitleWrap/index.tsx
@@ -5,11 +5,14 @@ interface ComProps {
   title: any;
   children?: any;
   wrapperStyle?: any;
+  hasSplit?: boolean;
 }
 
 const Comp = (props: ComProps) => {
+  const {hasSplit = true} = props;
+
   return (
-    <div style={props.wrapperStyle}>
+    <div style={props.wrapperStyle} className={hasSplit ? 'split-border' : ''}>
       <div className="title-wrap-title">{props.title}</div>
       {props.children}
     </div>
diff --git a/web/src/constants/broker.ts b/web/src/constants/broker.ts
index e2b17c6..7270aae 100644
--- a/web/src/constants/broker.ts
+++ b/web/src/constants/broker.ts
@@ -15,4 +15,8 @@ export const BROKER_INFO_ZH_MAP = {
   manageStatus: '管理状态',
   runStatus: '运行状态',
   subStatus: '运行子状态',
+  'runInfo.acceptPublish': 'broker可发布状态',
+  'runInfo.acceptSubscribe': 'broker可订阅状态',
+  'runInfo.numPartitions': 'broker分区数',
+  'runInfo.brokerManageStatus': 'broker运行状态'
 };
\ No newline at end of file
diff --git a/web/src/constants/person.ts b/web/src/constants/person.ts
new file mode 100644
index 0000000..1bd1b98
--- /dev/null
+++ b/web/src/constants/person.ts
@@ -0,0 +1,4 @@
+export const PERSON_INFO_ZH_MAP = {
+  createDate: '创建时间',
+  createUser: '创建人',
+};
\ No newline at end of file
diff --git a/web/src/constants/topic.ts b/web/src/constants/topic.ts
index 2757340..1f1cf9c 100644
--- a/web/src/constants/topic.ts
+++ b/web/src/constants/topic.ts
@@ -1,9 +1,10 @@
 export const TOPIC_INFO_ZH_MAP = {
-  topicName: 'Topic',
+  topicName: 'TopicName',
   infoCount: '配置Broker数',
   totalCfgNumPart: '配置分区数',
   totalRunNumPartCount: '运行分区数',
   isSrvAcceptPublish: '可发布',
   isSrvAcceptSubscribe: '可订阅',
   enableAuthControl: "权限受控",
+  groupCount: '授权消费组'
 };
\ No newline at end of file
diff --git a/web/src/pages/Broker/commonModal.tsx b/web/src/pages/Broker/commonModal.tsx
index 3543ebf..fb113d8 100644
--- a/web/src/pages/Broker/commonModal.tsx
+++ b/web/src/pages/Broker/commonModal.tsx
@@ -227,6 +227,7 @@ export const onOpenModal = (p: BrokerModalProps) => {
       onCancel: () =>
         updateFunction((m: any) => {
           m.visible = false;
+          m.isOk = null;
         }),
     });
   });
@@ -253,7 +254,7 @@ const Comp = (props: ComProps) => {
         {modalParams.type === 'editBroker' && renderEditBroker(modalParams, form)}
         {modalParams.type === 'brokerStateChange' && renderBrokerStateChange(modalParams)}
       </div>
-      <Query fire={modalParams.isOk} params={modalParams.okParams} type={modalParams.type} />
+      <Query fire={modalParams.isOk} params={modalParams.okParams} type={modalParams.query || modalParams.type} />
     </Modal>
   )
 };
diff --git a/web/src/pages/Broker/detail.tsx b/web/src/pages/Broker/detail.tsx
index deb3d30..b622dc7 100644
--- a/web/src/pages/Broker/detail.tsx
+++ b/web/src/pages/Broker/detail.tsx
@@ -237,7 +237,7 @@ const Detail: React.FC = () => {
     <Spin spinning={queryBrokerConf.loading && queryTopicInfo.loading}>
       <Breadcrumb
         breadcrumbMap={breadMap}
-        appendParams={`Broker(${id})`}
+        appendParams={`Broker(${id})详情`}
       />
       <div className="main-container">
         <TitleWrap title="运行状态" wrapperStyle={{position: 'relative'}}>
diff --git a/web/src/pages/Broker/index.less b/web/src/pages/Broker/index.less
index c0e07eb..c95e98d 100644
--- a/web/src/pages/Broker/index.less
+++ b/web/src/pages/Broker/index.less
@@ -1,6 +1,6 @@
 .broker-detail-options-wrapper {
   position: absolute;
-  top: 0;
+  top: 15px;
   right: 0;
 
   .mr10 {
diff --git a/web/src/pages/Topic/commonModal.tsx b/web/src/pages/Topic/commonModal.tsx
index 7b28980..3200060 100644
--- a/web/src/pages/Topic/commonModal.tsx
+++ b/web/src/pages/Topic/commonModal.tsx
@@ -145,20 +145,20 @@ const renderChooseBroker = (modalParams: any) => {
     },
     {
       title: '实例数',
-      dataIndex: 'runInfo.totalTopicStoreNum',
+      dataIndex: ['runInfo', 'numTopicStores'],
     },
     {
       title: '当前运行状态',
-      dataIndex: 'runInfo.brokerManageStatus',
+      dataIndex: ['runInfo', 'brokerManageStatus'],
     },
     {
       title: '可发布',
-      dataIndex: 'runInfo.acceptPublish',
+      dataIndex: ['runInfo', 'acceptPublish'],
       render: (t: string) => boolean2Chinese(t),
     },
     {
       title: '可订阅',
-      dataIndex: 'runInfo.acceptSubscribe',
+      dataIndex: ['runInfo', 'acceptSubscribe'],
       render: (t: string) => boolean2Chinese(t),
     },
   ];
@@ -176,7 +176,7 @@ const renderChooseBroker = (modalParams: any) => {
 };
 const renderEditTopic = (modalParams: any, form: FormProps['form']) => {
   const {params: p} = modalParams;
-  const pickArr = ['numPartitions', 'unflushThreshold', 'unflushInterval', 'deleteWhen', 'deletePolicy', 'acceptPublish', 'acceptSubscribe'];
+  const pickArr = ['topicName', 'numPartitions', 'unflushThreshold', 'unflushInterval', 'deleteWhen', 'deletePolicy', 'acceptPublish', 'acceptSubscribe'];
   let brokerFormArr : Array<{
     name: string;
     defaultValue: string;
@@ -229,6 +229,16 @@ const renderDeleteTopic = (modalParams: any) => {
     </div>
   );
 };
+const renderDeleteConsumeGroup = (modalParams: any) => {
+  const { params } = modalParams;
+
+  return (
+    <div>
+      确认<span className="enhance">删除</span> 以下 :
+      <span className="enhance">({params.groupName})</span> 吗?
+    </div>
+  );
+};
 const renderAuthorizeControlChange = (modalParams: any) => {
   const { params } = modalParams;
 
@@ -251,7 +261,7 @@ export const onOpenModal = (p: TopicModalProps) => {
         updateFunction((m: any) => {
           if(type === 'newTopic' || type === 'editTopic') {
             p.params = Object.assign(f && f.getFieldsValue(), {
-              callback: p.params.callback
+              callback: p.params.callback,
             });
           }
 
@@ -263,7 +273,7 @@ export const onOpenModal = (p: TopicModalProps) => {
 
             // end
             if(type === 'chooseBroker') {
-              m.query = 'endChooseBroker';
+              m.query = p.params.subType === 'edit' ? 'endEditChooseBroker' : 'endChooseBroker';
             }
             p.params = Object.assign({}, p.params, {
               selectBroker
@@ -277,6 +287,7 @@ export const onOpenModal = (p: TopicModalProps) => {
       onCancel: () =>
         updateFunction((m: any) => {
           m.visible = false;
+          m.isOk = null;
         }),
     });
   });
@@ -301,6 +312,7 @@ const Comp = (props: ComProps) => {
         {modalParams.type === 'editTopic' && renderEditTopic(modalParams, form)}
         {modalParams.type === 'topicStateChange' && renderTopicStateChange(modalParams)}
         {modalParams.type === 'deleteTopic' && renderDeleteTopic(modalParams)}
+        {modalParams.type === 'deleteConsumeGroup' && renderDeleteConsumeGroup(modalParams)}
         {modalParams.type === 'authorizeControl' && renderAuthorizeControlChange(modalParams)}
       </div>
      <Query fire={modalParams.isOk} params={modalParams.okParams} type={modalParams.visible && (modalParams.query || modalParams.type)} />
diff --git a/web/src/pages/Topic/detail.tsx b/web/src/pages/Topic/detail.tsx
index 693da49..37cb1e5 100644
--- a/web/src/pages/Topic/detail.tsx
+++ b/web/src/pages/Topic/detail.tsx
@@ -1 +1,412 @@
-export {}
\ No newline at end of file
+import React, {ReactNode, useContext, useState} from 'react';
+import GlobalContext from '@/context/globalContext';
+import Breadcrumb from '@/components/Breadcrumb';
+import Table from '@/components/Tablex';
+import TitleWrap from '@/components/TitleWrap';
+import {Form, Button, Spin, Col, Row, Switch} from 'antd';
+import { useImmer } from 'use-immer';
+import './index.less';
+import { useRequest } from '@/hooks';
+import {useParams} from 'react-router-dom';
+import {boolean2Chinese, transParamsWithConstantsMap} from "@/utils";
+import tableFilterHelper from "@/components/Tablex/tableFilterHelper";
+import CommonModal, {onOpenModal, TopicResultData} from './commonModal';
+import BrokerModal, {BrokerData, BrokerModalProps, onOpenModal as onOpenBrokerModal} from '@/pages/Broker/commonModal';
+import {BROKER_INFO_ZH_MAP} from "@/constants/broker";
+import {PERSON_INFO_ZH_MAP} from "@/constants/person";
+import {TOPIC_INFO_ZH_MAP} from "@/constants/topic";
+
+declare type TopicQueryData = {
+  topicName: string;
+};
+
+const Detail: React.FC = () => {
+  const { name } = useParams();
+  const { breadMap } = useContext(GlobalContext);
+  const [form] = Form.useForm();
+  const [modalParams, updateModelParams] = useImmer<any>({});
+  const [brokerModalParams, updateBrokerModalParams] = useImmer<any>({});
+  const [isSrvAcceptPublish, setIsSrvAcceptPublish] = useState<any>(false);
+  const [isSrvAcceptSubscribe, setIsSrvAcceptSubscribe] = useState<any>(false);
+  const [enableAuthControl, setEnableAuthControl] = useState<any>(false);
+  const [filterData, updateFilterData] = useImmer<any>({});
+  const queryTopicInfo = useRequest<any>((data: TopicQueryData = {
+    topicName: name,
+  }) => ({
+    url: '/api/op_query/admin_query_topic_authorize_control',
+    data: {
+      ...data
+    }
+  }));
+  const queryTopicConf = useRequest<any>((data: TopicQueryData = {
+    topicName: name,
+  }) => ({
+    url: '/api/op_query/admin_query_topic_info',
+    data: {
+      ...data
+    }
+  }), {
+    onSuccess: data => {
+      setIsSrvAcceptPublish(data[0]['isSrvAcceptPublish']);
+      setIsSrvAcceptSubscribe(data[0]['isSrvAcceptSubscribe']);
+      setEnableAuthControl(data[0]['authData']['enableAuthControl']);
+    }
+  });
+
+  // render
+  const searchStyle = {
+    position: 'absolute',
+    top: '-40px',
+    right: '10px',
+    zIndex: 1,
+    width: '300px'
+  };
+  const renderBrokerList = (): ReactNode => {
+    const columns = [{
+        title: 'Broker',
+        render: (t: string, r: TopicResultData) => {
+          return `${r.brokerId}#${r.brokerIp}:${r.brokerPort}`;
+        },
+      },
+      {
+        title: transParamsWithConstantsMap(BROKER_INFO_ZH_MAP, 'runInfo.acceptPublish'),
+        dataIndex: ['runInfo', 'acceptPublish'],
+        render: (t: string) => boolean2Chinese(t),
+      },
+      {
+        title: transParamsWithConstantsMap(BROKER_INFO_ZH_MAP, 'runInfo.acceptSubscribe'),
+        dataIndex: ['runInfo', 'acceptSubscribe'],
+        render: (t: string) => boolean2Chinese(t),
+      },
+      {
+        title: transParamsWithConstantsMap(BROKER_INFO_ZH_MAP, 'runInfo.numPartitions'),
+        dataIndex: ['runInfo', 'numPartitions'],
+      },
+      {
+        title: transParamsWithConstantsMap(BROKER_INFO_ZH_MAP, 'runInfo.brokerManageStatus'),
+        dataIndex: ['runInfo', 'brokerManageStatus'],
+      },
+      {
+        title: transParamsWithConstantsMap(BROKER_INFO_ZH_MAP, 'acceptPublish'),
+        dataIndex: 'acceptPublish',
+        render: (t: string) => boolean2Chinese(t),
+      },
+      {
+        title: transParamsWithConstantsMap(BROKER_INFO_ZH_MAP, 'acceptSubscribe'),
+        dataIndex: 'acceptSubscribe',
+        render: (t: string) => boolean2Chinese(t),
+      },
+      {
+        title: transParamsWithConstantsMap(BROKER_INFO_ZH_MAP, 'numPartitions'),
+        dataIndex: 'numPartitions',
+      },
+      {
+        title: transParamsWithConstantsMap(BROKER_INFO_ZH_MAP, 'unflushThreshold'),
+        dataIndex: 'unflushThreshold',
+      },
+      {
+        title: transParamsWithConstantsMap(BROKER_INFO_ZH_MAP, 'unflushInterval'),
+        dataIndex: 'unflushInterval',
+      },
+      {
+        title: transParamsWithConstantsMap(BROKER_INFO_ZH_MAP, 'deleteWhen'),
+        dataIndex: 'deleteWhen',
+      },
+      {
+        title: transParamsWithConstantsMap(BROKER_INFO_ZH_MAP, 'deletePolicy'),
+        dataIndex: 'deletePolicy',
+      },
+      {
+        title: '操作',
+        render: (t:string, r: TopicResultData) => {
+          return (<span>
+            <a onClick={() => onEdit(r)}>编辑</a>
+            <a onClick={() => onReload(r)}>重载</a>
+            <a onClick={() => onDeleteBroker(r)}>删除</a>
+          </span>)
+        }
+      }];
+    const {data} = queryTopicConf;
+    if(!data || !data[0]) return null;
+    const {topicInfo} = data[0];
+
+    return <Table columns={columns}
+                  dataSource={topicInfo}
+                  rowKey={r => `${r.brokerId}#${r.brokerIp}:${r.brokerPort}`}
+                  dataSourceX={filterData.topicInfoList}
+                  searchPlaceholder="请输入brokerId,Ip,Port搜索"
+                  searchStyle={searchStyle}
+                  filterFnX={value =>
+                    tableFilterHelper({
+                      key: value,
+                      srcArray: topicInfo,
+                      targetArray: filterData.topicInfoList,
+                      updateFunction: res =>
+                        updateFilterData(filterData => {
+                          filterData.topicInfoList = res;
+                        }),
+                      filterList: [
+                        'brokerId',
+                        'brokerIp',
+                        'brokerPort'
+                      ],
+                    })
+                  }
+    />;
+  }
+  const renderConsumeGroupList = (): ReactNode => {
+    const columns = [{
+        title: '消费组',
+        dataIndex: 'groupName',
+      }, {
+        title: transParamsWithConstantsMap(PERSON_INFO_ZH_MAP, 'createUser'),
+        dataIndex: 'createUser',
+      }, {
+        title: transParamsWithConstantsMap(PERSON_INFO_ZH_MAP, 'createDate'),
+        dataIndex: 'createDate',
+      },
+      {
+        title: '操作',
+        render: (t:string, r: TopicResultData) => {
+          return (<span>
+            <a onClick={() => onDeleteConsumeGroup(r)}>删除</a>
+          </span>)
+        }
+      }];
+    const {data} = queryTopicInfo;
+    if(!data || !data[0]) return null;
+    const {authConsumeGroup} = data[0];
+
+    return <Table columns={columns}
+                  dataSource={authConsumeGroup}
+                  rowKey={r => `${r.brokerId}#${r.brokerIp}:${r.brokerPort}`}
+                  dataSourceX={filterData.list}
+                  searchPlaceholder="请输入消费组名称搜索"
+                  searchStyle={searchStyle}
+                  filterFnX={value =>
+                    tableFilterHelper({
+                      key: value,
+                      srcArray: authConsumeGroup,
+                      targetArray: filterData.list,
+                      updateFunction: res =>
+                        updateFilterData(filterData => {
+                          filterData.list = res;
+                        }),
+                      filterList: [
+                        'groupName',
+                      ],
+                    })
+                  }
+    />;
+  }
+
+  // event
+  // isSrvAcceptPublish && isSrvAcceptSubscribe event
+  const queryBrokerListByTopicNameQuery = useRequest<any, any>(
+    data => ({ url: '/api/op_query/admin_query_topic_info', ...data }),
+    { manual: true }
+  );
+  const onSwitchChange = (e: boolean, type: string) => {
+    let option = '';
+    const topicName = queryTopicConf.data[0].topicInfo[0].topicName;
+    if (type === 'isSrvAcceptPublish') {
+      option = e ? '发布' : '禁止可发布';
+    } else if (type === 'isSrvAcceptSubscribe') {
+      option = e ? '订阅' : '禁止可订阅';
+    }
+
+    queryBrokerListByTopicNameQuery.run({
+      data: {
+        topicName,
+        brokerId: ''
+      },
+    }).then((d: TopicResultData) => {
+      onOpenModal({
+        type: 'topicStateChange',
+        title: `请确认操作`,
+        updateFunction: updateModelParams,
+        params: {
+          option,
+          value: e,
+          topicName,
+          data: d[0].topicInfo,
+          type,
+          callback: () => {
+            if (type === 'isSrvAcceptPublish') {
+              setIsSrvAcceptPublish(e);
+            } else if (type === 'isSrvAcceptSubscribe') {
+              setIsSrvAcceptSubscribe(e);
+            }
+          }
+        },
+      });
+    });
+  };
+  // author
+  const onAuthorizeControl = (e: boolean) => {
+    const option = e ? '发布' : '禁止可发布';
+    const topicName = queryTopicConf.data[0].topicInfo[0].topicName;
+    onOpenModal({
+      type: 'authorizeControl',
+      title: `请确认操作`,
+      updateFunction: updateModelParams,
+      params: {
+        option,
+        value: e,
+        topicName,
+        callback: () => {
+          setEnableAuthControl(e);
+        }
+      },
+    });
+  }
+  // edit topic
+  const onEdit = (r?: TopicResultData) => {
+    const p = r || queryTopicConf.data[0].topicInfo[0]
+    onOpenModal({
+      type: 'editTopic', title: '编辑Topic', updateFunction: updateModelParams, params: {
+        ...p,
+        callback: (d: any) => {
+          onOpenModal({
+            type: 'chooseBroker', title: '选择【修改】broker列表', updateFunction: updateModelParams, params: {
+              data: d,
+              subType: 'edit',
+              callback: () => {
+                onOpenModal({type: 'close', updateFunction: updateModelParams})
+              },
+            }
+          });
+        }
+      }
+    })
+  };
+  // reload topic
+  const queryBrokerInfo = useRequest<any, any>(
+    data => ({ url: '/api/op_query/admin_query_broker_run_status', ...data }),
+    { manual: true }
+  );
+  const onReload = (r: TopicResultData) => {
+    queryBrokerInfo.run({
+      data: {
+        brokerId: r.brokerId
+      }
+    }).then(data => {
+      onOpenBrokerModal({
+        type: 'reload',
+        title: `确认进行【重载】操作?`,
+        updateFunction: updateBrokerModalParams,
+        params: [data[0].brokerId],
+      });
+    })
+  }
+  // on delete broker
+  const onDeleteBroker = (r: TopicResultData) => {
+    queryBrokerListByTopicNameQuery.run({
+      data: {
+        topicName: r.topicName,
+        brokerId: r.brokerId
+      },
+    }).then((d: TopicResultData) => {
+      onOpenModal({
+        type: 'deleteTopic',
+        title: `请确认操作`,
+        updateFunction: updateModelParams,
+        params: {
+          topicName: r.topicName,
+          data: d[0].topicInfo,
+        },
+      });
+    });
+  }
+  const onDeleteConsumeGroup = (r: TopicResultData) => {
+    onOpenModal({
+      type: 'deleteConsumeGroup',
+      title: `请确认消费组`,
+      updateFunction: updateModelParams,
+      params: {
+        topicName: r.topicName,
+        groupName: r.groupName
+      },
+    });
+  }
+
+  return (
+    <Spin spinning={queryTopicConf.loading && queryTopicInfo.loading}>
+      <Breadcrumb
+        breadcrumbMap={breadMap}
+        appendParams={`Topic(${name})详情`}
+      />
+      <div className="main-container">
+        <TitleWrap title="基本信息" wrapperStyle={{position: 'relative'}} hasSplit={false}>
+          <div className="topic-detail-options-wrapper">
+            <Switch
+              className="mr10"
+              checked={isSrvAcceptPublish} checkedChildren="订阅" unCheckedChildren="订阅"
+              onChange={e => onSwitchChange(e, 'isSrvAcceptPublish')}
+            />
+            <Switch
+              className="mr10"
+              checked={isSrvAcceptSubscribe} checkedChildren="发布" unCheckedChildren="发布"
+              onChange={e => onSwitchChange(e, 'isSrvAcceptSubscribe')}
+            />
+            <Switch
+              className="mr10"
+              checked={enableAuthControl} checkedChildren="权限可控" unCheckedChildren="权限可控"
+              onChange={e => onAuthorizeControl(e)}
+            />
+          </div>
+          <Form form={form}>
+            <Row gutter={24}>
+              {queryTopicConf.data && Object.keys(queryTopicConf.data[0]).map((t: string, index: number) => {
+                const label = transParamsWithConstantsMap(TOPIC_INFO_ZH_MAP, t);
+                const ignoreList = ['isSrvAcceptPublish', 'isSrvAcceptSubscribe'];
+                if (queryTopicConf.data[0][t] instanceof Object || !label || ignoreList.includes(t)) return null
+                return (<Col span={12} key={'queryTopicConf' + index}>
+                  <Form.Item
+                    labelCol={{span: 12}}
+                    label={label}
+                  >
+                    {queryTopicConf.data[0][t] + ''}
+                  </Form.Item>
+                </Col>)
+              })}
+            </Row>
+          </Form>
+        </TitleWrap>
+        <TitleWrap title="缺省配置" wrapperStyle={{position: 'relative'}}>
+          <div className="topic-detail-options-wrapper">
+            <Button className="mr10" type="primary" size="small" onClick={() => onEdit()}>
+              编辑
+            </Button>
+          </div>
+          <Form form={form}>
+            <Row gutter={24}>
+              {['acceptPublish', 'acceptSubscribe', 'unflushThreshold', 'unflushInterval', 'deleteWhen', 'deletePolicy', 'numPartitions'].map((t: string, index: number) => {
+                if(!queryTopicConf.data || !queryTopicConf.data[0].topicInfo[0]) return null;
+                const value = queryTopicConf.data[0].topicInfo[0][t];
+                return (<Col span={12} key={'queryTopicConf' + index}>
+                  <Form.Item
+                    labelCol={{span: 12}}
+                    label={t}
+                  >
+                    {value + ''}
+                  </Form.Item>
+                </Col>)
+              })}
+            </Row>
+          </Form>
+        </TitleWrap>
+        <TitleWrap title="部署Broker列表">
+          {renderBrokerList()}
+        </TitleWrap>
+        <TitleWrap title="消费组列表">
+          {renderConsumeGroupList()}
+        </TitleWrap>
+      </div>
+      <CommonModal modalParams={modalParams} data={[queryTopicConf.data && queryTopicConf.data[0]]} />
+      <BrokerModal modalParams={brokerModalParams} data={[queryBrokerInfo.data && queryBrokerInfo.data[0]]} />
+    </Spin>
+  );
+};
+
+export default Detail;
diff --git a/web/src/pages/Topic/index.less b/web/src/pages/Topic/index.less
index c0e07eb..5efa767 100644
--- a/web/src/pages/Topic/index.less
+++ b/web/src/pages/Topic/index.less
@@ -1,6 +1,6 @@
-.broker-detail-options-wrapper {
+.topic-detail-options-wrapper {
   position: absolute;
-  top: 0;
+  top: 15px;
   right: 0;
 
   .mr10 {
diff --git a/web/src/pages/Topic/index.tsx b/web/src/pages/Topic/index.tsx
index d5b43c8..d85dc8b 100644
--- a/web/src/pages/Topic/index.tsx
+++ b/web/src/pages/Topic/index.tsx
@@ -148,7 +148,6 @@ const Topic: React.FC = () => {
         value: e,
         topicName: r.topicName,
         callback: () => {
-          debugger
           const index = data.findIndex(
             (t: TopicResultData) => t.topicName === r.topicName
           );
diff --git a/web/src/pages/Topic/query.tsx b/web/src/pages/Topic/query.tsx
index 13a4570..fc2a612 100644
--- a/web/src/pages/Topic/query.tsx
+++ b/web/src/pages/Topic/query.tsx
@@ -12,6 +12,7 @@ interface ComProps {
 }
 
 let newObjectTemp: string = '';
+let editObjectTemp: string = '';
 const Comp = (props: ComProps) => {
   const {fire} = props;
   const { userInfo } = useContext(GlobalContext);
@@ -33,6 +34,9 @@ const Comp = (props: ComProps) => {
       case 'editTopic':
         promise = editTopic(p);
         break;
+      case 'endEditChooseBroker':
+        promise = endEditChooseBroker(p);
+        break;
       case 'topicStateChange':
         promise = topicStateChange(type, p);
         break;
@@ -42,6 +46,9 @@ const Comp = (props: ComProps) => {
       case 'deleteTopic':
         promise = deleteTopic(type, p);
         break;
+      case 'deleteConsumeGroup':
+        promise = deleteConsumeGroup(type, p);
+        break;
     }
 
     promise && promise.then(t => {
@@ -49,7 +56,10 @@ const Comp = (props: ComProps) => {
       if(t.statusCode !== 0 && callback) callback(t);
     })
   };
-
+  const commonQuery = useRequest<any, any>(
+    (url, data) => ({ url, ...data }),
+    { manual: true }
+  );
   const newTopicQuery = useRequest<any, any>(
     data => ({ url: '/api/op_query/admin_query_broker_topic_config_info', ...data }),
     { manual: true }
@@ -80,28 +90,31 @@ const Comp = (props: ComProps) => {
     });
   };
 
-  const updateTopicQuery = useRequest<any, any>(
-    data => ({ url: '/api/op_modify/admin_update_broker_configure', ...data }),
-    { manual: true }
-  );
   const editTopic = (p: OKProps) => {
     const {params} = p;
-    return updateTopicQuery.run({
+    editObjectTemp = JSON.stringify(p.params);
+    return newTopicQuery.run({
+      data: {
+        topicName: params.topicName,
+        brokerId: '',
+      },
+    });
+  };
+  const endEditChooseBroker = (p: OKProps) => {
+    const topicParams = JSON.parse(editObjectTemp);
+    const {params} = p;
+    return commonQuery.run(`/api/op_modify/admin_modify_topic_info`, {
       data: {
-        ...params,
+        borkerId: params.selectBroker.join(','),
         confModAuthToken: p.psw,
-        createUser: userInfo.userName,
+        ...topicParams
       },
-    });
+    })
   };
 
-  const deleteTopicQuery = useRequest<any, any>(
-    (url, data) => ({ url, ...data }),
-    { manual: true }
-  );
   const deleteTopic = (type: string, p: OKProps) => {
     const { params } = p;
-    return deleteTopicQuery.run(`/api/op_modify/admin_delete_topic_info`, {
+    return commonQuery.run(`/api/op_modify/admin_delete_topic_info`, {
       data: {
         brokerId: params.selectBroker.join(','),
         confModAuthToken: p.psw,
@@ -110,11 +123,16 @@ const Comp = (props: ComProps) => {
       },
     });
   };
-
-  const topicStateChangeQuery = useRequest<any, any>(
-    (url, data) => ({ url, ...data }),
-    { manual: true }
-  );
+  const deleteConsumeGroup = (type: string, p: OKProps) => {
+    const { params } = p;
+    return commonQuery.run(`/api/op_modify/admin_delete_allowed_consumer_group_info`, {
+      data: {
+        groupName: params.groupName,
+        confModAuthToken: p.psw,
+        topicName: params.topicName
+      },
+    });
+  };
   const topicStateChange = (type: string, p: OKProps) => {
     const { params } = p;
     let data: {
@@ -134,15 +152,11 @@ const Comp = (props: ComProps) => {
       data.acceptSubscribe = params.value;
     }
 
-    return topicStateChangeQuery.run(`/api/op_modify/admin_modify_topic_info`, {
+    return commonQuery.run(`/api/op_modify/admin_modify_topic_info`, {
       data,
     });
   };
 
-  const authorizeControlQuery = useRequest<any, any>(
-    (url, data) => ({ url, ...data }),
-    { manual: true }
-  );
   const authorizeControl = (type: string, p: OKProps) => {
     const { params } = p;
     let data: {
@@ -156,7 +170,7 @@ const Comp = (props: ComProps) => {
       modifyUser: userInfo.userName,
     }
 
-    return authorizeControlQuery.run(`/api/op_modify/admin_set_topic_authorize_control`, {
+    return commonQuery.run(`/api/op_modify/admin_set_topic_authorize_control`, {
       data,
     });
   };
diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx
index 7fce665..285711d 100644
--- a/web/src/routes/index.tsx
+++ b/web/src/routes/index.tsx
@@ -17,10 +17,10 @@ const routes: RouteProps[] = [
     path: '/broker',
     component: () => import('@/pages/Broker'),
   },
-  // {
-  //   path: '/topic/:id',
-  //   component: () => import('@/pages/Topic/detail'),
-  // },
+  {
+    path: '/topic/:name',
+    component: () => import('@/pages/Topic/detail'),
+  },
   {
     path: '/topic',
     component: () => import('@/pages/Topic'),
diff --git a/web/src/setupProxy.js b/web/src/setupProxy.js
index b2f9c72..533447c 100644
--- a/web/src/setupProxy.js
+++ b/web/src/setupProxy.js
@@ -4,9 +4,9 @@ const { createProxyMiddleware } = require('http-proxy-middleware');
 module.exports = function(app) {
   app.use(
     createProxyMiddleware('/webapi.htm', {
-      target: ' http://9.56.46.168:8080',
+      // target: ' http://9.56.46.168:8080',
       // target: 'http://10.224.148.145:8080',
-      // target: 'http://10.215.131.92:8080',
+      target: 'http://10.215.131.92:8080',
       changeOrigin: true,
       ws: true,
     })