Skip to content

Commit bd94e8c

Browse files
authored
[GSOC][RIP-78][ISSUES#308] Add part of refactored front-end files (#311)
1 parent 52545cc commit bd94e8c

20 files changed

+2885
-0
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { Input, Select, Tag, Space } from 'antd';
19+
import { PlusOutlined } from '@ant-design/icons';
20+
import React, { useState } from 'react';
21+
22+
const { Option } = Select;
23+
24+
// 资源类型枚举
25+
const resourceTypes = [
26+
{ value: 0, label: 'Unknown', prefix: 'UNKNOWN' },
27+
{ value: 1, label: 'Any', prefix: 'ANY' },
28+
{ value: 2, label: 'Cluster', prefix: 'CLUSTER' },
29+
{ value: 3, label: 'Namespace', prefix: 'NAMESPACE' },
30+
{ value: 4, label: 'Topic', prefix: 'TOPIC' },
31+
{ value: 5, label: 'Group', prefix: 'GROUP' },
32+
];
33+
34+
const ResourceInput = ({ value = [], onChange }) => {
35+
// 确保 value 始终是数组
36+
const safeValue = Array.isArray(value) ? value : [];
37+
38+
const [selectedType, setSelectedType] = useState(resourceTypes[0].prefix); // 默认选中第一个
39+
const [resourceName, setResourceName] = useState('');
40+
const [inputVisible, setInputVisible] = useState(false);
41+
const inputRef = React.useRef(null);
42+
43+
// 处理删除已添加的资源
44+
const handleClose = removedResource => {
45+
const newResources = safeValue.filter(resource => resource !== removedResource);
46+
onChange(newResources);
47+
};
48+
49+
// 显示输入框
50+
const showInput = () => {
51+
setInputVisible(true);
52+
setTimeout(() => {
53+
inputRef.current?.focus();
54+
}, 0);
55+
};
56+
57+
// 处理资源类型选择
58+
const handleTypeChange = type => {
59+
setSelectedType(type);
60+
};
61+
62+
// 处理资源名称输入
63+
const handleNameChange = e => {
64+
setResourceName(e.target.value);
65+
};
66+
67+
// 添加资源到列表
68+
const handleAddResource = () => {
69+
if (resourceName) {
70+
const fullResource = `${selectedType}:${resourceName}`;
71+
// 避免重复添加
72+
if (!safeValue.includes(fullResource)) {
73+
onChange([...safeValue, fullResource]);
74+
}
75+
setResourceName(''); // 清空输入
76+
setInputVisible(false); // 隐藏输入框
77+
}
78+
};
79+
80+
return (
81+
<Space size={[0, 8]} wrap>
82+
{/* 显示已添加的资源标签 */}
83+
{safeValue.map(resource => ( // 使用 safeValue
84+
<Tag
85+
key={resource}
86+
closable
87+
onClose={() => handleClose(resource)}
88+
color="blue"
89+
>
90+
{resource}
91+
</Tag>
92+
))}
93+
94+
{/* 新增资源输入区域 */}
95+
{inputVisible ? (
96+
<Space>
97+
<Select
98+
value={selectedType}
99+
style={{ width: 120 }}
100+
onChange={handleTypeChange}
101+
>
102+
{resourceTypes.map(type => (
103+
<Option key={type.value} value={type.prefix}>
104+
{type.label}
105+
</Option>
106+
))}
107+
</Select>
108+
<Input
109+
ref={inputRef}
110+
style={{ width: 180 }}
111+
value={resourceName}
112+
onChange={handleNameChange}
113+
onPressEnter={handleAddResource}
114+
onBlur={handleAddResource} // 失去焦点也自动添加
115+
placeholder="请输入资源名称"
116+
/>
117+
</Space>
118+
) : (
119+
<Tag onClick={showInput} style={{ background: '#fff', borderStyle: 'dashed' }}>
120+
<PlusOutlined /> 添加资源
121+
</Tag>
122+
)}
123+
</Space>
124+
);
125+
};
126+
127+
export default ResourceInput;
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { Input, Select } from 'antd';
19+
import React, { useState, useEffect } from 'react';
20+
21+
const { Option } = Select;
22+
23+
// Subject 类型枚举
24+
const subjectTypes = [
25+
{ value: 'User', label: 'User' },
26+
];
27+
28+
const SubjectInput = ({ value, onChange, disabled }) => {
29+
// 解析传入的 value,将其拆分为 type 和 name
30+
const parseValue = (val) => {
31+
if (!val || typeof val !== 'string') {
32+
return { type: subjectTypes[0].value, name: '' }; // 默认值
33+
}
34+
const parts = val.split(':');
35+
if (parts.length === 2 && subjectTypes.some(t => t.value === parts[0])) {
36+
return { type: parts[0], name: parts[1] };
37+
}
38+
return { type: subjectTypes[0].value, name: val }; // 如果格式不匹配,将整个值作为 name,类型设为默认
39+
};
40+
41+
const [currentType, setCurrentType] = useState(() => parseValue(value).type);
42+
const [currentName, setCurrentName] = useState(() => parseValue(value).name);
43+
44+
// 当外部 value 变化时,更新内部状态
45+
useEffect(() => {
46+
const parsed = parseValue(value);
47+
setCurrentType(parsed.type);
48+
setCurrentName(parsed.name);
49+
}, [value]);
50+
51+
// 当类型或名称变化时,通知 Form.Item
52+
const triggerChange = (changedType, changedName) => {
53+
if (onChange) {
54+
// 只有当名称不为空时才组合,否则只返回类型或空字符串
55+
if (changedName) {
56+
onChange(`${changedType}:${changedName}`);
57+
} else if (changedType) { // 如果只选择了类型,但名称为空,则不组合
58+
onChange(''); // 或者根据需求返回 'User:' 等,但通常这种情况下不应该有值
59+
} else {
60+
onChange('');
61+
}
62+
}
63+
};
64+
65+
const onTypeChange = (newType) => {
66+
setCurrentType(newType);
67+
triggerChange(newType, currentName);
68+
};
69+
70+
const onNameChange = (e) => {
71+
const newName = e.target.value;
72+
setCurrentName(newName);
73+
triggerChange(currentType, newName);
74+
};
75+
76+
return (
77+
<Input.Group compact>
78+
<Select
79+
style={{ width: '30%' }}
80+
value={currentType}
81+
onChange={onTypeChange}
82+
disabled={disabled}
83+
>
84+
{subjectTypes.map(type => (
85+
<Option key={type.value} value={type.value}>
86+
{type.label}
87+
</Option>
88+
))}
89+
</Select>
90+
<Input
91+
style={{ width: '70%' }}
92+
value={currentName}
93+
onChange={onNameChange}
94+
placeholder="请输入名称 (例如: yourUsername)"
95+
disabled={disabled}
96+
/>
97+
</Input.Group>
98+
);
99+
};
100+
101+
export default SubjectInput;
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import React, { useState, useEffect } from 'react';
19+
import { Modal, Table, Spin } from 'antd';
20+
import { remoteApi } from '../../api/remoteApi/remoteApi';
21+
import { useLanguage } from '../../i18n/LanguageContext';
22+
23+
const ClientInfoModal = ({ visible, group, address, onCancel }) => {
24+
const { t } = useLanguage();
25+
const [loading, setLoading] = useState(false);
26+
const [connectionData, setConnectionData] = useState(null);
27+
const [subscriptionData, setSubscriptionData] = useState(null);
28+
29+
useEffect(() => {
30+
const fetchData = async () => {
31+
if (!visible) return;
32+
33+
setLoading(true);
34+
try {
35+
const connResponse = await remoteApi.queryConsumerConnection(group, address);
36+
const topicResponse = await remoteApi.queryTopicByConsumer(group, address);
37+
38+
if (connResponse.status === 0) setConnectionData(connResponse.data);
39+
if (topicResponse.status === 0) setSubscriptionData(topicResponse.data);
40+
} finally {
41+
setLoading(false);
42+
}
43+
};
44+
45+
fetchData();
46+
}, [visible, group, address]);
47+
48+
const connectionColumns = [
49+
{ title: 'ClientId', dataIndex: 'clientId' },
50+
{ title: 'ClientAddr', dataIndex: 'clientAddr' },
51+
{ title: 'Language', dataIndex: 'language' },
52+
{ title: 'Version', dataIndex: 'versionDesc' },
53+
];
54+
55+
const subscriptionColumns = [
56+
{ title: 'Topic', dataIndex: 'topic' },
57+
{ title: 'SubExpression', dataIndex: 'subString' },
58+
];
59+
60+
return (
61+
<Modal
62+
title={`[${group}]${t.CLIENT}`}
63+
visible={visible}
64+
onCancel={onCancel}
65+
footer={null}
66+
width={800}
67+
>
68+
<Spin spinning={loading}>
69+
{connectionData && (
70+
<>
71+
<Table
72+
columns={connectionColumns}
73+
dataSource={connectionData.connectionSet}
74+
rowKey="clientId"
75+
pagination={false}
76+
/>
77+
<h4>{t.SUBSCRIPTION}</h4>
78+
<Table
79+
columns={subscriptionColumns}
80+
dataSource={
81+
subscriptionData?.subscriptionTable
82+
? Object.entries(subscriptionData.subscriptionTable).map(([topic, detail]) => ({
83+
topic,
84+
...detail,
85+
}))
86+
: []
87+
}
88+
rowKey="topic"
89+
pagination={false}
90+
locale={{
91+
emptyText: loading ? <Spin size="small" /> : t.NO_DATA
92+
}}
93+
/>
94+
<p>ConsumeType: {connectionData.consumeType}</p>
95+
<p>MessageModel: {connectionData.messageModel}</p>
96+
</>
97+
)}
98+
</Spin>
99+
</Modal>
100+
);
101+
};
102+
103+
export default ClientInfoModal;

0 commit comments

Comments
 (0)